diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index fb7f4a8a465d42b4a0390d464b83b99e8465bba7..61a9130cd9669c3843e6445dfe1fee2d493869bc 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="CompilerConfiguration">
-    <bytecodeTargetLevel target="11" />
+    <bytecodeTargetLevel target="1.8" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index ef61796f57624d99df9ef9b2479763aed4ad62eb..d5d35ec44f10991b508f6454a85204a276726364 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">
diff --git a/CHANGES.md b/CHANGES.md
index 71ad471453558637894bb01dd19278b620b39bd4..292e34de5a789dd3ddeba696170a0b719610af50 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,28 @@
 Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/main/CHANGES.md
 
+Changes in Matrix-SDK 1.4.14 (2022-05-10)
+===================================================
+
+Imported from Element 1.4.14. (https://github.com/vector-im/element-android/releases/tag/v1.4.14)
+
+Bugfixes 🐛
+----------
+- Fixes crash when accepting or receiving VOIP calls ([#5421](https://github.com/vector-im/element-android/issues/5421))
+- Improve/fix crashes on messages decryption ([#5592](https://github.com/vector-im/element-android/issues/5592))
+- Don't pause timer when call is held. ([#5885](https://github.com/vector-im/element-android/issues/5885))
+
+SDK API changes ⚠️
+------------------
+- Added registrationCustom into RegistrationWizard to send custom auth params for sign up
+- Moved terms converter into api package to make it accessible in sdk ([#5575](https://github.com/vector-im/element-android/issues/5575))
+- Move package `org.matrix.android.sdk.api.pushrules` to `org.matrix.android.sdk.api.session.pushrules` ([#5812](https://github.com/vector-im/element-android/issues/5812))
+- Some `Session` apis are now available by requesting the service first. For instance `Session.updateAvatar(...)` is now `Session.profileService().updateAvatar(...)`
+- The shortcut `Room.search()` has been removed, you have to use `Session.searchService().search()` ([#5816](https://github.com/vector-im/element-android/issues/5816))
+- Add return type to RoomApi.sendStateEvent() to retrieve the created event id ([#5855](https://github.com/vector-im/element-android/issues/5855))
+- `Room` apis are now available by requesting the service first. For instance `Room.updateAvatar(...)` is now `Room.stateService().updateAvatar(...)` ([#5858](https://github.com/vector-im/element-android/issues/5858))
+- Remove unecessary field `eventId` from `EventAnnotationsSummary` and `ReferencesAggregatedSummary` ([#5890](https://github.com/vector-im/element-android/issues/5890))
+- Replace usage of `System.currentTimeMillis()` by a `Clock` interface ([#5907](https://github.com/vector-im/element-android/issues/5907))
+
 Changes in Matrix-SDK 1.4.13 (2022-04-26)
 ===================================================
 
diff --git a/gradle.properties b/gradle.properties
index 99aa02f1355b7f2cfbb32cf3b73d6b5377896b08..6402e3ad3a9b07b41f5e6aa166f272d93357f297 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -26,7 +26,7 @@ vector.httpLogLevel=NONE
 # Ref: https://github.com/vanniktech/gradle-maven-publish-plugin
 GROUP=org.matrix.android
 POM_ARTIFACT_ID=matrix-android-sdk2
-VERSION_NAME=1.4.13
+VERSION_NAME=1.4.14
 
 POM_PACKAGING=aar
 
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 18f4325027d6b4c8ffe8b7339155a2ed2115f985..8362d35960f8a43d103a6599481dfe6d90c2fba3 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -16,6 +16,27 @@ buildscript {
     }
 }
 
+dokkaHtml {
+    dokkaSourceSets {
+        configureEach {
+            // Emit warnings about not documented members.
+            reportUndocumented.set(true)
+            // Suppress legacy Riot's packages.
+            perPackageOption {
+                matchingRegex.set("org.matrix.android.sdk.internal.legacy.riot")
+                suppress.set(true)
+            }
+            perPackageOption {
+                matchingRegex.set("org.matrix.androidsdk.crypto.data")
+                suppress.set(true)
+            }
+            // List of files with module and package documentation
+            // https://kotlinlang.org/docs/reference/kotlin-doc.html#module-and-package-documentation
+            includes.from("./docs/modules.md", "./docs/packages.md")
+        }
+    }
+}
+
 android {
     testOptions.unitTests.includeAndroidResources = true
 
@@ -174,7 +195,7 @@ dependencies {
     implementation libs.apache.commonsImaging
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.46'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.47'
 
     testImplementation libs.tests.junit
     testImplementation 'org.robolectric:robolectric:4.7.3'
diff --git a/matrix-sdk-android/docs/modules.md b/matrix-sdk-android/docs/modules.md
new file mode 100644
index 0000000000000000000000000000000000000000..edf5af64d024d6a214e3657bc73701ec4182b214
--- /dev/null
+++ b/matrix-sdk-android/docs/modules.md
@@ -0,0 +1,18 @@
+# Module matrix-sdk-android
+
+## Welcome to the matrix-sdk-android documentation!
+
+This pages list the complete API that this SDK is exposing to a client application.
+
+*We are still building the documentation, so everything is not documented yet.*
+
+A few entry points:
+
+- **Matrix**: The app will have to create and manage a Matrix object.
+- From this **Matrix** object, you will be able to get various services, including the **AuthenticationService**.
+- With this **AuthenticationService** you will be able to get an existing **Session**, or create one using a **LoginWizard** or a **RegistrationWizard**, which will finally give you a **Session**.
+- From the **Session**, you will be able to retrieve many Services, including the **RoomService**.
+- From the **RoomService**, you will be able to list the rooms, create a **Room**, and get a specific **Room**.
+- And from a **Room**, you will be able to do many things, including get a **Timeline**, send messages, etc.
+
+Please read the whole documentation to learn more!
diff --git a/matrix-sdk-android/docs/packages.md b/matrix-sdk-android/docs/packages.md
new file mode 100644
index 0000000000000000000000000000000000000000..ae7bee1b4e0c8fb954fc39629e87dc79aafa26fd
--- /dev/null
+++ b/matrix-sdk-android/docs/packages.md
@@ -0,0 +1,3 @@
+# Package org.matrix.android.sdk.api
+
+This is the root package of the API exposed by this SDK.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
index 933074cdce2dc14f6c7d22b85ceaa2645db70d95..6d740c5a3421c5668b3fb13c5583f69ebe980944 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
@@ -46,7 +46,7 @@ class ChangePasswordTest : InstrumentedTest {
 
         // Change password
         commonTestHelper.runBlockingTest {
-            session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD)
+            session.accountService().changePassword(TestConstants.PASSWORD, NEW_PASSWORD)
         }
 
         // Try to login with the previous password, it will fail
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
index f8d108fb739c232140744e0ac3b358bbfabbb834..52dbfc7155b53f10b188dd845a02ee486efc2b26 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
@@ -47,7 +47,7 @@ class DeactivateAccountTest : InstrumentedTest {
 
         // Deactivate the account
         commonTestHelper.runBlockingTest {
-            session.deactivateAccount(
+            session.accountService().deactivateAccount(
                     eraseAllData = false,
                     userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
                         override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
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 ac4ccf56d109768ba72e978dff23c5710d005522..4b9e605cd0a54e068b1c6cdb5bad94e6f228bb18 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
@@ -57,7 +57,8 @@ import java.util.concurrent.TimeUnit
 class CommonTestHelper(context: Context) {
 
     internal val matrix: TestMatrix
-    val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
+    private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
+    private var accountNumber = 0
 
     fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
 
@@ -145,7 +146,7 @@ class CommonTestHelper(context: Context) {
      * @param nbOfMessages the number of time the message will be sent
      */
     fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
-        val timeline = room.createTimeline(null, TimelineSettings(10))
+        val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
         timeline.start()
         val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout)
         timeline.dispose()
@@ -165,11 +166,12 @@ class CommonTestHelper(context: Context) {
                 .forEach { batchedMessages ->
                     batchedMessages.forEach { formattedMessage ->
                         if (rootThreadEventId != null) {
-                            room.replyInThread(
+                            room.relationService().replyInThread(
                                     rootThreadEventId = rootThreadEventId,
-                                    replyInThreadText = formattedMessage)
+                                    replyInThreadText = formattedMessage
+                            )
                         } else {
-                            room.sendTextMessage(formattedMessage)
+                            room.sendService().sendTextMessage(formattedMessage)
                         }
                     }
                     waitWithLatch(timeout) { latch ->
@@ -214,7 +216,7 @@ class CommonTestHelper(context: Context) {
             numberOfMessages: Int,
             rootThreadEventId: String,
             timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
-        val timeline = room.createTimeline(null, TimelineSettings(10))
+        val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
         timeline.start()
         val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout, rootThreadEventId)
         timeline.dispose()
@@ -237,7 +239,7 @@ class CommonTestHelper(context: Context) {
                               password: String,
                               testParams: SessionTestParams): Session {
         val session = createAccountAndSync(
-                userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(),
+                userNamePrefix + "_" + accountNumber++ + "_" + UUID.randomUUID(),
                 password,
                 testParams
         )
@@ -431,7 +433,7 @@ class CommonTestHelper(context: Context) {
 
     fun signOutAndClose(session: Session) {
         runBlockingTest(timeout = 60_000) {
-            session.signOut(true)
+            session.signOutService().signOut(true)
         }
         // no need signout will close
         // session.close()
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 e3ab1a4921a5d943a9cacf4a26b4263bc0e39692..058b1f79338237569877e21ee8c255e0a0987e90 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
@@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.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.RoomSummary
@@ -64,12 +65,12 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
 
         val roomId = testHelper.runBlockingTest {
-            aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
+            aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" })
         }
         if (encryptedRoom) {
             testHelper.waitWithLatch { latch ->
                 val room = aliceSession.getRoom(roomId)!!
-                room.enableEncryption()
+                room.roomCryptoService().enableEncryption()
                 val roomSummaryLive = room.getRoomSummaryLive()
                 val roomSummaryObserver = object : Observer<Optional<RoomSummary>> {
                     override fun onChanged(roomSummary: Optional<RoomSummary>) {
@@ -98,7 +99,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
 
         testHelper.waitWithLatch { latch ->
-            val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
+            val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
             val newRoomObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (t?.isNotEmpty() == true) {
@@ -108,14 +109,15 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
                 }
             }
             bobRoomSummariesLive.observeForever(newRoomObserver)
-            aliceRoom.invite(bobSession.myUserId)
+            aliceRoom.membershipService().invite(bobSession.myUserId)
         }
 
         testHelper.waitWithLatch { latch ->
-            val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
+            val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
             val roomJoinedObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (bobSession.getRoom(aliceRoomId)
+                                    ?.membershipService()
                                     ?.getRoomMember(bobSession.myUserId)
                                     ?.membership == Membership.JOIN) {
                         bobRoomSummariesLive.removeObserver(this)
@@ -124,7 +126,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
                 }
             }
             bobRoomSummariesLive.observeForever(roomJoinedObserver)
-            bobSession.joinRoom(aliceRoomId)
+            bobSession.roomService().joinRoom(aliceRoomId)
         }
         // Ensure bob can send messages to the room
 //        val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
@@ -160,11 +162,11 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
 
         testHelper.runBlockingTest {
-            room.invite(samSession.myUserId, null)
+            room.membershipService().invite(samSession.myUserId, null)
         }
 
         testHelper.runBlockingTest {
-            samSession.joinRoom(room.roomId, null, emptyList())
+            samSession.roomService().joinRoom(room.roomId, null, emptyList())
         }
 
         return samSession
@@ -242,8 +244,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
     fun createDM(alice: Session, bob: Session): String {
         var roomId: String = ""
         testHelper.waitWithLatch { latch ->
-            roomId = alice.createDirectRoom(bob.myUserId)
-            val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
+            roomId = alice.roomService().createDirectRoom(bob.myUserId)
+            val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
             val newRoomObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (t?.any { it.roomId == roomId }.orFalse()) {
@@ -256,10 +258,11 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         }
 
         testHelper.waitWithLatch { latch ->
-            val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
+            val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
             val newRoomObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (bob.getRoom(roomId)
+                                    ?.membershipService()
                                     ?.getRoomMember(bob.myUserId)
                                     ?.membership == Membership.JOIN) {
                         bobRoomSummariesLive.removeObserver(this)
@@ -268,7 +271,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
                 }
             }
             bobRoomSummariesLive.observeForever(newRoomObserver)
-            bob.joinRoom(roomId)
+            bob.roomService().joinRoom(roomId)
         }
 
         return roomId
@@ -367,20 +370,20 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         aliceSession.cryptoService().setWarnOnUnknownDevices(false)
 
         val roomId = testHelper.runBlockingTest {
-            aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
+            aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" })
         }
         val room = aliceSession.getRoom(roomId)!!
 
         testHelper.runBlockingTest {
-            room.enableEncryption()
+            room.roomCryptoService().enableEncryption()
         }
 
         val sessions = mutableListOf(aliceSession)
         for (index in 1 until numberOfMembers) {
             val session = testHelper.createAccount("User_$index", defaultSessionParams)
-            testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
+            testHelper.runBlockingTest(timeout = 600_000) { room.membershipService().invite(session.myUserId, null) }
             println("TEST -> " + session.myUserId + " invited")
-            testHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
+            testHelper.runBlockingTest { session.roomService().joinRoom(room.roomId, null, emptyList()) }
             println("TEST -> " + session.myUserId + " joined")
             sessions.add(session)
         }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
index 732f4f7dce28b28422d5417b8920e588b7f1c486..f5f585a1e0c9f6e4daa9194220962fc5349e8f16 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
@@ -29,8 +29,10 @@ import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt
 import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
 import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey
 import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 import java.io.ByteArrayOutputStream
 import java.io.InputStream
+import java.util.UUID
 
 /**
  * Unit tests AttachmentEncryptionTest.
@@ -48,13 +50,18 @@ class AttachmentEncryptionTest {
         inputStream = if (inputAsByteArray.isEmpty()) {
             inputAsByteArray.inputStream()
         } else {
-            val memoryFile = MemoryFile("file" + System.currentTimeMillis(), inputAsByteArray.size)
+            val memoryFile = MemoryFile("file_" + UUID.randomUUID(), inputAsByteArray.size)
             memoryFile.outputStream.write(inputAsByteArray)
             memoryFile.inputStream
         }
 
         val decryptedStream = ByteArrayOutputStream()
-        val result = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo.toElementToDecrypt()!!, decryptedStream)
+        val result = MXEncryptedAttachments.decryptAttachment(
+                attachmentStream = inputStream,
+                elementToDecrypt = encryptedFileInfo.toElementToDecrypt()!!,
+                outputStream = decryptedStream,
+                clock = DefaultClock()
+        )
 
         assert(result)
 
@@ -117,9 +124,13 @@ class AttachmentEncryptionTest {
                 url = "dummyUrl"
         )
 
-        assertEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
-                checkDecryption("zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q",
-                        encryptedFileInfo))
+        assertEquals(
+                "YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
+                checkDecryption(
+                        "zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q",
+                        encryptedFileInfo
+                )
+        )
     }
 
     @Test
@@ -138,8 +149,12 @@ class AttachmentEncryptionTest {
                 url = "dummyUrl"
         )
 
-        assertNotEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
-                checkDecryption("tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA",
-                        encryptedFileInfo))
+        assertNotEquals(
+                "YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
+                checkDecryption(
+                        "tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA",
+                        encryptedFileInfo
+                )
+        )
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
index c717c8e33fe46dc170fff63e6cbba0d7ac5f4db2..ba1afd4758a2fd68cb618943d9acc80fa136f5ff 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
 import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
 import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
 import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 import kotlin.random.Random
 
 internal class CryptoStoreHelper {
@@ -34,7 +35,8 @@ internal class CryptoStoreHelper {
                         .build(),
                 crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()),
                 userId = "userId_" + Random.nextInt(),
-                deviceId = "deviceId_sample"
+                deviceId = "deviceId_sample",
+                clock = DefaultClock(),
         )
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
index f43c425cc9744263434ca676f096c4be6ef8aee7..3f75aa09798a476184cf35bd170a73b318e17b47 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
@@ -27,6 +27,7 @@ import org.junit.runner.RunWith
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmManager
 import org.matrix.olm.OlmSession
@@ -37,6 +38,7 @@ private const val DUMMY_DEVICE_KEY = "DeviceKey"
 class CryptoStoreTest : InstrumentedTest {
 
     private val cryptoStoreHelper = CryptoStoreHelper()
+    private val clock = DefaultClock()
 
     @Before
     fun setup() {
@@ -106,7 +108,7 @@ class CryptoStoreTest : InstrumentedTest {
 
         // Note: we cannot be sure what will be the result of getLastUsedSessionId() here
 
-        olmSessionWrapper2.onMessageReceived()
+        olmSessionWrapper2.onMessageReceived(clock.epochMillis())
         cryptoStore.storeSession(olmSessionWrapper2, DUMMY_DEVICE_KEY)
 
         // sessionId2 is returned now
@@ -114,7 +116,7 @@ class CryptoStoreTest : InstrumentedTest {
 
         Thread.sleep(2)
 
-        olmSessionWrapper1.onMessageReceived()
+        olmSessionWrapper1.onMessageReceived(clock.epochMillis())
         cryptoStore.storeSession(olmSessionWrapper1, DUMMY_DEVICE_KEY)
 
         // sessionId1 is returned now
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 388468315109a9ea2b2350476a92c5a3327c9836..88d99f12e062237540b28fed1cff3ae2e952a176 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
@@ -38,8 +38,11 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.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
 import org.matrix.android.sdk.api.session.room.send.SendState
@@ -86,7 +89,7 @@ class E2eeSanityTests : InstrumentedTest {
         otherAccounts.forEach {
             testHelper.runBlockingTest {
                 Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
-                aliceRoomPOV.invite(it.myUserId)
+                aliceRoomPOV.membershipService().invite(it.myUserId)
             }
         }
 
@@ -128,7 +131,7 @@ class E2eeSanityTests : InstrumentedTest {
         newAccount.forEach {
             testHelper.runBlockingTest {
                 Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
-                aliceRoomPOV.invite(it.myUserId)
+                aliceRoomPOV.membershipService().invite(it.myUserId)
             }
         }
 
@@ -523,10 +526,10 @@ class E2eeSanityTests : InstrumentedTest {
     }
 
     private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
-        aliceRoomPOV.sendTextMessage(text)
+        aliceRoomPOV.sendService().sendTextMessage(text)
         var sentEventId: String? = null
         testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
-            val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
+            val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
             timeline.start()
 
             testHelper.retryPeriodicallyWithLatch(latch) {
@@ -551,7 +554,7 @@ class E2eeSanityTests : InstrumentedTest {
         testHelper.waitWithLatch { latch ->
             testHelper.retryPeriodicallyWithLatch(latch) {
                 otherAccounts.map {
-                    aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership
+                    aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
                 }.all {
                     it == Membership.JOIN
                 }
@@ -574,7 +577,7 @@ class E2eeSanityTests : InstrumentedTest {
         testHelper.runBlockingTest(60_000) {
             Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
             try {
-                otherSession.joinRoom(e2eRoomID)
+                otherSession.roomService().joinRoom(e2eRoomID)
             } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
                 // it's ok we will wait after
             }
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 aa9f09314fdd4a6a13c23dd0731484ff8213173c..8a1edec5e3dcd9448998992fa801b3f952accac2 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
@@ -29,6 +29,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
 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.getTimelineEvent
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestHelper
 
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
index 83464305307c15eab276deed8b1a77960b5e21ac..de4a928dc36f4bedfb079b8cf6c99a05666b1669 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
@@ -97,7 +98,7 @@ class UnwedgingTest : InstrumentedTest {
         val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
         val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
 
-        val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
+        val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(20))
         bobTimeline.start()
 
         val bobFinalLatch = CountDownLatch(1)
@@ -128,7 +129,7 @@ class UnwedgingTest : InstrumentedTest {
         messagesReceivedByBob = emptyList()
 
         // - Alice sends a 1st message with a 1st megolm session
-        roomFromAlicePOV.sendTextMessage("First message")
+        roomFromAlicePOV.sendService().sendTextMessage("First message")
 
         // Wait for the message to be received by Bob
         testHelper.await(latch)
@@ -156,7 +157,7 @@ class UnwedgingTest : InstrumentedTest {
 
         Timber.i("## CRYPTO | testUnwedging:  Alice sends a 2nd message with a 2nd megolm session")
         // - Alice sends a 2nd message with a 2nd megolm session
-        roomFromAlicePOV.sendTextMessage("Second message")
+        roomFromAlicePOV.sendService().sendTextMessage("Second message")
 
         // Wait for the message to be received by Bob
         testHelper.await(latch)
@@ -185,7 +186,7 @@ class UnwedgingTest : InstrumentedTest {
 
             Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session")
             // - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
-            roomFromAlicePOV.sendTextMessage("Third message")
+            roomFromAlicePOV.sendService().sendTextMessage("Third message")
             // Bob should not be able to decrypt, because the session key could not be sent
         }
         bobTimeline.removeListener(bobEventsListener)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
index dc65cec187b088732b259e56b5f188a24bf7c87b..0f3ff7898fd357c3ceada6d94f14cb873274fe7f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
@@ -110,11 +110,13 @@ class XSigningTest : InstrumentedTest {
                 }
             }, it)
         }
-        testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
-            override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
-                promise.resume(bobAuthParams)
-            }
-        }, it) }
+        testHelper.doSync<Unit> {
+            bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                    promise.resume(bobAuthParams)
+                }
+            }, it)
+        }
 
         // Check that alice can see bob keys
         testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
@@ -149,16 +151,20 @@ class XSigningTest : InstrumentedTest {
                 password = TestConstants.PASSWORD
         )
 
-        testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
-            override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
-                promise.resume(aliceAuthParams)
-            }
-        }, it) }
-        testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
-            override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
-                promise.resume(bobAuthParams)
-            }
-        }, it) }
+        testHelper.doSync<Unit> {
+            aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                    promise.resume(aliceAuthParams)
+                }
+            }, it)
+        }
+        testHelper.doSync<Unit> {
+            bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                    promise.resume(bobAuthParams)
+                }
+            }, it)
+        }
 
         // Check that alice can see bob keys
         val bobUserId = bobSession.myUserId
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
index 84c9487e030a27eeeeae5d325ea3b8a984d067bb..85b6c21df37ea2744e84c86b29cbe9a8c89f036d 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
 import org.matrix.android.sdk.api.session.events.model.toContent
+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.send.SendState
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
@@ -48,7 +49,7 @@ class EncryptionTest : InstrumentedTest {
     fun test_EncryptionEvent() {
         performTest(roomShouldBeEncrypted = false) { room ->
             // Send an encryption Event as an Event (and not as a state event)
-            room.sendEvent(
+            room.sendService().sendEvent(
                     eventType = EventType.STATE_ROOM_ENCRYPTION,
                     content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
             )
@@ -60,7 +61,7 @@ class EncryptionTest : InstrumentedTest {
         performTest(roomShouldBeEncrypted = true) { room ->
             runBlocking {
                 // Send an encryption Event as a State Event
-                room.sendStateEvent(
+                room.stateService().sendStateEvent(
                         eventType = EventType.STATE_ROOM_ENCRYPTION,
                         stateKey = "",
                         body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
@@ -75,9 +76,9 @@ class EncryptionTest : InstrumentedTest {
         val aliceSession = cryptoTestData.firstSession
         val room = aliceSession.getRoom(cryptoTestData.roomId)!!
 
-        room.isEncrypted() shouldBe false
+        room.roomCryptoService().isEncrypted() shouldBe false
 
-        val timeline = room.createTimeline(null, TimelineSettings(10))
+        val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
         val latch = CountDownLatch(1)
         val timelineListener = object : Timeline.Listener {
             override fun onTimelineFailure(throwable: Throwable) {
@@ -105,7 +106,7 @@ class EncryptionTest : InstrumentedTest {
         testHelper.await(latch)
         timeline.dispose()
         testHelper.waitWithLatch {
-            room.isEncrypted() shouldBe roomShouldBeEncrypted
+            room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted
             it.countDown()
         }
         cryptoTestData.cleanUp(testHelper)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index 5e271c6910180bc4a390d9797034499d0e9bd0f5..592d24fb690650338a44e8e166cb93aee72a29e0 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -50,6 +50,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 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.getRoom
+import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@@ -73,7 +75,7 @@ class KeyShareTests : InstrumentedTest {
 
         // Create an encrypted room and add a message
         val roomId = commonTestHelper.runBlockingTest {
-            aliceSession.createRoom(
+            aliceSession.roomService().createRoom(
                     CreateRoomParams().apply {
                         visibility = RoomDirectoryVisibility.PRIVATE
                         enableEncryption()
@@ -83,7 +85,7 @@ class KeyShareTests : InstrumentedTest {
         val room = aliceSession.getRoom(roomId)
         assertNotNull(room)
         Thread.sleep(4_000)
-        assertTrue(room?.isEncrypted() == true)
+        assertTrue(room?.roomCryptoService()?.isEncrypted() == true)
         val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
 
         // Open a new sessionx
@@ -340,7 +342,7 @@ class KeyShareTests : InstrumentedTest {
 
         // Create an encrypted room and send a couple of messages
         val roomId = commonTestHelper.runBlockingTest {
-            aliceSession.createRoom(
+            aliceSession.roomService().createRoom(
                     CreateRoomParams().apply {
                         visibility = RoomDirectoryVisibility.PRIVATE
                         enableEncryption()
@@ -350,7 +352,7 @@ class KeyShareTests : InstrumentedTest {
         val roomAlicePov = aliceSession.getRoom(roomId)
         assertNotNull(roomAlicePov)
         Thread.sleep(1_000)
-        assertTrue(roomAlicePov?.isEncrypted() == true)
+        assertTrue(roomAlicePov?.roomCryptoService()?.isEncrypted() == true)
         val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
 
         // Create bob session
@@ -374,11 +376,11 @@ class KeyShareTests : InstrumentedTest {
 
         // Let alice invite bob
         commonTestHelper.runBlockingTest {
-            roomAlicePov.invite(bobSession.myUserId, null)
+            roomAlicePov.membershipService().invite(bobSession.myUserId, null)
         }
 
         commonTestHelper.runBlockingTest {
-            bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
+            bobSession.roomService().joinRoom(roomAlicePov.roomId, null, emptyList())
         }
 
         // we want to discard alice outbound session
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
index 55bb03278c77b8bf8de0b11343f6761a7682c36c..bad9fd0f68775a6618767cff21546cf059e96f73 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
@@ -33,6 +33,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestHelper
 import org.matrix.android.sdk.common.MockOkHttpInterceptor
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 063c0c0ca397310a442a9eefb0d93a23893ae6a8..3220f161fa499a131b968baff01631372cdf11c0 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
@@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
 import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
 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.session.getRoom
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestHelper
 import org.matrix.android.sdk.common.TestConstants
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
index d6baa4b1e0d42430ec79bb91c295b6284bf4d576..a882f690132c3da214982a50d10f8150e7f79675 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
@@ -60,7 +60,7 @@ class QuadSTests : InstrumentedTest {
 
         val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
-        val quadS = aliceSession.sharedSecretStorageService
+        val quadS = aliceSession.sharedSecretStorageService()
 
         val TEST_KEY_ID = "my.test.Key"
 
@@ -120,7 +120,7 @@ class QuadSTests : InstrumentedTest {
         // Store a secret
         val clearSecret = "42".toByteArray().toBase64NoPadding()
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.storeSecret(
+            aliceSession.sharedSecretStorageService().storeSecret(
                     "secret.of.life",
                     clearSecret,
                     listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key
@@ -141,7 +141,7 @@ class QuadSTests : InstrumentedTest {
         // Try to decrypt??
 
         val decryptedSecret = testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret(
+            aliceSession.sharedSecretStorageService().getSecret(
                     "secret.of.life",
                     null, // default key
                     keySpec!!
@@ -158,7 +158,7 @@ class QuadSTests : InstrumentedTest {
 
         val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
-        val quadS = aliceSession.sharedSecretStorageService
+        val quadS = aliceSession.sharedSecretStorageService()
 
         val TEST_KEY_ID = "my.test.Key"
 
@@ -187,7 +187,7 @@ class QuadSTests : InstrumentedTest {
         val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
 
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.storeSecret(
+            aliceSession.sharedSecretStorageService().storeSecret(
                     "my.secret",
                     mySecretText.toByteArray().toBase64NoPadding(),
                     listOf(
@@ -207,14 +207,14 @@ class QuadSTests : InstrumentedTest {
 
         // Assert that can decrypt with both keys
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret("my.secret",
+            aliceSession.sharedSecretStorageService().getSecret("my.secret",
                     keyId1,
                     RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
             )
         }
 
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret("my.secret",
+            aliceSession.sharedSecretStorageService().getSecret("my.secret",
                     keyId2,
                     RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
             )
@@ -236,7 +236,7 @@ class QuadSTests : InstrumentedTest {
         val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
 
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.storeSecret(
+            aliceSession.sharedSecretStorageService().storeSecret(
                     "my.secret",
                     mySecretText.toByteArray().toBase64NoPadding(),
                     listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)))
@@ -245,7 +245,7 @@ class QuadSTests : InstrumentedTest {
 
         testHelper.runBlockingTest {
             try {
-                aliceSession.sharedSecretStorageService.getSecret("my.secret",
+                aliceSession.sharedSecretStorageService().getSecret("my.secret",
                         keyId1,
                         RawBytesKeySpec.fromPassphrase(
                                 "A bad passphrase",
@@ -260,7 +260,7 @@ class QuadSTests : InstrumentedTest {
 
         // Now try with correct key
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret("my.secret",
+            aliceSession.sharedSecretStorageService().getSecret("my.secret",
                     keyId1,
                     RawBytesKeySpec.fromPassphrase(
                             passphrase,
@@ -292,7 +292,7 @@ class QuadSTests : InstrumentedTest {
     }
 
     private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
-        val quadS = session.sharedSecretStorageService
+        val quadS = session.sharedSecretStorageService()
         val testHelper = CommonTestHelper(context())
 
         val creationInfo = testHelper.runBlockingTest {
@@ -312,7 +312,7 @@ class QuadSTests : InstrumentedTest {
     }
 
     private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
-        val quadS = session.sharedSecretStorageService
+        val quadS = session.sharedSecretStorageService()
         val testHelper = CommonTestHelper(context())
 
         val creationInfo = testHelper.runBlockingTest {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
index 2c96568102904016eafb7439c897c9cf87011eee..374d709505311572790f0019f01d2b8dddadde79 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
@@ -155,8 +155,8 @@ class VerificationTest : InstrumentedTest {
                        bobSupportedMethods: List<VerificationMethod>,
                        expectedResultForAlice: ExpectedResult,
                        expectedResultForBob: ExpectedResult) {
-         val testHelper = CommonTestHelper(context())
-         val cryptoTestHelper = CryptoTestHelper(testHelper)
+        val testHelper = CommonTestHelper(context())
+        val cryptoTestHelper = CryptoTestHelper(testHelper)
         val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
index dcb181f0c1e498fbec597b0c7f0e51346234dcbb..f6e08a576e20b3fcf29a88f2689ec05d6bd635cc 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
@@ -31,6 +31,7 @@ import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
 import org.matrix.android.sdk.api.session.events.model.isTextMessage
 import org.matrix.android.sdk.api.session.events.model.isThread
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.common.CommonTestHelper
@@ -80,7 +81,7 @@ class ThreadMessagingTest : InstrumentedTest {
         replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
 
         // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
         timeline.start()
 
         aliceSession.startSync(true)
@@ -141,7 +142,7 @@ class ThreadMessagingTest : InstrumentedTest {
         replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
 
         // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
         timeline.start()
 
         aliceSession.startSync(true)
@@ -214,7 +215,7 @@ class ThreadMessagingTest : InstrumentedTest {
         }
 
         // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
         timeline.start()
 
         aliceSession.startSync(true)
@@ -309,7 +310,7 @@ class ThreadMessagingTest : InstrumentedTest {
         }
 
         // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
         timeline.start()
 
         aliceSession.startSync(true)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
index 5c011c8b2fb195711e3afe55bc2f31b389912573..27d3fdc85615fe8e106ac69868560b04322ab088 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.database.model.ChunkEntity
 import org.matrix.android.sdk.internal.database.model.SessionRealmModule
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeListOfEvents
 import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent
 
@@ -42,6 +43,7 @@ import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMes
 internal class ChunkEntityTest : InstrumentedTest {
 
     private lateinit var monarchy: Monarchy
+    private val clock = DefaultClock()
 
     @Before
     fun setup() {
@@ -59,7 +61,7 @@ internal class ChunkEntityTest : InstrumentedTest {
         monarchy.runTransactionSync { realm ->
             val chunk: ChunkEntity = realm.createObject()
 
-            val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
+            val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, clock.epochMillis()).let {
                 realm.copyToRealm(it)
             }
             chunk.addTimelineEvent(
@@ -75,7 +77,7 @@ internal class ChunkEntityTest : InstrumentedTest {
     fun add_shouldNotAdd_whenAlreadyIncluded() {
         monarchy.runTransactionSync { realm ->
             val chunk: ChunkEntity = realm.createObject()
-            val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
+            val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, clock.epochMillis()).let {
                 realm.copyToRealm(it)
             }
             chunk.addTimelineEvent(
@@ -153,7 +155,7 @@ internal class ChunkEntityTest : InstrumentedTest {
                                    events: List<Event>,
                                    direction: PaginationDirection) {
         events.forEach { event ->
-            val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
+            val fakeEvent = event.toEntity(roomId, SendState.SYNCED, clock.epochMillis()).let {
                 realm.copyToRealm(it)
             }
             addTimelineEvent(
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
index b86c86c0c7179c92f0498712dbfd83490e046128..ccf1c7c2c9410fd5559d6de54ab51d69361a9489 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
@@ -26,8 +26,8 @@ internal class FakeGetContextOfEventTask constructor(private val tokenChunkEvent
     override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
         val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
         val tokenChunkEvent = FakeTokenChunkEvent(
-                Random.nextLong(System.currentTimeMillis()).toString(),
-                Random.nextLong(System.currentTimeMillis()).toString(),
+                Random.nextLong().toString(),
+                Random.nextLong().toString(),
                 fakeEvents
         )
         return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
index d09bfb18c6c6949e03d637577d43a548652dfea4..f241be0c5c90054fdfbd33ef3b8e12ed28250783 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
@@ -25,7 +25,7 @@ internal class FakePaginationTask @Inject constructor(private val tokenChunkEven
 
     override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
         val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
-        val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
+        val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong().toString(), fakeEvents)
         return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt
index 6792d6ddfd039024870c2434b36cd2efb32b3399..61ab6d4b40f301673ac6c32c804f6875ff6e8513 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt
@@ -30,6 +30,7 @@ import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 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.getRoom
 import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary
 import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
@@ -57,10 +58,10 @@ class PollAggregationTest : InstrumentedTest {
 
         val roomFromBobPOV = cryptoTestData.secondSession!!.getRoom(cryptoTestData.roomId)!!
         // Bob creates a poll
-        roomFromBobPOV.sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions)
+        roomFromBobPOV.sendService().sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions)
 
         aliceSession.startSync(true)
-        val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
+        val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30))
         aliceTimeline.start()
 
         val TOTAL_TEST_COUNT = 7
@@ -83,37 +84,37 @@ class PollAggregationTest : InstrumentedTest {
                             // Poll has just been created.
                             testInitialPollConditions(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
+                            roomFromBobPOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 1 -> {
                             // Bob: Option 1
                             testBobVotesOption1(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
+                            roomFromBobPOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 2 -> {
                             // Bob: Option 2
                             testBobChangesVoteToOption2(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
+                            roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 3 -> {
                             // Alice: Option 2, Bob: Option 2
                             testAliceAndBobVoteToOption2(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
+                            roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 4 -> {
                             // Alice: Option 1, Bob: Option 2
                             testAliceVotesOption1AndBobVotesOption2(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromBobPOV.endPoll(pollEventId)
+                            roomFromBobPOV.sendService().endPoll(pollEventId)
                         }
                         TOTAL_TEST_COUNT - 5 -> {
                             // Alice: Option 1, Bob: Option 2 [poll is ended]
                             testEndedPoll(pollSummary)
                             lock.countDown()
-                            roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
+                            roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 6 -> {
                             // Alice: Option 1 (ignore change), Bob: Option 2 [poll is ended]
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
index ee44af58b305549f000b3cba5d6c7a96bb5a0c24..3864ea1cd1b28941f485677c719e2deff006a6af 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
 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.getRoom
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
@@ -74,7 +75,7 @@ class TimelineForwardPaginationTest : InstrumentedTest {
 
         // Alice clear the cache and restart the sync
         commonTestHelper.clearCacheAndSync(aliceSession)
-        val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
+        val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30))
         aliceTimeline.start()
 
         // Alice sees the 10 last message of the room, and can only navigate BACKWARD
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
index c6d40bcaa26d1effe5c3415b96aed9b6b1a31b4b..5d09b74e6c3f8773ef10ffea907996418dfd8524 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
 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.getRoom
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
@@ -62,7 +63,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
         val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
         val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
 
-        val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
+        val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
         bobTimeline.start()
 
         run {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
index 53f76f1c46c2181b5eba816f62f10dd22b81c18b..251b2c614cd7302c1738fb7ba7ee86f0506b5df8 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.events.model.isTextMessage
 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.model.message.MessageTextContent
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
@@ -65,7 +66,7 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest {
                 message,
                 numberOfMessagesToSent)
 
-        val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
+        val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
         bobTimeline.start()
 
         commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
index ce02b2b527e89898b926e54ec7b16b81c219849c..02430dda745a541292cdcb9f079c2b328504846c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
@@ -27,6 +27,7 @@ import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
 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.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.common.CommonTestHelper
@@ -71,7 +72,7 @@ class TimelineWithManyMembersTest : InstrumentedTest {
         for (index in 1 until cryptoTestData.sessions.size) {
             val session = cryptoTestData.sessions[index]
             val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!!
-            val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30))
+            val timelineForCurrentMember = roomForCurrentMember.timelineService().createTimeline(null, TimelineSettings(30))
             timelineForCurrentMember.start()
 
             session.startSync(true)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
index fa07cf5a02c07587f1d70725b8034c6a97c7d410..ab0bbe7f73f9450fc913577e3f3c1c417bbb93de 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
@@ -24,6 +24,7 @@ import org.junit.runners.JUnit4
 import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestData
@@ -59,9 +60,10 @@ class SearchMessagesTest : InstrumentedTest {
     fun sendTextMessageAndSearchPartOfItUsingRoom() {
         doTest { cryptoTestData ->
             cryptoTestData.firstSession
-                    .getRoom(cryptoTestData.roomId)!!
+                    .searchService()
                     .search(
                             searchTerm = "lore",
+                            roomId = cryptoTestData.roomId,
                             limit = 10,
                             includeProfile = true,
                             afterLimit = 0,
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 3b0f7586ccfd571d97c2c0c8887495f44420480e..b9760c1bfc73f57903a33e04900ae8ddac92d080 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
@@ -29,6 +29,7 @@ import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
@@ -147,7 +148,7 @@ class SpaceCreationTest : InstrumentedTest {
         // create a room
         var firstChild: String? = null
         commonTestHelper.waitWithLatch {
-            firstChild = aliceSession.createRoom(CreateRoomParams().apply {
+            firstChild = aliceSession.roomService().createRoom(CreateRoomParams().apply {
                 this.name = "FirstRoom"
                 this.topic = "Description of first room"
                 this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
@@ -162,7 +163,7 @@ class SpaceCreationTest : InstrumentedTest {
 
         var secondChild: String? = null
         commonTestHelper.waitWithLatch {
-            secondChild = aliceSession.createRoom(CreateRoomParams().apply {
+            secondChild = aliceSession.roomService().createRoom(CreateRoomParams().apply {
                 this.name = "SecondRoom"
                 this.topic = "Description of second room"
                 this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
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 50e4a6feb6d7c1af67c28122af82cce790165d73..d2c8b52fc7693ac08e1eab3c4c7e9b953a2395ab 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
@@ -35,6 +35,9 @@ 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.getRoomSummary
+import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -68,7 +71,7 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         var roomId = ""
         commonTestHelper.waitWithLatch {
-            roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
+            roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" })
             it.countDown()
         }
 
@@ -203,27 +206,27 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         var orphan1 = ""
         commonTestHelper.waitWithLatch {
-            orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
+            orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" })
             it.countDown()
         }
 
         var orphan2 = ""
         commonTestHelper.waitWithLatch {
-            orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" })
+            orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" })
             it.countDown()
         }
 
-        val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
+        val allRooms = session.roomService().getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
 
         assertEquals("Unexpected number of rooms", 9, allRooms.size)
 
-        val orphans = session.getFlattenRoomSummaryChildrenOf(null)
+        val orphans = session.roomService().getFlattenRoomSummaryChildrenOf(null)
 
         assertEquals("Unexpected number of orphan rooms", 2, orphans.size)
         assertTrue("O1 should be an orphan", orphans.any { it.roomId == orphan1 })
         assertTrue("O2 should be an orphan ${orphans.map { it.name }}", orphans.any { it.roomId == orphan2 })
 
-        val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
+        val aChildren = session.roomService().getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
 
         assertEquals("Unexpected number of flatten child rooms", 4, aChildren.size)
         assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" })
@@ -233,13 +236,13 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         // Add a non canonical child and check that it does not appear as orphan
         commonTestHelper.waitWithLatch {
-            val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
+            val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" })
             spaceA!!.addChildren(a3, viaServers, null, false)
             it.countDown()
         }
 
         Thread.sleep(6_000)
-        val orphansUpdate = session.getRoomSummaries(roomSummaryQueryParams {
+        val orphansUpdate = session.roomService().getRoomSummaries(roomSummaryQueryParams {
             activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
         })
         assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size)
@@ -279,7 +282,7 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         // A -> C -> A
 
-        val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
+        val aChildren = session.roomService().getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
 
         assertEquals("Unexpected number of flatten child rooms ${aChildren.map { it.name }}", 4, aChildren.size)
         assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" })
@@ -319,7 +322,7 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         commonTestHelper.waitWithLatch { latch ->
 
-            val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
+            val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
             val childObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(children: List<RoomSummary>?) {
 //                    Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}")
@@ -346,7 +349,7 @@ class SpaceHierarchyTest : InstrumentedTest {
         val bRoomId = spaceBInfo.roomIds.first()
 
         commonTestHelper.waitWithLatch { latch ->
-            val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
+            val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
             val childObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(children: List<RoomSummary>?) {
                     System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
@@ -359,7 +362,7 @@ class SpaceHierarchyTest : InstrumentedTest {
             }
 
             // part from b room
-            session.leaveRoom(bRoomId)
+            session.roomService().leaveRoom(bRoomId)
             // The room should have disapear from flat children
             flatAChildren.observeForever(childObserver)
         }
@@ -385,7 +388,7 @@ class SpaceHierarchyTest : InstrumentedTest {
             val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
 
             roomIds = childInfo.map { entry ->
-                session.createRoom(CreateRoomParams().apply { name = entry.first })
+                session.roomService().createRoom(CreateRoomParams().apply { name = entry.first })
             }
             roomIds.forEachIndexed { index, roomId ->
                 syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
@@ -414,8 +417,9 @@ class SpaceHierarchyTest : InstrumentedTest {
             roomIds =
                     childInfo.map { entry ->
                         val homeServerCapabilities = session
+                                .homeServerCapabilitiesService()
                                 .getHomeServerCapabilities()
-                        session.createRoom(CreateRoomParams().apply {
+                        session.roomService().createRoom(CreateRoomParams().apply {
                             name = entry.first
                             this.featurePreset = RestrictedRoomPreset(
                                     homeServerCapabilities,
@@ -496,22 +500,22 @@ class SpaceHierarchyTest : InstrumentedTest {
         ))
 
         commonTestHelper.runBlockingTest {
-            aliceSession.getRoom(spaceAInfo.spaceId)!!.invite(bobSession.myUserId, null)
+            aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null)
         }
 
         commonTestHelper.runBlockingTest {
-            bobSession.joinRoom(spaceAInfo.spaceId, null, emptyList())
+            bobSession.roomService().joinRoom(spaceAInfo.spaceId, null, emptyList())
         }
 
         var bobRoomId = ""
         commonTestHelper.waitWithLatch {
-            bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
-            bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
+            bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" })
+            bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId)
             it.countDown()
         }
 
         commonTestHelper.runBlockingTest {
-            aliceSession.joinRoom(bobRoomId)
+            aliceSession.roomService().joinRoom(bobRoomId)
         }
 
         commonTestHelper.waitWithLatch { latch ->
@@ -551,7 +555,7 @@ class SpaceHierarchyTest : InstrumentedTest {
                     ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
                     ?.toContent()
 
-            room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
+            room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
             it.countDown()
         }
 
diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
index 3add757efadf3b3d0577b7493b4377e679f8a80c..6dd3553d02c06d3867bfa465e4d980a923a6d7cd 100644
--- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
+++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
@@ -37,7 +37,7 @@ import javax.inject.Inject
  */
 @MatrixScope
 internal class CurlLoggingInterceptor @Inject constructor() :
-    Interceptor {
+        Interceptor {
 
     /**
      * Set any additional curl command options (see 'curl --help').
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index e7b4b766adefd743132b539abc8eeb8165461890..217f7e3da81361100540b2baa43f25eb16b2b13f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -43,7 +43,8 @@ import javax.inject.Inject
 
 /**
  * This is the main entry point to the matrix sdk.
- * To get the singleton instance, use getInstance static method.
+ * <br/>
+ * See [Companion.createInstance] to create an instance. The app should create and manage the instance itself.
  */
 class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
 
@@ -74,9 +75,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
 
     fun getUserAgent() = userAgentHolder.userAgent
 
-    fun authenticationService(): AuthenticationService {
-        return authenticationService
-    }
+    fun authenticationService() = authenticationService
 
     fun rawService() = rawService
 
@@ -84,9 +83,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
 
     fun homeServerHistoryService() = homeServerHistoryService
 
-    fun legacySessionImporter(): LegacySessionImporter {
-        return legacySessionImporter
-    }
+    fun legacySessionImporter() = legacySessionImporter
 
     fun workerFactory(): WorkerFactory = matrixWorkerFactory
 
@@ -143,6 +140,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
             return instance
         }
 
+        /**
+         * @return a String with details about the Matrix SDK version
+         */
         fun getSdkVersion(): String {
             return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ca0123b8328155d66a921ee4848e8cdb085c5452
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.auth
+
+data class UrlAndName(
+        val url: String,
+        val name: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1a4c1ee51ce4fc0cb8a721c0752cc165eba2c2c5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.auth
+
+import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms
+import org.matrix.android.sdk.api.auth.registration.TermPolicies
+
+/**
+ * This method extract the policies from the login terms parameter, regarding the user language.
+ * For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable)
+ *
+ * Example of Data:
+ * <pre>
+ * "m.login.terms": {
+ *       "policies": {
+ *         "privacy_policy": {
+ *           "version": "1.0",
+ *           "en": {
+ *             "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
+ *             "name": "Terms and Conditions"
+ *           }
+ *         }
+ *       }
+ *     }
+ *</pre>
+ *
+ * @param userLanguage the user language
+ * @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse
+ */
+fun TermPolicies.toLocalizedLoginTerms(userLanguage: String,
+                                       defaultLanguage: String = "en"): List<LocalizedFlowDataLoginTerms> {
+    val result = ArrayList<LocalizedFlowDataLoginTerms>()
+
+    val policies = get("policies")
+    if (policies is Map<*, *>) {
+        policies.keys.forEach { policyName ->
+            val localizedFlowDataLoginTermsPolicyName = policyName as String
+            var localizedFlowDataLoginTermsVersion: String? = null
+            var localizedFlowDataLoginTermsLocalizedUrl: String? = null
+            var localizedFlowDataLoginTermsLocalizedName: String? = null
+
+            val policy = policies[policyName]
+
+            // Enter this policy
+            if (policy is Map<*, *>) {
+                // Version
+                localizedFlowDataLoginTermsVersion = policy["version"] as String?
+
+                var userLanguageUrlAndName: UrlAndName? = null
+                var defaultLanguageUrlAndName: UrlAndName? = null
+                var firstUrlAndName: UrlAndName? = null
+
+                // Search for language
+                policy.keys.forEach { policyKey ->
+                    when (policyKey) {
+                        "version"       -> Unit // Ignore
+                        userLanguage    -> {
+                            // We found the data for the user language
+                            userLanguageUrlAndName = extractUrlAndName(policy[policyKey])
+                        }
+                        defaultLanguage -> {
+                            // We found default language
+                            defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey])
+                        }
+                        else            -> {
+                            if (firstUrlAndName == null) {
+                                // Get at least some data
+                                firstUrlAndName = extractUrlAndName(policy[policyKey])
+                            }
+                        }
+                    }
+                }
+
+                // Copy found language data by priority
+                when {
+                    userLanguageUrlAndName != null    -> {
+                        localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url
+                        localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name
+                    }
+                    defaultLanguageUrlAndName != null -> {
+                        localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url
+                        localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name
+                    }
+                    firstUrlAndName != null           -> {
+                        localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url
+                        localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name
+                    }
+                }
+            }
+
+            result.add(LocalizedFlowDataLoginTerms(
+                    policyName = localizedFlowDataLoginTermsPolicyName,
+                    version = localizedFlowDataLoginTermsVersion,
+                    localizedUrl = localizedFlowDataLoginTermsLocalizedUrl,
+                    localizedName = localizedFlowDataLoginTermsLocalizedName
+            ))
+        }
+    }
+
+    return result
+}
+
+private fun extractUrlAndName(policyData: Any?): UrlAndName? {
+    if (policyData is Map<*, *>) {
+        val url = policyData["url"] as String?
+        val name = policyData["name"] as String?
+
+        if (url != null && name != null) {
+            return UrlAndName(url, name)
+        }
+    }
+    return null
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
index 621253faa5c7d31692024c32602591777f8edd0b..0cda64499f126def2d5d006c0b1b1bca80661199 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
@@ -16,6 +16,8 @@
 
 package org.matrix.android.sdk.api.auth.registration
 
+import org.matrix.android.sdk.api.util.JsonDict
+
 /**
  * Set of methods to be able to create an account on a homeserver.
  *
@@ -73,6 +75,13 @@ interface RegistrationWizard {
      */
     suspend fun dummy(): RegistrationResult
 
+    /**
+     * Perform custom registration stage by sending a custom JsonDict.
+     * Current registration "session" param will be included into authParams by default.
+     * The authParams should contain at least one entry "type" with a String value.
+     */
+    suspend fun registrationCustom(authParams: JsonDict): RegistrationResult
+
     /**
      * Perform the "m.login.email.identity" or "m.login.msisdn" stage.
      *
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
index b5165b6687d403512b43e1eb36c109be3ff62d44..5b4896f95f2f170b1f2523a6cfda2de39727f03a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
@@ -23,5 +23,10 @@ sealed class GlobalError {
     data class InvalidToken(val softLogout: Boolean) : GlobalError()
     data class ConsentNotGivenError(val consentUri: String) : GlobalError()
     data class CertificateError(val fingerprint: Fingerprint) : GlobalError()
+
+    /**
+     * The SDK requires the app (which should request the user) to perform an initial sync.
+     */
+    data class InitialSyncRequest(val reason: InitialSyncRequestReason) : GlobalError()
     object ExpiredAccount : GlobalError()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ebe07823f470f3fbdca55586dcb49e2787742b06
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.failure
+
+/**
+ * This enum provide the reason why the SDK request an initial sync to the application
+ */
+enum class InitialSyncRequestReason {
+    /**
+     * The list of ignored users has changed, and at least one user who was ignored is not ignored anymore
+     */
+    IGNORED_USERS_LIST_CHANGE,
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
index be924e206367edac1242842571ab0c8652a76661..19502f0b46b873a1d658aad6986bbb53f9cce91c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
@@ -24,10 +24,8 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.api.federation.FederationService
-import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.account.AccountService
 import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
-import org.matrix.android.sdk.api.session.cache.CacheService
 import org.matrix.android.sdk.api.session.call.CallSignalingService
 import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
@@ -47,6 +45,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService
 import org.matrix.android.sdk.api.session.presence.PresenceService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.pushers.PushersService
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
 import org.matrix.android.sdk.api.session.room.RoomService
 import org.matrix.android.sdk.api.session.search.SearchService
@@ -68,26 +67,7 @@ import org.matrix.android.sdk.api.session.widgets.WidgetService
  * This interface defines interactions with a session.
  * An instance of a session will be provided by the SDK.
  */
-interface Session :
-        RoomService,
-        RoomDirectoryService,
-        GroupService,
-        UserService,
-        CacheService,
-        SignOutService,
-        FilterService,
-        TermsService,
-        EventService,
-        ProfileService,
-        PresenceService,
-        PushRuleService,
-        PushersService,
-        SyncStatusService,
-        HomeServerCapabilitiesService,
-        SecureStorageService,
-        AccountService,
-        ToDeviceService,
-        EventStreamService {
+interface Session {
 
     val coroutineDispatchers: MatrixCoroutineDispatchers
 
@@ -144,6 +124,11 @@ interface Session :
      */
     fun stopSync()
 
+    /**
+     * Clear cache of the session
+     */
+    suspend fun clearCache()
+
     /**
      * This method allows to listen the sync state.
      * @return a [LiveData] of [SyncState].
@@ -206,6 +191,96 @@ interface Session :
      */
     fun identityService(): IdentityService
 
+    /**
+     * Returns the HomeServerCapabilities service associated with the session
+     */
+    fun homeServerCapabilitiesService(): HomeServerCapabilitiesService
+
+    /**
+     * Returns the RoomService associated with the session
+     */
+    fun roomService(): RoomService
+
+    /**
+     * Returns the RoomDirectoryService associated with the session
+     */
+    fun roomDirectoryService(): RoomDirectoryService
+
+    /**
+     * Returns the GroupService associated with the session
+     */
+    fun groupService(): GroupService
+
+    /**
+     * Returns the UserService associated with the session
+     */
+    fun userService(): UserService
+
+    /**
+     * Returns the SignOutService associated with the session
+     */
+    fun signOutService(): SignOutService
+
+    /**
+     * Returns the FilterService associated with the session
+     */
+    fun filterService(): FilterService
+
+    /**
+     * Returns the PushRuleService associated with the session
+     */
+    fun pushRuleService(): PushRuleService
+
+    /**
+     * Returns the PushersService associated with the session
+     */
+    fun pushersService(): PushersService
+
+    /**
+     * Returns the EventService associated with the session
+     */
+    fun eventService(): EventService
+
+    /**
+     * Returns the TermsService associated with the session
+     */
+    fun termsService(): TermsService
+
+    /**
+     * Returns the SyncStatusService associated with the session
+     */
+    fun syncStatusService(): SyncStatusService
+
+    /**
+     * Returns the SecureStorageService associated with the session
+     */
+    fun secureStorageService(): SecureStorageService
+
+    /**
+     * Returns the ProfileService associated with the session
+     */
+    fun profileService(): ProfileService
+
+    /**
+     * Returns the PresenceService associated with the session
+     */
+    fun presenceService(): PresenceService
+
+    /**
+     * Returns the AccountService associated with the session
+     */
+    fun accountService(): AccountService
+
+    /**
+     * Returns the ToDeviceService associated with the session
+     */
+    fun toDeviceService(): ToDeviceService
+
+    /**
+     * Returns the EventStreamService associated with the session
+     */
+    fun eventStreamService(): EventStreamService
+
     /**
      * Returns the widget service associated with the session
      */
@@ -266,6 +341,11 @@ interface Session :
      */
     fun accountDataService(): SessionAccountDataService
 
+    /**
+     * Returns the SharedSecretStorageService associated with the session
+     */
+    fun sharedSecretStorageService(): SharedSecretStorageService
+
     /**
      * Add a listener to the session.
      * @param listener the listener to add.
@@ -298,12 +378,11 @@ interface Session :
          * Possible cases:
          * - The access token is not valid anymore,
          * - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
+         * See [GlobalError] for all the possible cases
          */
         fun onGlobalError(session: Session, globalError: GlobalError) = Unit
     }
 
-    val sharedSecretStorageService: SharedSecretStorageService
-
     fun getUiaSsoFallbackUrl(authenticationSessionId: String): String
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..aeb0e7e4ee73f5dee19e8e7cccb1ad16b1b4aa9a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session
+
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.user.model.User
+
+/**
+ * Get a room using the RoomService of a Session
+ */
+fun Session.getRoom(roomId: String): Room? = roomService().getRoom(roomId)
+
+/**
+ * Get a room summary using the RoomService of a Session
+ */
+fun Session.getRoomSummary(roomIdOrAlias: String): RoomSummary? = roomService().getRoomSummary(roomIdOrAlias)
+
+/**
+ * Get a user using the UserService of a Session
+ */
+fun Session.getUser(userId: String): User? = userService().getUser(userId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt
index 74ca7304f7348386e65b4040b938a773852d1b5e..ad11ef9a5e465b2cfc04a92af2ecd43511404dbf 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt
@@ -46,8 +46,9 @@ data class IncomingRequestCancellation(
          * Factory
          *
          * @param event the event
+         * @param currentTimeMillis the current time in milliseconds
          */
-        fun fromEvent(event: Event): IncomingRequestCancellation? {
+        fun fromEvent(event: Event, currentTimeMillis: Long): IncomingRequestCancellation? {
             return event.getClearContent()
                     .toModel<ShareRequestCancellation>()
                     ?.let {
@@ -55,7 +56,7 @@ data class IncomingRequestCancellation(
                                 userId = event.senderId,
                                 deviceId = it.requestingDeviceId,
                                 requestId = it.requestId,
-                                localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
+                                localCreationTimestamp = event.ageLocalTs ?: currentTimeMillis
                         )
                     }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
index 45b0926d8912cca217193efffc8d13f880d13fae..0b2c32284b05edd79316b5de883b5b5bb8e4d232 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
@@ -64,8 +64,9 @@ data class IncomingRoomKeyRequest(
          * Factory
          *
          * @param event the event
+         * @param currentTimeMillis the current time in milliseconds
          */
-        fun fromEvent(event: Event): IncomingRoomKeyRequest? {
+        fun fromEvent(event: Event, currentTimeMillis: Long): IncomingRoomKeyRequest? {
             return event.getClearContent()
                     .toModel<RoomKeyShareRequest>()
                     ?.let {
@@ -74,7 +75,7 @@ data class IncomingRoomKeyRequest(
                                 deviceId = it.requestingDeviceId,
                                 requestId = it.requestId,
                                 requestBody = it.body ?: RoomKeyRequestBody(),
-                                localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
+                                localCreationTimestamp = event.ageLocalTs ?: currentTimeMillis
                         )
                     }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt
index 5afffef1aed4d2dafd4f6cb97c2ad2f931460468..80f70c83f3a6e7671bc53cc463b95b448ebe7d4e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt
@@ -64,8 +64,9 @@ data class IncomingSecretShareRequest(
          * Factory
          *
          * @param event the event
+         * @param currentTimeMillis the current time in milliseconds
          */
-        fun fromEvent(event: Event): IncomingSecretShareRequest? {
+        fun fromEvent(event: Event, currentTimeMillis: Long): IncomingSecretShareRequest? {
             return event.getClearContent()
                     .toModel<SecretShareRequest>()
                     ?.let {
@@ -74,7 +75,7 @@ data class IncomingSecretShareRequest(
                                 deviceId = it.requestingDeviceId,
                                 requestId = it.requestId,
                                 secretName = it.secretName,
-                                localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
+                                localCreationTimestamp = event.ageLocalTs ?: currentTimeMillis
                         )
                     }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
index b9d0c0ad2cec8fbaa65be1128496b74ab6859bb2..ec67e4b31d143adfdc048d6eea232d8be0376546 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
@@ -129,11 +129,10 @@ interface VerificationService {
         private const val TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000
         private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
 
-        fun isValidRequest(age: Long?): Boolean {
+        fun isValidRequest(age: Long?, currentTimeMillis: Long): Boolean {
             if (age == null) return false
-            val now = System.currentTimeMillis()
-            val tooInThePast = now - TEN_MINUTES_IN_MILLIS
-            val tooInTheFuture = now + FIVE_MINUTES_IN_MILLIS
+            val tooInThePast = currentTimeMillis - TEN_MINUTES_IN_MILLIS
+            val tooInTheFuture = currentTimeMillis + FIVE_MINUTES_IN_MILLIS
             return age in tooInThePast..tooInTheFuture
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
index 72f8019ada3c45c1273324cfdb617c9e090e9ceb..e3ccbad2490b712587f7e15779c3524ad8ea30f3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
@@ -45,9 +45,9 @@ interface FileService {
      * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
      */
     suspend fun downloadFile(fileName: String,
-                     mimeType: String?,
-                     url: String?,
-                     elementToDecrypt: ElementToDecrypt?): File
+                             mimeType: String?,
+                             url: String?,
+                             elementToDecrypt: ElementToDecrypt?): File
 
     suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File =
             downloadFile(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
index 267436916ec33c27dd087fe9f6e6e9f83d5f7a26..759813939f432691cc4e9e389d362bf831759704 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
@@ -43,6 +43,7 @@ interface SyncStatusService {
                 val rooms: Int,
                 val toDevice: Int
         ) : IncrementalSyncStatus()
+
         object IncrementalSyncError : IncrementalSyncStatus()
         object IncrementalSyncDone : IncrementalSyncStatus()
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
index 3e27da0c41d9af9e45bd3b61869372dde1e780e0..c5d919407aecbf50ea6a03239c0a07266a520c7d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
@@ -55,7 +55,7 @@ object MatrixLinkify {
                             MatrixPatterns.isRoomId(url) ||
                             MatrixPatterns.isGroupId(url) ||
                             MatrixPatterns.isEventId(url)) {
-                        url = PermalinkService.MATRIX_TO_URL_BASE  + url
+                        url = PermalinkService.MATRIX_TO_URL_BASE + url
                     }
                     val span = MatrixPermalinkSpan(url, callback)
                     spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
index 85291cf0f6c59fbcce12f0f2a32ef97e072d4e7d..57aacc98b81a64509699d5707a393c04ca4c3e5a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
@@ -35,21 +35,21 @@ sealed class PermalinkData {
 
     /**
      * &room_name=Team2
-        &room_avatar_url=mxc:
-         &inviter_name=bob
+    &room_avatar_url=mxc:
+    &inviter_name=bob
      */
     @Parcelize
     data class RoomEmailInviteLink(
-        val roomId: String,
-        val email: String,
-        val signUrl: String,
-        val roomName: String?,
-        val roomAvatarUrl: String?,
-        val inviterName: String?,
-        val identityServer: String,
-        val token: String,
-        val privateKey: String,
-        val roomType: String?
+            val roomId: String,
+            val email: String,
+            val signUrl: String,
+            val roomName: String?,
+            val roomAvatarUrl: String?,
+            val inviterName: String?,
+            val identityServer: String,
+            val token: String,
+            val privateKey: String,
+            val roomType: String?
     ) : PermalinkData(), Parcelable
 
     data class UserLink(val userId: String) : PermalinkData()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
index 30289531e747a94ec0a70d5234078b1982e417e0..7790942d8453bf4655f1dbad1521f36c9c0414a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import timber.log.Timber
 
 sealed class Action {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
index 04cccf73192e9988c3a417d37e89fde0c08d4e6f..df5b056c2ebf3f2ae5f566d8e0a2b6db8ff50522 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
index 0a7366e5d2210ca281c9b4ec4a450399bad15ab3..f8a930f987d1cd51ba5716f9ca5746e6ce159a63 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
index 7f430238738d7c6ac8b464915440768d0c13235a..69dd14ddc2c2468e6ea05150cf89c4dcf6a9a353 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
similarity index 98%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
index 65a13b4fec916b247206464408c2b169ce4cb990..8875807b8add337a6f439ca543f63f9d98ec887a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.di.MoshiProvider
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
index 293a06af9f6343ccc5993ecbc64177f20c7256d7..463f3c2a73d0b4870b8a2e2ae718bcffe5ead215 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 enum class Kind(val value: String) {
     EventMatch("event_match"),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
similarity index 88%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
index 466e345cad5bd77969492df9854ccfca31b91f79..ee460d70762f9c0eb935357ee65be76c481423b0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 
 data class PushEvents(
         val matchedEvents: List<Pair<Event, PushRule>>,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
similarity index 91%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
index 76885d85454e5e0401273f438448a3e4785439ab..abbdbf81049660d2edd0411a0d1b2c5be7329983 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.rest.RuleSet
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 
 interface PushRuleService {
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
similarity index 94%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
index 328e6dae118719b4ac601d095bd765e02e788af1..6973ff1372a30c0ec25620725e3597707d94f56e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.session.room.RoomGetter
@@ -44,7 +44,7 @@ class RoomMemberCountCondition(
         // Parse the is field into prefix and number the first time
         val (prefix, count) = parseIsField() ?: return false
 
-        val numMembers = room.getNumberOfJoinedMembers()
+        val numMembers = room.membershipService().getNumberOfJoinedMembers()
 
         return when (prefix) {
             "<"  -> numMembers < count
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
index 5b14e97d5ea50ec6f8ebbd28f3b8045b3993a770..4f35fb79c3623c3fc81d78c8f96ecce45bffc41b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 /**
  * Known rule ids
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
similarity index 92%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
index 7c1edc1aca03f096d9de26d763e3a631beca8ea9..307b9db042dd25110e990f42cb8071515d6f3da5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 object RuleScope {
     const val GLOBAL = "global"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
similarity index 95%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
index 5b6f6713f8016e77238779a8acf20d8b904fbf64..7b8f4c9f958cf536bb3402fb69a6d8bd08b590aa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
index 6675fb0ff5082b6b0d9169ecaf45937a3026ef73..82f5023c2f14a3bd630d10756e9577f03f31ff45 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
similarity index 84%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
index b31a1e6343929fef3481325a0188a5179026450a..1fc8329535460aa9176e6e368b7f4718be8e52c6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.pushrules.Condition
-import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
-import org.matrix.android.sdk.api.pushrules.EventMatchCondition
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
-import org.matrix.android.sdk.api.pushrules.RuleIds
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
+import org.matrix.android.sdk.api.session.pushrules.Condition
+import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition
+import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition
+import org.matrix.android.sdk.api.session.pushrules.RuleIds
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
 import timber.log.Timber
 
 /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
similarity index 94%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
index 31d7770a9f4a62193825f39e81f97dc9f85ac6a7..270ffb29408ec9a2699fdb8dc2963b1cf443de0d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.extensions.orFalse
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.toJson
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
index 46f5148714db5f447d46cbd5b40636488c6df428..5bf42b8252a63f4dd258dac3ace31250f79e073e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.pushrules.RuleIds
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.RuleIds
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index be65b883b36238509027a6398e61652138232e7f..1f990f4c0a51cdf083a214e3b72329f9f6e0794e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -38,33 +38,13 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
 import org.matrix.android.sdk.api.session.room.version.RoomVersionService
-import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.util.Optional
 
 /**
  * This interface defines methods to interact within a room.
  */
-interface Room :
-        TimelineService,
-        ThreadsService,
-        ThreadsLocalService,
-        SendService,
-        DraftService,
-        ReadService,
-        TypingService,
-        AliasService,
-        TagsService,
-        MembershipService,
-        StateService,
-        UploadsService,
-        ReportingService,
-        RoomCallService,
-        RelationService,
-        RoomCryptoService,
-        RoomPushRuleService,
-        RoomAccountDataService,
-        RoomVersionService {
+interface Room {
 
     val coroutineDispatchers: MatrixCoroutineDispatchers
 
@@ -85,27 +65,102 @@ interface Room :
     fun roomSummary(): RoomSummary?
 
     /**
-     * Generic function to search a term in a room.
-     * Ref: https://matrix.org/docs/spec/client_server/latest#module-search
-     * @param searchTerm the term to search
-     * @param nextBatch the token that retrieved from the previous response. Should be provided to get the next batch of results
-     * @param orderByRecent if true, the most recent message events will return in the first places of the list
-     * @param limit the maximum number of events to return.
-     * @param beforeLimit how many events before the result are returned.
-     * @param afterLimit how many events after the result are returned.
-     * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
-     * @return The search result
+     * Use this room as a Space, if the type is correct.
      */
-    suspend fun search(searchTerm: String,
-                       nextBatch: String?,
-                       orderByRecent: Boolean,
-                       limit: Int,
-                       beforeLimit: Int,
-                       afterLimit: Int,
-                       includeProfile: Boolean): SearchResult
+    fun asSpace(): Space?
 
     /**
-     * Use this room as a Space, if the type is correct.
+     * Get the TimelineService associated to this Room
      */
-    fun asSpace(): Space?
+    fun timelineService(): TimelineService
+
+    /**
+     * Get the ThreadsService associated to this Room
+     */
+    fun threadsService(): ThreadsService
+
+    /**
+     * Get the ThreadsLocalService associated to this Room
+     */
+    fun threadsLocalService(): ThreadsLocalService
+
+    /**
+     * Get the SendService associated to this Room
+     */
+    fun sendService(): SendService
+
+    /**
+     * Get the DraftService associated to this Room
+     */
+    fun draftService(): DraftService
+
+    /**
+     * Get the ReadService associated to this Room
+     */
+    fun readService(): ReadService
+
+    /**
+     * Get the TypingService associated to this Room
+     */
+    fun typingService(): TypingService
+
+    /**
+     * Get the AliasService associated to this Room
+     */
+    fun aliasService(): AliasService
+
+    /**
+     * Get the TagsService associated to this Room
+     */
+    fun tagsService(): TagsService
+
+    /**
+     * Get the MembershipService associated to this Room
+     */
+    fun membershipService(): MembershipService
+
+    /**
+     * Get the StateService associated to this Room
+     */
+    fun stateService(): StateService
+
+    /**
+     * Get the UploadsService associated to this Room
+     */
+    fun uploadsService(): UploadsService
+
+    /**
+     * Get the ReportingService associated to this Room
+     */
+    fun reportingService(): ReportingService
+
+    /**
+     * Get the RoomCallService associated to this Room
+     */
+    fun roomCallService(): RoomCallService
+
+    /**
+     * Get the RelationService associated to this Room
+     */
+    fun relationService(): RelationService
+
+    /**
+     * Get the RoomCryptoService associated to this Room
+     */
+    fun roomCryptoService(): RoomCryptoService
+
+    /**
+     * Get the RoomPushRuleService associated to this Room
+     */
+    fun roomPushRuleService(): RoomPushRuleService
+
+    /**
+     * Get the RoomAccountDataService associated to this Room
+     */
+    fun roomAccountDataService(): RoomAccountDataService
+
+    /**
+     * Get the RoomVersionService associated to this Room
+     */
+    fun roomVersionService(): RoomVersionService
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ece9cfbfacf59a77f9f9a7cfc57fb48041f5a6bf
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room
+
+import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+
+/**
+ * Get a TimelineEvent using the TimelineService of a Room
+ */
+fun Room.getTimelineEvent(eventId: String): TimelineEvent? =
+        timelineService().getTimelineEvent(eventId)
+
+/**
+ * Get a StateEvent using the StateService of a Room
+ */
+fun Room.getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? =
+        stateService().getStateEvent(eventType, stateKey)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
index 0238eb6c8dee4dbdd93c46efba0df588986fe517..0aded203395d5fcfaa30839734953e5bf01f1c7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
@@ -15,10 +15,12 @@
  */
 package org.matrix.android.sdk.api.session.room.model
 
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
+
 data class EventAnnotationsSummary(
-        val eventId: String,
         val reactionsSummary: List<ReactionAggregatedSummary> = emptyList(),
         val editSummary: EditAggregatedSummary? = null,
         val pollResponseSummary: PollResponseAggregatedSummary? = null,
-        val referencesAggregatedSummary: ReferencesAggregatedSummary? = null
+        val referencesAggregatedSummary: ReferencesAggregatedSummary? = null,
+        val liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummary? = null,
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
index 31ac09efb851bba6ca6dbc429dbbf8deb0b009d8..49ba2d5ab6a1015d85eef6cc5bd3775bc5a6ed21 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
@@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.events.model.Content
  * of all events that are referencing the 'eventId' event via the RelationType.REFERENCE
  */
 data class ReferencesAggregatedSummary(
-        val eventId: String,
         val content: Content?,
         val sourceEvents: List<String>,
         val localEchos: List<String>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
index 871b299f93bcfa7142b0d50ecac25e18932d2ef8..5237b10d52b3cbd8d9edcdb85309029ad7caa8e9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
@@ -36,10 +36,10 @@ data class RoomJoinRulesContent(
         @Json(name = "allow") val allowList: List<RoomJoinRulesAllowEntry>? = null
 ) {
     val joinRules: RoomJoinRules? = when (_joinRules) {
-        "public" -> RoomJoinRules.PUBLIC
-        "invite" -> RoomJoinRules.INVITE
-        "knock" -> RoomJoinRules.KNOCK
-        "private" -> RoomJoinRules.PRIVATE
+        "public"     -> RoomJoinRules.PUBLIC
+        "invite"     -> RoomJoinRules.INVITE
+        "knock"      -> RoomJoinRules.KNOCK
+        "private"    -> RoomJoinRules.PRIVATE
         "restricted" -> RoomJoinRules.RESTRICTED
         else         -> {
             Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
index 6b4d78283264799cb76a8f1229052e6245f24660..67ef85787ecfb87348aaa0d4afab3eeca9ab0c18 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
@@ -44,7 +44,7 @@ data class CallAnswerContent(
          * Capability advertisement.
          */
         @Json(name = "capabilities") val capabilities: CallCapabilities? = null
-) : CallSignalingContent  {
+) : CallSignalingContent {
 
     @JsonClass(generateAdapter = true)
     data class Answer(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
index d70e63d1226f421f72849508fd28c091534f30fb..24c8152f3cca195faa00c3ada52b74688ea5665f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
@@ -55,7 +55,7 @@ data class CallInviteContent(
          */
         @Json(name = "capabilities") val capabilities: CallCapabilities? = null
 
-) : CallSignalingContent  {
+) : CallSignalingContent {
     @JsonClass(generateAdapter = true)
     data class Offer(
             /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
index bbbfbe68ab8c8c061d891f2fe50deb11bdf3438c..5c6c6cda01a2c1db3b30dc88e60e6d436c52d8a2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
@@ -47,7 +47,7 @@ data class CallNegotiateContent(
          */
         @Json(name = "version") override val version: String?
 
-        ) : CallSignalingContent  {
+) : CallSignalingContent {
     @JsonClass(generateAdapter = true)
     data class Description(
             /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
index 7947b7d0bd1b545738ce28d4f3bf69d8ae2cc494..e480e013ea9ced4c49dffe7029a3a6a0dddbc719 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
@@ -61,7 +61,7 @@ data class CallReplacesContent(
          * Required. The version of the VoIP specification this messages adheres to.
          */
         @Json(name = "version") override val version: String?
-) : CallSignalingContent  {
+) : CallSignalingContent {
 
     @JsonClass(generateAdapter = true)
     data class TargetUser(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0b28d62f56b8d0ae02856d248a5272c6db131732
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model.livelocation
+
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
+
+/**
+ * Aggregation info concerning a live location share.
+ */
+data class LiveLocationShareAggregatedSummary(
+        val isActive: Boolean?,
+        val endOfLiveTimestampMillis: Long?,
+        val lastLocationDataContent: MessageBeaconLocationDataContent?,
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
index fa18bfd21f79485bc05a3baaf397a444b6b7752a..132b72902f5e9de2c397fc6ded5c29ce468ad25f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
@@ -52,5 +52,5 @@ data class FileInfo(
  * Get the url of the encrypted thumbnail or of the thumbnail
  */
 fun FileInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
index 0099208320ee6f28a54c45bf3a3c64d3b173d573..bd99ea690019b9ba428f5bf0d96376226e553eaa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
@@ -62,5 +62,5 @@ data class ImageInfo(
  * Get the url of the encrypted thumbnail or of the thumbnail
  */
 fun ImageInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
similarity index 57%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
index 106e76eafd08889017852326406aedd4738ab502..f75704a89158b704dca114a22b13439c1ee1b8b0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
@@ -14,60 +14,59 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.session.room.model.livelocation
+package org.matrix.android.sdk.api.session.room.model.message
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Content
-import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
-import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
-import org.matrix.android.sdk.api.session.room.model.message.MessageContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
+/**
+ * Content of the state event of type
+ * [EventType.STATE_ROOM_BEACON_INFO][org.matrix.android.sdk.api.session.events.model.EventType.STATE_ROOM_BEACON_INFO]
+ *
+ * It contains general info related to a live location share.
+ * Locations are sent in a different message related to the state event.
+ * See [MessageBeaconLocationDataContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
+ */
 @JsonClass(generateAdapter = true)
-data class LiveLocationBeaconContent(
+data class MessageBeaconInfoContent(
         /**
          * Local message type, not from server
          */
         @Transient
-        override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION_STATE,
+        override val msgType: String = MessageType.MSGTYPE_BEACON_INFO,
 
         @Json(name = "body") override val body: String = "",
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
         @Json(name = "m.new_content") override val newContent: Content? = null,
 
         /**
-         * Indicates user's intent to share ephemeral location.
+         * Optional description of the beacon.
          */
-        @Json(name = "org.matrix.msc3672.beacon_info") val unstableBeaconInfo: BeaconInfo? = null,
-        @Json(name = "m.beacon_info") val beaconInfo: BeaconInfo? = null,
+        @Json(name = "description") val description: String? = null,
         /**
-         * Beacon creation timestamp.
+         * Beacon should be considered as inactive after this timeout as milliseconds.
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
-        @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null,
+        @Json(name = "timeout") val timeout: Long? = null,
         /**
-         * Live location asset type.
+         * Should be set true to start sharing beacon.
          */
-        @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
-        @Json(name = "m.asset") val locationAsset: LocationAsset? = null,
+        @Json(name = "live") val isLive: Boolean? = null,
 
         /**
-         * Client side tracking of the last location
+         * Beacon creation timestamp.
          */
-        var lastLocationContent: MessageLiveLocationContent? = null,
-
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null,
         /**
-         * Client side tracking of whether the beacon has timed out.
+         * Live location asset type.
          */
-        var hasTimedOut: Boolean = false
+        @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
+        @Json(name = "m.asset") val locationAsset: LocationAsset? = null,
 ) : MessageContent {
 
-    fun getBestBeaconInfo() = beaconInfo ?: unstableBeaconInfo
-
-    fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 
     fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
similarity index 72%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
index 548dc8536944d16e4d84f3187104840b704d37d8..4a4ef46bc84b583d8050a0db242c8983469e3216 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
@@ -21,13 +21,21 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
+/**
+ * Content of the state event of type
+ * [EventType.BEACON_LOCATION_DATA][org.matrix.android.sdk.api.session.events.model.EventType.BEACON_LOCATION_DATA]
+ *
+ * It contains location data related to a live location share.
+ * It is related to the state event that originally started the live.
+ * See [MessageBeaconInfoContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent]
+ */
 @JsonClass(generateAdapter = true)
-data class MessageLiveLocationContent(
+data class MessageBeaconLocationDataContent(
         /**
          * Local message type, not from server
          */
         @Transient
-        override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION,
+        override val msgType: String = MessageType.MSGTYPE_BEACON_LOCATION_DATA,
 
         @Json(name = "body") override val body: String = "",
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@@ -42,11 +50,11 @@ data class MessageLiveLocationContent(
         /**
          * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
-        @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null
 ) : MessageContent {
 
     fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
 
-    fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
index 2052133b068cf360d7da16ae60ae7fb4f758edce..19cb20430d2968b3d3cad8f7f28f95c3be00f113 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
@@ -49,8 +49,8 @@ data class MessageLocationContent(
         /**
          * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null,
-        @Json(name = "m.ts") val ts: Long? = null,
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null,
         @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
         @Json(name = "m.text") val text: String? = null,
         /**
@@ -66,7 +66,7 @@ data class MessageLocationContent(
 
     fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
 
-    fun getBestTs() = ts ?: unstableTs
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 
     fun getBestText() = text ?: unstableText
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
index 106bf2e03085fa4847ee829df59caf3a293616bb..b12d9ed6c825145c7d029e816be8ba4bdc8ce999 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
@@ -41,6 +41,6 @@ object MessageType {
     const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
 
     // Fake message types for live location events to be able to inherit them from MessageContent
-    const val MSGTYPE_LIVE_LOCATION_STATE = "org.matrix.android.sdk.livelocation.state"
-    const val MSGTYPE_LIVE_LOCATION = "org.matrix.android.sdk.livelocation"
+    const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
+    const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data"
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
index b2b3cdac9090b231389aa2799e04e75b81cf5c2b..a0699831f72b3152fa6209f41a9c0c8254cb6e44 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReque
 
 @JsonClass(generateAdapter = true)
 data class MessageVerificationRequestContent(
-        @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
         @Json(name = "body") override val body: String,
         @Json(name = "from_device") override val fromDevice: String?,
         @Json(name = "methods") override val methods: List<String>,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
index 9266a0fb0f368e18a2ed87b67c681e8b2250ada4..9b657971b9258cb50413a54152ccb4493b77addf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
@@ -27,7 +27,7 @@ data class MessageVideoContent(
         /**
          * Required. Must be 'm.video'.
          */
-        @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
index 28f3a47d114ef7986ab5207161624ffddfc72d6b..b02b4d96adc7696ffe8ca16d54f02e4a375ad636 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
@@ -67,5 +67,5 @@ data class VideoInfo(
  * Get the url of the encrypted thumbnail or of the thumbnail
  */
 fun VideoInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
index f645f3ebf9cb94d021091995def4beaa9b8cb412..98171795e2cb30307f80af2d7d63aaaa730217f7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
@@ -84,8 +84,9 @@ interface StateService {
      * @param eventType The type of event to send.
      * @param stateKey The state_key for the state to send. Can be an empty string.
      * @param body The content object of the event; the fields in this object will vary depending on the type of event
+     * @return the id of the created state event
      */
-    suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict)
+    suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict): String
 
     /**
      * Get a state event of the room
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index a2ae8bfeb52e5b88068efff6e215a44a0b50a189..adbc8ab12a02ea8c1aa8196dde983a20d84abf11 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.isSticker
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
-import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
@@ -139,7 +139,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
     return when (root.getClearType()) {
         EventType.STICKER                   -> root.getClearContent().toModel<MessageStickerContent>()
         in EventType.POLL_START             -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>()
-        in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<LiveLocationBeaconContent>()
+        in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageBeaconInfoContent>()
         else                                -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
index 4415c8e4b307dc31e2d08e262d788b361ef5e99b..a35a291d9b2342f385447efb167da54c221d3080 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.api.session.room.timeline
 
+// TODO Move to internal, strange?
 data class TimelineEventFilters(
         /**
          * A flag to filter edit events
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
index 6548453c8a126da8f203c82957ed46083781f59d..b45f3ecb71f5ff6ff90de643c6f9bdf93e1ad7ae 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
@@ -31,7 +31,8 @@ data class TimelineSettings(
         /**
          * The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline
          */
-        val rootThreadEventId: String? = null) {
+        val rootThreadEventId: String? = null,
+) {
 
     /**
      * Returns true if this is a thread timeline or false otherwise
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
index a91b97b86cd74531c6be22535b93de260e264f46..038533c19e814c562fda444f087091d31b79fe7c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
@@ -23,7 +23,7 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
     data class UnsupportedAlgorithm(val algorithm: String) : SharedSecretStorageError("Unknown algorithm $algorithm")
     data class SecretNotEncrypted(val secretName: String) : SharedSecretStorageError("Missing content for secret $secretName")
     data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String) :
-        SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
+            SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
 
     object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
     object ParsingError : SharedSecretStorageError("parsing Error")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
index 78267640675bea67c87b30a07aa55d283503bbfe..afd26f7be54e10d545732d2131dc54a3d267556d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
@@ -68,7 +68,7 @@ interface SpaceService {
                                    suggestedOnly: Boolean? = null,
                                    limit: Int? = null,
                                    from: String? = null,
-                                   // when paginating, pass back the m.space.child state events
+            // when paginating, pass back the m.space.child state events
                                    knownStateList: List<Event>? = null): SpaceHierarchyData
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ee4a180dea394def649e236834340191ba7e0fac
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.uia
+
+enum class UiaResult {
+    SUCCESS,
+    FAILURE,
+    CANCELLED
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d5f9d72efe5674fb8177ebd727cfe4f4aad339f4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.uia.exceptions
+
+class UiaCancelledException(message: String? = null) : Exception(message)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
index 554e21ce55981a76522302f4bde66b3562b37619..ebad859b05178c9b0b8ac79753045606448e9988 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig
 import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed
 import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
 import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
+import org.matrix.android.sdk.internal.auth.registration.RegistrationCustomParams
 import org.matrix.android.sdk.internal.auth.registration.RegistrationParams
 import org.matrix.android.sdk.internal.auth.registration.SuccessResult
 import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
@@ -68,6 +69,14 @@ internal interface AuthAPI {
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
     suspend fun register(@Body registrationParams: RegistrationParams): Credentials
 
+    /**
+     * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete
+     * method to perform other custom stages
+     * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
+     */
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
+    suspend fun registerCustom(@Body registrationCustomParams: RegistrationCustomParams): Credentials
+
     /**
      * Checks to see if a username is available, and valid, for the server.
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
index 4a156e74cd994743205e42240a5d6f887c065b53..590b333e9008ec64d799db074f19f660bcfd6ec1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.auth.registration
 
 import kotlinx.coroutines.delay
+import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
 import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
 import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
@@ -25,6 +26,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
 import org.matrix.android.sdk.api.auth.registration.toFlowResult
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
+import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.auth.AuthAPI
 import org.matrix.android.sdk.internal.auth.PendingSessionStore
 import org.matrix.android.sdk.internal.auth.SessionCreator
@@ -45,6 +47,7 @@ internal class DefaultRegistrationWizard(
     private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI)
     private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
     private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI)
+    private val registerCustomTask: RegisterCustomTask = DefaultRegisterCustomTask(authAPI)
 
     override val currentThreePid: String?
         get() {
@@ -187,22 +190,51 @@ internal class DefaultRegistrationWizard(
         return performRegistrationRequest(params)
     }
 
-    private suspend fun performRegistrationRequest(registrationParams: RegistrationParams,
-                                                   delayMillis: Long = 0): RegistrationResult {
+    override suspend fun registrationCustom(
+            authParams: JsonDict
+    ): RegistrationResult {
+        val safeSession = pendingSessionData.currentSession
+                ?: throw IllegalStateException("developer error, call createAccount() method first")
+
+        val mutableParams = authParams.toMutableMap()
+        mutableParams["session"] = safeSession
+
+        val params = RegistrationCustomParams(auth = mutableParams)
+        return performRegistrationOtherRequest(params)
+    }
+
+    private suspend fun performRegistrationRequest(
+            registrationParams: RegistrationParams,
+            delayMillis: Long = 0
+    ): RegistrationResult {
         delay(delayMillis)
+        return register { registerTask.execute(RegisterTask.Params(registrationParams)) }
+    }
+
+    private suspend fun performRegistrationOtherRequest(
+            registrationCustomParams: RegistrationCustomParams
+    ): RegistrationResult {
+        return register { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) }
+    }
+
+    private suspend fun register(
+            execute: suspend () -> Credentials
+    ): RegistrationResult {
         val credentials = try {
-            registerTask.execute(RegisterTask.Params(registrationParams))
+            execute.invoke()
         } catch (exception: Throwable) {
             if (exception is RegistrationFlowError) {
-                pendingSessionData = pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session)
-                        .also { pendingSessionStore.savePendingSessionData(it) }
+                pendingSessionData =
+                        pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session)
+                                .also { pendingSessionStore.savePendingSessionData(it) }
                 return RegistrationResult.FlowResponse(exception.registrationFlowResponse.toFlowResult())
             } else {
                 throw exception
             }
         }
 
-        val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
+        val session =
+                sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
         return RegistrationResult.Success(session)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt
new file mode 100644
index 0000000000000000000000000000000000000000..60af708c38e86ba4646c9261b73e0078346fcc1a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.auth.registration
+
+import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
+import org.matrix.android.sdk.internal.auth.AuthAPI
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+
+internal interface RegisterCustomTask : Task<RegisterCustomTask.Params, Credentials> {
+    data class Params(
+            val registrationCustomParams: RegistrationCustomParams
+    )
+}
+
+internal class DefaultRegisterCustomTask(
+        private val authAPI: AuthAPI
+) : RegisterCustomTask {
+
+    override suspend fun execute(params: RegisterCustomTask.Params): Credentials {
+        try {
+            return executeRequest(null) {
+                authAPI.registerCustom(params.registrationCustomParams)
+            }
+        } catch (throwable: Throwable) {
+            throw throwable.toRegistrationFlowResponse()
+                    ?.let { Failure.RegistrationFlowError(it) }
+                    ?: throwable
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt
similarity index 56%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt
index 873edc0f1fd3ac2976021ea87351039792b8f3e1..45adac6c2691bd9d8c2f47abe681f6e315dc392c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Matrix.org Foundation C.I.C.
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,20 +14,18 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.session.room.model.livelocation
+package org.matrix.android.sdk.internal.auth.registration
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.util.JsonDict
 
+/**
+ * Class to pass parameters to the custom registration types for /register.
+ */
 @JsonClass(generateAdapter = true)
-data class BeaconInfo(
-        @Json(name = "description") val description: String? = null,
-        /**
-         * Beacon should be considered as inactive after this timeout as milliseconds.
-         */
-        @Json(name = "timeout") val timeout: Long? = null,
-        /**
-         * Should be set true to start sharing beacon.
-         */
-        @Json(name = "live") val isLive: Boolean? = null
+internal data class RegistrationCustomParams(
+        // authentication parameters
+        @Json(name = "auth")
+        val auth: JsonDict? = null,
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt
index da0866a5fd56b3e8e7ec58f907c930d78ce74bb4..7dafacb3c06ad3b8202f0681dc8cf8a323eb3157 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt
@@ -20,6 +20,8 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
+import org.matrix.android.sdk.api.session.uia.UiaResult
+import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException
 import timber.log.Timber
 import kotlin.coroutines.suspendCoroutine
 
@@ -30,14 +32,15 @@ import kotlin.coroutines.suspendCoroutine
  * @param interceptor see doc in [UserInteractiveAuthInterceptor]
  * @param retryBlock called at the end of the process, in this block generally retry executing the task, with
  * provided authUpdate
- * @return true if UIA is handled without error
+ * @return UiaResult if UIA handled, failed or cancelled
+ *
  */
 internal suspend fun handleUIA(failure: Throwable,
                                interceptor: UserInteractiveAuthInterceptor,
-                               retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
+                               retryBlock: suspend (UIABaseAuth) -> Unit): UiaResult {
     Timber.d("## UIA: check error ${failure.message}")
     val flowResponse = failure.toRegistrationFlowResponse()
-            ?: return false.also {
+            ?: return UiaResult.FAILURE.also {
                 Timber.d("## UIA: not a UIA error")
             }
 
@@ -50,14 +53,19 @@ internal suspend fun handleUIA(failure: Throwable,
             interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
         }
     } catch (failure2: Throwable) {
-        Timber.w(failure2, "## UIA: failed to participate")
-        return false
+        return if (failure2 is UiaCancelledException) {
+            Timber.w(failure2, "## UIA: cancelled")
+            UiaResult.CANCELLED
+        } else {
+            Timber.w(failure2, "## UIA: failed to participate")
+            UiaResult.FAILURE
+        }
     }
 
     Timber.d("## UIA: updated auth")
     return try {
         retryBlock(authUpdate)
-        true
+        UiaResult.SUCCESS
     } catch (failure3: Throwable) {
         handleUIA(failure3, interceptor, retryBlock)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
index 98950374eddb2e12db908c4b9090570a881d50be..aaf23d17b366723fb91587dcd88d8be1dbe6a938 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
@@ -32,12 +32,13 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
 
 internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -65,6 +66,7 @@ internal class CancelGossipRequestWorker(context: Context, params: WorkerParamet
     @Inject lateinit var sendToDeviceTask: SendToDeviceTask
     @Inject lateinit var cryptoStore: IMXCryptoStore
     @Inject lateinit var credentials: Credentials
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -85,7 +87,7 @@ internal class CancelGossipRequestWorker(context: Context, params: WorkerParamet
                 content = toDeviceContent.toContent(),
                 senderId = credentials.userId
         ).also {
-            it.ageLocalTs = System.currentTimeMillis()
+            it.ageLocalTs = clock.epochMillis()
         })
 
         params.recipients.forEach { userToDeviceMap ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
index 2a58d731e5310215631eb359362ede7b17426f25..73dfc468d97a2ba35189dfdb0ac7eaae954c1d96 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
@@ -48,7 +48,7 @@ internal class CryptoSessionInfoProvider @Inject constructor(
     /**
      * @param allActive if true return joined as well as invited, if false, only joined
      */
-     fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
+    fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
         var userIds: List<String> = emptyList()
         monarchy.doWithRealm { realm ->
             userIds = if (allActive) {
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 6a57d94677dddcdfd5c48b964883e6347d92d6f4..54c9990bf623ffaf4b3ea88cb3488f264c67c7c9 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
@@ -103,6 +103,7 @@ import org.matrix.android.sdk.internal.task.TaskThread
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.task.launchToCallback
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmManager
 import timber.log.Timber
 import java.util.concurrent.atomic.AtomicBoolean
@@ -130,6 +131,7 @@ internal class DefaultCryptoService @Inject constructor(
         private val userId: String,
         @DeviceId
         private val deviceId: String?,
+        private val clock: Clock,
         private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
         // the crypto store
         private val cryptoStore: IMXCryptoStore,
@@ -701,11 +703,11 @@ internal class DefaultCryptoService @Inject constructor(
             }
             val safeAlgorithm = alg
             if (safeAlgorithm != null) {
-                val t0 = System.currentTimeMillis()
+                val t0 = clock.epochMillis()
                 Timber.tag(loggerTag.value).v("encryptEventContent() starts")
                 runCatching {
                     val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
-                    Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
+                    Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
                     MXEncryptEventContentResult(content, EventType.ENCRYPTED)
                 }.foldToCallback(callback)
             } else {
@@ -1022,9 +1024,9 @@ internal class DefaultCryptoService @Inject constructor(
         return withContext(coroutineDispatchers.crypto) {
             Timber.tag(loggerTag.value).v("importRoomKeys starts")
 
-            val t0 = System.currentTimeMillis()
+            val t0 = clock.epochMillis()
             val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
-            val t1 = System.currentTimeMillis()
+            val t1 = clock.epochMillis()
 
             Timber.tag(loggerTag.value).v("importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
 
@@ -1032,7 +1034,7 @@ internal class DefaultCryptoService @Inject constructor(
                     .adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
                     .fromJson(roomKeys)
 
-            val t2 = System.currentTimeMillis()
+            val t2 = clock.epochMillis()
 
             Timber.tag(loggerTag.value).v("importRoomKeys : JSON parsing ${t2 - t1} ms")
 
@@ -1224,7 +1226,7 @@ internal class DefaultCryptoService @Inject constructor(
 //        val deviceKey = deviceInfo.identityKey()
 //
 //        val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
-//        val now = System.currentTimeMillis()
+//        val now = clock.epochMillis()
 //        if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
 //            Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
 //            return
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
index 6cae2d093586bfaf928e0a6e19656e26d3f83aa3..535999373b8434d21cd0a6c12cfe471b50a0c8ff 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
@@ -31,19 +31,23 @@ import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.logLimit
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
 // Legacy name: MXDeviceList
 @SessionScope
-internal class DeviceListManager @Inject constructor(private val cryptoStore: IMXCryptoStore,
-                                                     private val olmDevice: MXOlmDevice,
-                                                     private val syncTokenStore: SyncTokenStore,
-                                                     private val credentials: Credentials,
-                                                     private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
-                                                     private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
-                                                     coroutineDispatchers: MatrixCoroutineDispatchers,
-                                                     private val taskExecutor: TaskExecutor) {
+internal class DeviceListManager @Inject constructor(
+        private val cryptoStore: IMXCryptoStore,
+        private val olmDevice: MXOlmDevice,
+        private val syncTokenStore: SyncTokenStore,
+        private val credentials: Credentials,
+        private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
+        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
+        coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
+) {
 
     interface UserDevicesUpdateListener {
         fun onUsersDeviceUpdate(userIds: List<String>)
@@ -310,9 +314,9 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
             stored
         } else {
             Timber.v("## CRYPTO | downloadKeys() : starts")
-            val t0 = System.currentTimeMillis()
+            val t0 = clock.epochMillis()
             val result = doKeyDownloadForUsers(downloadUsers)
-            Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
+            Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${clock.epochMillis() - t0} ms")
             result.also {
                 it.addEntriesFromMap(stored)
             }
@@ -475,8 +479,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
         }
 
         if (!isVerified) {
-            Timber.e("## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" +
-                    deviceKeys.deviceId + " with error " + errorMessage)
+            Timber.e(
+                    "## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" +
+                            deviceKeys.deviceId + " with error " + errorMessage
+            )
             return false
         }
 
@@ -486,9 +492,11 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
                 // best off sticking with the original keys.
                 //
                 // Should we warn the user about it somehow?
-                Timber.e("## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" +
-                        deviceKeys.deviceId + " has changed : " +
-                        previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
+                Timber.e(
+                        "## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" +
+                                deviceKeys.deviceId + " has changed : " +
+                                previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey
+                )
 
                 Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
                 Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
index 1c8bce7377c6382d81336a6c20105b626bd70339..a09418964538a74890aa9cd1fa66e9c9809ea4e4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.extensions.foldToCallback
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -47,6 +48,7 @@ private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
 internal class EventDecryptor @Inject constructor(
         private val cryptoCoroutineScope: CoroutineScope,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
         private val roomDecryptorProvider: RoomDecryptorProvider,
         private val messageEncrypter: MessageEncrypter,
         private val sendToDeviceTask: SendToDeviceTask,
@@ -153,7 +155,7 @@ internal class EventDecryptor @Inject constructor(
         // we should force start a new session for those
         Timber.tag(loggerTag.value).v("Unwedging:  ${wedgedDevices.size} are wedged")
         // get the one that should be retried according to rate limit
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         val toUnwedge = wedgedDevices.filter {
             val lastForcedDate = lastNewSessionForcedDates[it] ?: 0
             if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
index 0013c31eead3319a5977e72d9dad4275bc944328..a2c85e5ceb1e825e5f3d61481d14f3263a160691 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.CancelableWork
 import org.matrix.android.sdk.internal.worker.startChain
+import java.util.UUID
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -44,8 +45,8 @@ internal class GossipingWorkManager @Inject constructor(
     }
 
     // Prevent sending queue to stay broken after app restart
-    // The unique queue id will stay the same as long as this object is instanciated
-    val queueSuffixApp = System.currentTimeMillis()
+    // The unique queue id will stay the same as long as this object is instantiated
+    private val queueSuffixApp = UUID.randomUUID()
 
     fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
         workManagerProvider.workManager
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 a78444dff9ff222d49d8cbc7f3fd9edccff420bf..28ddf291b216d987e785373a4a45eebad1ddf767 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
@@ -75,15 +75,15 @@ internal class InboundGroupSessionStore @Inject constructor(
 
     @Synchronized
     fun getInboundGroupSession(sessionId: String, senderKey: String): InboundGroupSessionHolder? {
-            val known = sessionCache[CacheKey(sessionId, senderKey)]
-            Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession  $sessionId in cache ${known != null}")
-            return known
-                    ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
-                Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
-                sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it))
-            }?.let {
-                InboundGroupSessionHolder(it)
-            }
+        val known = sessionCache[CacheKey(sessionId, senderKey)]
+        Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession  $sessionId in cache ${known != null}")
+        return known
+                ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
+                    Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
+                    sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it))
+                }?.let {
+                    InboundGroupSessionHolder(it)
+                }
     }
 
     @Synchronized
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
index 3a409cf3fdbe4b73e672867e5e568784bbd63697..1612caba9fa55cf3532be20e93e4e4f4bb7f7883 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
@@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import timber.log.Timber
 import java.util.concurrent.Executors
@@ -59,7 +60,9 @@ internal class IncomingGossipingRequestManager @Inject constructor(
         private val roomEncryptorsStore: RoomEncryptorsStore,
         private val roomDecryptorProvider: RoomDecryptorProvider,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope) {
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
+) {
 
     private val executor = Executors.newSingleThreadExecutor()
 
@@ -89,7 +92,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
     fun onVerificationCompleteForDevice(deviceId: String) {
         // For now we just keep an in memory cache
         synchronized(recentlyVerifiedDevices) {
-            recentlyVerifiedDevices[deviceId] = System.currentTimeMillis()
+            recentlyVerifiedDevices[deviceId] = clock.epochMillis()
         }
     }
 
@@ -100,7 +103,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
         }
         if (verifTimestamp == null) return false
 
-        val age = System.currentTimeMillis() - verifTimestamp
+        val age = clock.epochMillis() - verifTimestamp
 
         return age < FIVE_MINUTES_IN_MILLIS
     }
@@ -114,11 +117,11 @@ internal class IncomingGossipingRequestManager @Inject constructor(
     fun onGossipingRequestEvent(event: Event) {
         val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>()
         Timber.i("## CRYPTO | GOSSIP onGossipingRequestEvent received type ${event.type} from user:${event.senderId}, content:$roomKeyShare")
-        // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+        // val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it }
         when (roomKeyShare?.action) {
-            GossipingToDeviceObject.ACTION_SHARE_REQUEST -> {
+            GossipingToDeviceObject.ACTION_SHARE_REQUEST      -> {
                 if (event.getClearType() == EventType.REQUEST_SECRET) {
-                    IncomingSecretShareRequest.fromEvent(event)?.let {
+                    IncomingSecretShareRequest.fromEvent(event, clock.epochMillis())?.let {
                         if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
                             // ignore, it was sent by me as *
                             Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
@@ -129,7 +132,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
                         }
                     }
                 } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
-                    IncomingRoomKeyRequest.fromEvent(event)?.let {
+                    IncomingRoomKeyRequest.fromEvent(event, clock.epochMillis())?.let {
                         if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
                             // ignore, it was sent by me as *
                             Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
@@ -141,7 +144,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
                 }
             }
             GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> {
-                IncomingRequestCancellation.fromEvent(event)?.let {
+                IncomingRequestCancellation.fromEvent(event, clock.epochMillis())?.let {
                     receivedRequestCancellations.add(it)
                 }
             }
@@ -346,7 +349,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
         val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified()
 
         when (secretName) {
-            MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master
+            MASTER_KEY_SSSS_NAME       -> cryptoStore.getCrossSigningPrivateKeys()?.master
             SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
             USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
             KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
index f8235bf344f7ecf54e336a66c505278877a9236c..89e38cb7cfc418d88f7d1b27bc9d827bd497f2c9 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
@@ -29,6 +29,7 @@ import javax.crypto.spec.SecretKeySpec
 import kotlin.experimental.and
 import kotlin.experimental.xor
 import kotlin.math.min
+import kotlin.system.measureTimeMillis
 
 /**
  * Utility class to import/export the crypto data
@@ -310,41 +311,41 @@ internal object MXMegolmExportEncryption {
      */
     @Throws(Exception::class)
     private fun deriveKeys(salt: ByteArray, iterations: Int, password: String): ByteArray {
-        val t0 = System.currentTimeMillis()
-
-        // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
-        // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
-        // noticed as dklen/hlen
-        val prf = Mac.getInstance("HmacSHA512")
-        prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
-
-        // 512 bits key length
         val key = ByteArray(64)
-        val uc = ByteArray(64)
-
-        // U1 = PRF(Password, Salt || INT_32_BE(i))
-        prf.update(salt)
-        val int32BE = ByteArray(4) { 0.toByte() }
-        int32BE[3] = 1.toByte()
-        prf.update(int32BE)
-        prf.doFinal(uc, 0)
+        measureTimeMillis {
+            // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
+            // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
+            // noticed as dklen/hlen
+            val prf = Mac.getInstance("HmacSHA512")
+            prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
+
+            // 512 bits key length
+            val uc = ByteArray(64)
+
+            // U1 = PRF(Password, Salt || INT_32_BE(i))
+            prf.update(salt)
+            val int32BE = ByteArray(4) { 0.toByte() }
+            int32BE[3] = 1.toByte()
+            prf.update(int32BE)
+            prf.doFinal(uc, 0)
 
-        // copy to the key
-        System.arraycopy(uc, 0, key, 0, uc.size)
+            // copy to the key
+            System.arraycopy(uc, 0, key, 0, uc.size)
 
-        for (index in 2..iterations) {
-            // Uc = PRF(Password, Uc-1)
-            prf.update(uc)
-            prf.doFinal(uc, 0)
+            for (index in 2..iterations) {
+                // Uc = PRF(Password, Uc-1)
+                prf.update(uc)
+                prf.doFinal(uc, 0)
 
-            // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
-            for (byteIndex in uc.indices) {
-                key[byteIndex] = key[byteIndex] xor uc[byteIndex]
+                // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
+                for (byteIndex in uc.indices) {
+                    key[byteIndex] = key[byteIndex] xor uc[byteIndex]
+                }
             }
+        }.also {
+            Timber.v("## deriveKeys() : $iterations in $it ms")
         }
 
-        Timber.v("## deriveKeys() : $iterations in ${System.currentTimeMillis() - t0} ms")
-
         return key
     }
 }
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 4947761f05c3ca5e204f6c2c7864d6787c8331f7..7eec83abddf0b501daab4a3cf860567b2d7bae1d 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
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 import org.matrix.android.sdk.internal.util.convertFromUTF8
 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.OlmMessage
@@ -55,7 +56,8 @@ internal class MXOlmDevice @Inject constructor(
          */
         private val store: IMXCryptoStore,
         private val olmSessionStore: OlmSessionStore,
-        private val inboundGroupSessionStore: InboundGroupSessionStore
+        private val inboundGroupSessionStore: InboundGroupSessionStore,
+        private val clock: Clock,
 ) {
 
     val mutex = Mutex()
@@ -277,7 +279,7 @@ internal class MXOlmDevice @Inject constructor(
             // Pretend we've received a message at this point, otherwise
             // if we try to send a message to the device, it won't use
             // this session
-            olmSessionWrapper.onMessageReceived()
+            olmSessionWrapper.onMessageReceived(clock.epochMillis())
 
             olmSessionStore.storeSession(olmSessionWrapper, theirIdentityKey)
 
@@ -348,7 +350,7 @@ internal class MXOlmDevice @Inject constructor(
 
                 val olmSessionWrapper = OlmSessionWrapper(olmSession, 0)
                 // This counts as a received message: set last received message time to now
-                olmSessionWrapper.onMessageReceived()
+                olmSessionWrapper.onMessageReceived(clock.epochMillis())
 
                 olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
             } catch (e: Exception) {
@@ -454,7 +456,7 @@ internal class MXOlmDevice @Inject constructor(
             payloadString =
                     olmSessionWrapper.mutex.withLock {
                         olmSessionWrapper.olmSession.decryptMessage(olmMessage).also {
-                            olmSessionWrapper.onMessageReceived()
+                            olmSessionWrapper.onMessageReceived(clock.epochMillis())
                         }
                     }
             olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
@@ -520,6 +522,7 @@ internal class MXOlmDevice @Inject constructor(
             return MXOutboundSessionInfo(
                     sessionId = sessionId,
                     sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
+                    clock,
                     restoredOutboundGroupSession.creationTime
             )
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
index f4fbca6a0fe5bc7a29cc5f2f7eacf8d366e0c3a6..aac6f67aea1ea95dd0aa698b4160865986b5f336 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
@@ -66,7 +66,8 @@ internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoS
         olmSessions.getOrPut(deviceKey) { mutableListOf() }.forEach { cached ->
             getSafeSessionIdentifier(cached.olmSession)?.let { cachedSessionId ->
                 if (!persistedKnownSessions.contains(cachedSessionId)) {
-                    persistedKnownSessions.add(cachedSessionId)
+                    // as it's in cache put in on top
+                    persistedKnownSessions.add(0, cachedSessionId)
                 }
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
index 792c9a25dcdb3d1a484417b37bbc73277b2a64fe..8143e36892e50f9bbb9c9e4c37c870c0c00b62a1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
 import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import timber.log.Timber
 import javax.inject.Inject
@@ -38,6 +39,7 @@ internal class OneTimeKeysUploader @Inject constructor(
         private val olmDevice: MXOlmDevice,
         private val objectSigner: ObjectSigner,
         private val uploadKeysTask: UploadKeysTask,
+        private val clock: Clock,
         context: Context
 ) {
     // tell if there is a OTK check in progress
@@ -77,7 +79,7 @@ internal class OneTimeKeysUploader @Inject constructor(
             Timber.v("maybeUploadOneTimeKeys: already in progress")
             return
         }
-        if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
+        if (clock.epochMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
             // we've done a key upload recently.
             Timber.v("maybeUploadOneTimeKeys: executed too recently")
             return
@@ -94,7 +96,7 @@ internal class OneTimeKeysUploader @Inject constructor(
 
         Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}")
 
-        lastOneTimeKeyCheck = System.currentTimeMillis()
+        lastOneTimeKeyCheck = clock.epochMillis()
 
         // We then check how many keys we can store in the Account object.
         val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
@@ -126,7 +128,7 @@ internal class OneTimeKeysUploader @Inject constructor(
 
         // Check if we need to forget a fallback key
         val latestPublishedTime = getLastFallbackKeyPublishTime()
-        if (latestPublishedTime != 0L && System.currentTimeMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
+        if (latestPublishedTime != 0L && clock.epochMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
             // This should be called once you are reasonably certain that you will not receive any more messages
             // that use the old fallback key
             Timber.d("## forgetFallbackKey()")
@@ -168,7 +170,7 @@ internal class OneTimeKeysUploader @Inject constructor(
         olmDevice.markKeysAsPublished()
         if (hadUnpublishedFallbackKey) {
             // It had an unpublished fallback key that was published just now
-            saveLastFallbackKeyPublishTime(System.currentTimeMillis())
+            saveLastFallbackKeyPublishTime(clock.epochMillis())
         }
 
         if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
index dbdea9741107897cb1d6b1648b145a1b11f22b5b..3b43ad672b57e0544918e0d0b484d6aee3ebadb1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
@@ -35,13 +35,14 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class SendGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -57,6 +58,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter
     @Inject lateinit var sendToDeviceTask: SendToDeviceTask
     @Inject lateinit var cryptoStore: IMXCryptoStore
     @Inject lateinit var credentials: Credentials
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -85,7 +87,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter
                         content = toDeviceContent.toContent(),
                         senderId = credentials.userId
                 ).also {
-                    it.ageLocalTs = System.currentTimeMillis()
+                    it.ageLocalTs = clock.epochMillis()
                 })
 
                 params.keyShareRequest.recipients.forEach { userToDeviceMap ->
@@ -109,7 +111,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter
                         content = toDeviceContent.toContent(),
                         senderId = credentials.userId
                 ).also {
-                    it.ageLocalTs = System.currentTimeMillis()
+                    it.ageLocalTs = clock.epochMillis()
                 })
 
                 params.secretShareRequest.recipients.forEach { userToDeviceMap ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
index fd472fe73bc323cadf77f760202aefab4e95b136..113d71d387d40f643a46253273c6937393f8517b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import timber.log.Timber
@@ -63,6 +64,7 @@ internal class SendGossipWorker(
     @Inject lateinit var credentials: Credentials
     @Inject lateinit var messageEncrypter: MessageEncrypter
     @Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -129,7 +131,7 @@ internal class SendGossipWorker(
                 content = toDeviceContent.toContent(),
                 senderId = credentials.userId
         ).also {
-            it.ageLocalTs = System.currentTimeMillis()
+            it.ageLocalTs = clock.epochMillis()
         })
 
         try {
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 f9bcdf2c68091a3c64e9c6df2340b8908bbea3b0..86674b4ac55046bfd9499388e6c0a47973a93f82 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
@@ -26,13 +26,17 @@ import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
 import org.matrix.android.sdk.internal.crypto.RoomDecryptorProvider
 import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryption
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class MegolmSessionDataImporter @Inject constructor(private val olmDevice: MXOlmDevice,
-                                                             private val roomDecryptorProvider: RoomDecryptorProvider,
-                                                             private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-                                                             private val cryptoStore: IMXCryptoStore) {
+internal class MegolmSessionDataImporter @Inject constructor(
+        private val olmDevice: MXOlmDevice,
+        private val roomDecryptorProvider: RoomDecryptorProvider,
+        private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        private val cryptoStore: IMXCryptoStore,
+        private val clock: Clock,
+) {
 
     /**
      * Import a list of megolm session keys.
@@ -47,7 +51,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
     fun handle(megolmSessionsData: List<MegolmSessionData>,
                fromBackup: Boolean,
                progressListener: ProgressListener?): ImportRoomKeysResult {
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
 
         val totalNumbersOfKeys = megolmSessionsData.size
         var lastProgress = 0
@@ -103,7 +107,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
             cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
         }
 
-        val t1 = System.currentTimeMillis()
+        val t1 = clock.epochMillis()
 
         Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
 
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 f0521942304acb1877e17c4c4cd4b6ad96748c17..b31b5e8a645e055e264fae305769db3535101cc8 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
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 import org.matrix.android.sdk.internal.util.convertToUTF8
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 private val loggerTag = LoggerTag("MXMegolmEncryption", LoggerTag.CRYPTO)
@@ -64,7 +65,8 @@ internal class MXMegolmEncryption(
         private val messageEncrypter: MessageEncrypter,
         private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
 ) : IMXEncrypting, IMXGroupEncryption {
 
     // OutboundSessionInfo. Null if we haven't yet started setting one up. Note
@@ -86,11 +88,11 @@ internal class MXMegolmEncryption(
     override suspend fun encryptEventContent(eventContent: Content,
                                              eventType: String,
                                              userIds: List<String>): Content {
-        val ts = System.currentTimeMillis()
+        val ts = clock.epochMillis()
         Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
         val devices = getDevicesInRoom(userIds)
         Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
-        Timber.tag(loggerTag.value).v("encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
+        Timber.tag(loggerTag.value).v("encryptEventContent ${clock.epochMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
         val outboundSession = ensureOutboundSession(devices.allowedDevices)
 
         return encryptContent(outboundSession, eventType, eventContent)
@@ -99,7 +101,7 @@ internal class MXMegolmEncryption(
                     // annoyingly we have to serialize again the saved outbound session to store message index :/
                     // if not we would see duplicate message index errors
                     olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
-                    Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${System.currentTimeMillis() - ts} millis")
+                    Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${clock.epochMillis() - ts} millis")
                 }
     }
 
@@ -125,14 +127,14 @@ internal class MXMegolmEncryption(
     }
 
     override suspend fun preshareKey(userIds: List<String>) {
-        val ts = System.currentTimeMillis()
+        val ts = clock.epochMillis()
         Timber.tag(loggerTag.value).d("preshareKey started in $roomId ...")
         val devices = getDevicesInRoom(userIds)
         val outboundSession = ensureOutboundSession(devices.allowedDevices)
 
         notifyWithheldForSession(devices.withHeldDevices, outboundSession)
 
-        Timber.tag(loggerTag.value).d("preshareKey in $roomId done in  ${System.currentTimeMillis() - ts} millis")
+        Timber.tag(loggerTag.value).d("preshareKey in $roomId done in  ${clock.epochMillis() - ts} millis")
     }
 
     /**
@@ -148,12 +150,14 @@ internal class MXMegolmEncryption(
                 "ed25519" to olmDevice.deviceEd25519Key!!
         )
 
-        olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
-                emptyList(), keysClaimedMap, false)
+        olmDevice.addInboundGroupSession(
+                sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
+                emptyList(), keysClaimedMap, false
+        )
 
         defaultKeysBackupService.maybeBackupKeys()
 
-        return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore))
+        return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore), clock)
     }
 
     /**
@@ -243,12 +247,12 @@ internal class MXMegolmEncryption(
         payload["type"] = EventType.ROOM_KEY
         payload["content"] = submap
 
-        var t0 = System.currentTimeMillis()
+        var t0 = clock.epochMillis()
         Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts")
 
         val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
         Timber.tag(loggerTag.value).v(
-                """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
+                """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${clock.epochMillis() - t0} ms"""
                         .trimMargin()
         )
         val contentMap = MXUsersDevicesMap<Any>()
@@ -301,7 +305,7 @@ internal class MXMegolmEncryption(
         cryptoStore.saveGossipingEvents(gossipingEventBuffer)
 
         if (haveTargets) {
-            t0 = System.currentTimeMillis()
+            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()}")
             val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
@@ -309,7 +313,7 @@ internal class MXMegolmEncryption(
                 withContext(coroutineDispatchers.io) {
                     sendToDeviceTask.execute(sendToDeviceParams)
                 }
-                Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
+                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}>")
@@ -334,7 +338,8 @@ internal class MXMegolmEncryption(
                                           senderKey: String?,
                                           code: WithHeldCode) {
         Timber.tag(loggerTag.value).d("notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
-                " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}")
+                " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}"
+        )
         val withHeldContent = RoomKeyWithHeldContent(
                 roomId = roomId,
                 senderKey = senderKey,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
index 136fdc05f5162d9e3e2ff8e1842eaf52adceec47..4225d604aae52e8cf5dfb07053e5ac5acbe9f105 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class MXMegolmEncryptionFactory @Inject constructor(
@@ -42,7 +43,9 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
         private val messageEncrypter: MessageEncrypter,
         private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope) {
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
+) {
 
     fun create(roomId: String): MXMegolmEncryption {
         return MXMegolmEncryption(
@@ -58,7 +61,8 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
                 messageEncrypter = messageEncrypter,
                 warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository,
                 coroutineDispatchers = coroutineDispatchers,
-                cryptoCoroutineScope = cryptoCoroutineScope
+                cryptoCoroutineScope = cryptoCoroutineScope,
+                clock = clock,
         )
     }
 }
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 091abd49746f1c5e8dd2889e68bd1b261433b1dc..28d925d8fd5cf3690af003798442a82b00da9483 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
@@ -18,21 +18,24 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
 
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 internal class MXOutboundSessionInfo(
         // The id of the session
         val sessionId: String,
         val sharedWithHelper: SharedWithHelper,
+        private val clock: Clock,
         // When the session was created
-        private val creationTime: Long = System.currentTimeMillis()) {
+        private val creationTime: Long = clock.epochMillis(),
+) {
 
     // Number of times this session has been used
     var useCount: Int = 0
 
     fun needsRotation(rotationPeriodMsgs: Int, rotationPeriodMs: Int): Boolean {
         var needsRotation = false
-        val sessionLifetime = System.currentTimeMillis() - creationTime
+        val sessionLifetime = clock.epochMillis() - creationTime
 
         if (useCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) {
             Timber.v("## needsRotation() : Rotating megolm session after $useCount, ${sessionLifetime}ms")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
index 7fdfd5a2876420745209733f323ed2b3a04a8839..c842c5404151adbcce729b5f4fc24f1d1559c81e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
@@ -33,7 +33,7 @@ internal class MXOlmEncryption(
         private val messageEncrypter: MessageEncrypter,
         private val deviceListManager: DeviceListManager,
         private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) :
-    IMXEncrypting {
+        IMXEncrypting {
 
     override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content {
         // pick the list of recipients based on the membership list.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
index 91b6af6fc3ad845e3e9f8119db198b7b9e1f99bd..65bbb0c4125ef7f5a2f50a5fa1d51f10c75cc4c3 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey
 import org.matrix.android.sdk.internal.util.base64ToBase64Url
 import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64
 import org.matrix.android.sdk.internal.util.base64UrlToBase64
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.io.ByteArrayOutputStream
 import java.io.File
@@ -42,8 +43,9 @@ internal object MXEncryptedAttachments {
 
     fun encrypt(clearStream: InputStream,
                 outputFile: File,
+                clock: Clock,
                 progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
         val secureRandom = SecureRandom()
         val initVectorBytes = ByteArray(16) { 0.toByte() }
 
@@ -100,7 +102,7 @@ internal object MXEncryptedAttachments {
                 hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))),
                 v = "v2"
         )
-                .also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
+                .also { Timber.v("Encrypt in ${clock.epochMillis() - t0}ms") }
     }
 
 //    fun cipherInputStream(attachmentStream: InputStream, mimetype: String?): Pair<DigestInputStream, EncryptedFileInfo> {
@@ -159,8 +161,8 @@ internal object MXEncryptedAttachments {
      * @param attachmentStream the attachment stream. Will be closed after this method call.
      * @return the encryption file info
      */
-    fun encryptAttachment(attachmentStream: InputStream): EncryptionResult {
-        val t0 = System.currentTimeMillis()
+    fun encryptAttachment(attachmentStream: InputStream, clock: Clock): EncryptionResult {
+        val t0 = clock.epochMillis()
         val secureRandom = SecureRandom()
 
         // generate a random iv key
@@ -221,7 +223,7 @@ internal object MXEncryptedAttachments {
                 ),
                 encryptedByteArray = byteArrayOutputStream.toByteArray()
         )
-                .also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
+                .also { Timber.v("Encrypt in ${clock.epochMillis() - t0}ms") }
     }
 
     /**
@@ -234,14 +236,16 @@ internal object MXEncryptedAttachments {
      */
     fun decryptAttachment(attachmentStream: InputStream?,
                           elementToDecrypt: ElementToDecrypt?,
-                          outputStream: OutputStream): Boolean {
+                          outputStream: OutputStream,
+                          clock: Clock
+    ): Boolean {
         // sanity checks
         if (null == attachmentStream || elementToDecrypt == null) {
             Timber.e("## decryptAttachment() : null stream")
             return false
         }
 
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
 
         try {
             val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
@@ -279,7 +283,8 @@ internal object MXEncryptedAttachments {
                 return false
             }
 
-            return true.also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") }
+            Timber.v("Decrypt in ${clock.epochMillis() - t0} ms")
+            return true
         } catch (oom: OutOfMemoryError) {
             Timber.e(oom, "## decryptAttachment() failed: OOM")
         } catch (e: Exception) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
index c12879dbeeb7d72221376817a00ae0ac7b85fbd6..4d5b38acbf18fabdd3d3f0537faae556933fe77a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
@@ -26,6 +26,7 @@ import java.util.UUID
 import javax.crypto.Mac
 import javax.crypto.spec.SecretKeySpec
 import kotlin.experimental.xor
+import kotlin.system.measureTimeMillis
 
 private const val SALT_LENGTH = 32
 private const val DEFAULT_ITERATION = 500_000
@@ -91,52 +92,53 @@ internal fun deriveKey(password: String,
                        iterations: Int,
                        progressListener: ProgressListener?): ByteArray {
     // Note: copied and adapted from MXMegolmExportEncryption
-    val t0 = System.currentTimeMillis()
-
     // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
     // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
     // noticed as dklen/hlen
 
-    // dklen = 256
-    // hlen = 512
-    val prf = Mac.getInstance("HmacSHA512")
-
-    prf.init(SecretKeySpec(password.toByteArray(), "HmacSHA512"))
-
     // 256 bits key length
     val dk = ByteArray(32)
-    val uc = ByteArray(64)
 
-    // U1 = PRF(Password, Salt || INT_32_BE(i)) with i goes from 1 to dklen/hlen
-    prf.update(salt.toByteArray())
-    val int32BE = byteArrayOf(0, 0, 0, 1)
-    prf.update(int32BE)
-    prf.doFinal(uc, 0)
+    measureTimeMillis {
+        // dklen = 256
+        // hlen = 512
+        val prf = Mac.getInstance("HmacSHA512")
 
-    // copy to the key
-    System.arraycopy(uc, 0, dk, 0, dk.size)
+        prf.init(SecretKeySpec(password.toByteArray(), "HmacSHA512"))
 
-    var lastProgress = -1
+        val uc = ByteArray(64)
 
-    for (index in 2..iterations) {
-        // Uc = PRF(Password, Uc-1)
-        prf.update(uc)
+        // U1 = PRF(Password, Salt || INT_32_BE(i)) with i goes from 1 to dklen/hlen
+        prf.update(salt.toByteArray())
+        val int32BE = byteArrayOf(0, 0, 0, 1)
+        prf.update(int32BE)
         prf.doFinal(uc, 0)
 
-        // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
-        for (byteIndex in dk.indices) {
-            dk[byteIndex] = dk[byteIndex] xor uc[byteIndex]
-        }
+        // copy to the key
+        System.arraycopy(uc, 0, dk, 0, dk.size)
 
-        val progress = (index + 1) * 100 / iterations
-        if (progress != lastProgress) {
-            lastProgress = progress
-            progressListener?.onProgress(lastProgress, 100)
+        var lastProgress = -1
+
+        for (index in 2..iterations) {
+            // Uc = PRF(Password, Uc-1)
+            prf.update(uc)
+            prf.doFinal(uc, 0)
+
+            // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
+            for (byteIndex in dk.indices) {
+                dk[byteIndex] = dk[byteIndex] xor uc[byteIndex]
+            }
+
+            val progress = (index + 1) * 100 / iterations
+            if (progress != lastProgress) {
+                lastProgress = progress
+                progressListener?.onProgress(lastProgress, 100)
+            }
         }
+    }.also {
+        Timber.v("KeysBackupPassword: deriveKeys() : $iterations in $it ms")
     }
 
-    Timber.v("KeysBackupPassword: deriveKeys() : " + iterations + " in " + (System.currentTimeMillis() - t0) + " ms")
-
     return dk
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
index 927d049eca9c13a325f786b5b881c53b0e720637..d7ce553f39aebfbba1f97fbb4161edc6987bcb31 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
@@ -34,7 +34,7 @@ internal data class OlmSessionWrapper(
     /**
      * Notify that a message has been received on this olm session so that it updates `lastReceivedMessageTs`
      */
-    fun onMessageReceived() {
-        lastReceivedMessageTs = System.currentTimeMillis()
+    fun onMessageReceived(currentTimeMillis: Long) {
+        lastReceivedMessageTs = currentTimeMillis
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
index 19e66635c748812168e67d3d8b4c3e6b454ec39e..8c877593e74424183ce18b0e8510d198a36b688a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
@@ -66,7 +66,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                                      key: SsssKeySpec?,
                                      keyName: String,
                                      keySigner: KeySigner?): SsssKeyCreationInfo {
-        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val bytes = (key as? RawBytesKeySpec)?.privateKey
                     ?: ByteArray(32).also {
                         SecureRandom().nextBytes(it)
@@ -99,7 +99,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                                                    passphrase: String,
                                                    keySigner: KeySigner,
                                                    progressListener: ProgressListener?): SsssKeyCreationInfo {
-        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
 
             val storageKeyContent = SecretStorageKeyContent(
@@ -158,7 +158,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
     }
 
     override suspend fun storeSecret(name: String, secretBase64: String, keys: List<SharedSecretStorageService.KeyRef>) {
-        withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val encryptedContents = HashMap<String, EncryptedSecretContent>()
             keys.forEach {
                 val keyId = it.keyId
@@ -174,7 +174,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                             throw SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")
                         }
                     }
-                    is KeyInfoResult.Error -> throw key.error
+                    is KeyInfoResult.Error   -> throw key.error
                 }
             }
 
@@ -316,7 +316,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         val algorithm = key.keyInfo.content
         if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
             val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
-            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
                 // decrypt from recovery key
                 withOlmDecryption { olmPkDecryption ->
                     olmPkDecryption.setPrivateKey(keySpec.privateKey)
@@ -331,7 +331,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
             }
         } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
             val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
-            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
                 decryptAesHmacSha2(keySpec, name, secretContent)
             }
         } else {
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 99adbbfbae00615d4bb5f05fa051b6413bf53e96..a509315e7af28f04a9f07c2ed585aae6546659b5 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
@@ -97,6 +97,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.extensions.clearWith
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmException
 import org.matrix.olm.OlmOutboundGroupSession
@@ -110,7 +111,8 @@ internal class RealmCryptoStore @Inject constructor(
         @CryptoDatabase private val realmConfiguration: RealmConfiguration,
         private val crossSigningKeysMapper: CrossSigningKeysMapper,
         @UserId private val userId: String,
-        @DeviceId private val deviceId: String?
+        @DeviceId private val deviceId: String?,
+        private val clock: Clock,
 ) : IMXCryptoStore {
 
     /* ==========================================================================================
@@ -307,7 +309,7 @@ internal class RealmCryptoStore @Inject constructor(
                         // Add the device
                         Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
                         val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
-                        newEntity.firstTimeSeenLocalTs = System.currentTimeMillis()
+                        newEntity.firstTimeSeenLocalTs = clock.epochMillis()
                         userEntity.devices.add(newEntity)
                     } else {
                         // Update the device
@@ -715,6 +717,7 @@ internal class RealmCryptoStore @Inject constructor(
         return doWithRealm(realmConfiguration) {
             it.where<OlmSessionEntity>()
                     .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey)
+                    .sort(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Sort.DESCENDING)
                     .findAll()
                     .mapNotNull { sessionEntity ->
                         sessionEntity.sessionId
@@ -791,7 +794,7 @@ internal class RealmCryptoStore @Inject constructor(
 
                 if (outboundGroupSession != null) {
                     val info = realm.createObject(OutboundGroupSessionInfoEntity::class.java).apply {
-                        creationTime = System.currentTimeMillis()
+                        creationTime = clock.epochMillis()
                         putOutboundGroupSession(outboundGroupSession)
                     }
                     entity.outboundSessionInfo = info
@@ -881,7 +884,8 @@ internal class RealmCryptoStore @Inject constructor(
                 try {
                     val key = OlmInboundGroupSessionEntity.createPrimaryKey(
                             olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier(),
-                            olmInboundGroupSessionWrapper.senderKey)
+                            olmInboundGroupSessionWrapper.senderKey
+                    )
 
                     it.where<OlmInboundGroupSessionEntity>()
                             .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
@@ -1056,13 +1060,16 @@ internal class RealmCryptoStore @Inject constructor(
                             localCreationTimestamp = 0
                     )
         }
-        return monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        return monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
     }
 
@@ -1071,13 +1078,16 @@ internal class RealmCryptoStore @Inject constructor(
             realm.where<GossipingEventEntity>().sort(GossipingEventEntityFields.AGE_LOCAL_TS, Sort.DESCENDING)
         }
         val dataSourceFactory = realmDataSourceFactory.map { it.toModel() }
-        val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        val trail = monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
         return trail
     }
@@ -1152,7 +1162,7 @@ internal class RealmCryptoStore @Inject constructor(
 
     override fun saveGossipingEvents(events: List<Event>) {
         monarchy.writeAsync { realm ->
-            val now = System.currentTimeMillis()
+            val now = clock.epochMillis()
             events.forEach { event ->
                 val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
                 val entity = GossipingEventEntity(
@@ -1325,7 +1335,7 @@ internal class RealmCryptoStore @Inject constructor(
                     .findAll()
                     .mapNotNull { entity ->
                         when (entity.type) {
-                            GossipRequestType.KEY    -> {
+                            GossipRequestType.KEY -> {
                                 IncomingRoomKeyRequest(
                                         userId = entity.otherUserId,
                                         deviceId = entity.otherDeviceId,
@@ -1358,7 +1368,7 @@ internal class RealmCryptoStore @Inject constructor(
                 it.otherUserId = request.userId
                 it.requestId = request.requestId ?: ""
                 it.requestState = GossipingRequestState.PENDING
-                it.localCreationTimestamp = ageLocalTS ?: System.currentTimeMillis()
+                it.localCreationTimestamp = ageLocalTS ?: clock.epochMillis()
                 if (request is IncomingSecretShareRequest) {
                     it.type = GossipRequestType.SECRET
                     it.requestedInfoStr = request.secretName
@@ -1379,7 +1389,7 @@ internal class RealmCryptoStore @Inject constructor(
                     it.otherUserId = request.userId
                     it.requestId = request.requestId ?: ""
                     it.requestState = GossipingRequestState.PENDING
-                    it.localCreationTimestamp = request.localCreationTimestamp ?: System.currentTimeMillis()
+                    it.localCreationTimestamp = request.localCreationTimestamp ?: clock.epochMillis()
                     if (request is IncomingSecretShareRequest) {
                         it.type = GossipRequestType.SECRET
                         it.requestedInfoStr = request.secretName
@@ -1535,13 +1545,16 @@ internal class RealmCryptoStore @Inject constructor(
             it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
                     ?: OutgoingRoomKeyRequest(requestBody = null, requestId = "?", recipients = emptyMap(), state = OutgoingGossipingRequestState.CANCELLED)
         }
-        val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        val trail = monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
         return trail
     }
@@ -1706,7 +1719,7 @@ internal class RealmCryptoStore @Inject constructor(
      * So we need to tidy up a bit
      */
     override fun tidyUpDataBase() {
-        val prevWeekTs = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1_000
+        val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000
         doRealmTransaction(realmConfiguration) { realm ->
 
             // Only keep one week history
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 cac649948685b08c3ad76d480e38d5c9e3dd106b..32f24c5d2e7897c92f5017315ee6ada4d7e50cb2 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
@@ -33,10 +33,13 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo013
 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.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration {
+internal class RealmCryptoStoreMigration @Inject constructor(
+        private val clock: Clock,
+) : RealmMigration {
     /**
      * Forces all RealmCryptoStoreMigration instances to be equal
      * Avoids Realm throwing when multiple instances of the migration are set
@@ -59,7 +62,7 @@ internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration
         if (oldVersion < 5) MigrateCryptoTo005(realm).perform()
         if (oldVersion < 6) MigrateCryptoTo006(realm).perform()
         if (oldVersion < 7) MigrateCryptoTo007(realm).perform()
-        if (oldVersion < 8) MigrateCryptoTo008(realm).perform()
+        if (oldVersion < 8) MigrateCryptoTo008(realm, clock).perform()
         if (oldVersion < 9) MigrateCryptoTo009(realm).perform()
         if (oldVersion < 10) MigrateCryptoTo010(realm).perform()
         if (oldVersion < 11) MigrateCryptoTo011(realm).perform()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
index 785e6a04f42a6fe8ee02ce534b8ea3f92c929c83..ad195e6e555ba72a3c72d96a0512540a236b19ef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
@@ -21,8 +21,12 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import org.matrix.android.sdk.internal.util.time.Clock
 
-internal class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) {
+internal class MigrateCryptoTo008(
+        realm: DynamicRealm,
+        private val clock: Clock,
+) : RealmMigrator(realm, 8) {
 
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.create("MyDeviceLastSeenInfoEntity")
@@ -33,7 +37,7 @@ internal class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8)
                 .addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
                 .setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
 
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         realm.schema.get("DeviceInfoEntity")
                 ?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
                 ?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
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 6167314b5a354db61c9ca734b67bf5cce7b64a43..114a596964fa7228ed15a5db8490f164b321a906 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
@@ -31,8 +31,8 @@ internal open class CryptoRoomEntity(
         // a security to ensure that a room will never revert to not encrypted
         // even if a new state event with empty encryption, or state is reset somehow
         var wasEncryptedOnce: Boolean? = false
-        ) :
-    RealmObject() {
+) :
+        RealmObject() {
 
     companion object
 }
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 f330e8822acccc10f40217da6915379a6612f1cc..83671b28d95e387ba48f2b2b9211a4a47c4d5e7d 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
@@ -34,7 +34,7 @@ internal open class OlmInboundGroupSessionEntity(
         var olmInboundGroupSessionData: String? = null,
         // Indicate if the key has been backed up to the homeserver
         var backedUp: Boolean = false) :
-    RealmObject() {
+        RealmObject() {
 
     fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
         return try {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
index 0b69311c57dea2dd6b4971532b2ebc66a5bc3a7d..1a637d76c4ac8c9dc81fd1e924d4ee4196a39e45 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
@@ -30,7 +30,7 @@ internal open class OlmSessionEntity(@PrimaryKey var primaryKey: String = "",
                                      var deviceKey: String? = null,
                                      var olmSessionData: String? = null,
                                      var lastReceivedMessageTs: Long = 0) :
-    RealmObject() {
+        RealmObject() {
 
     fun getOlmSession(): OlmSession? {
         return deserializeFromRealm(olmSessionData)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
index ca04bac5d5d65ca8f72fef831c4fefa8024f486e..0a77d33accc9710bacbd3233cecc4b4146d72e58 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.tasks
 
 import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.api.session.uia.UiaResult
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
@@ -47,13 +48,13 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
             }
         } catch (throwable: Throwable) {
             if (params.userInteractiveAuthInterceptor == null ||
-                    !handleUIA(
+                    handleUIA(
                             failure = throwable,
                             interceptor = params.userInteractiveAuthInterceptor,
                             retryBlock = { authUpdate ->
                                 execute(params.copy(userAuthParam = authUpdate))
                             }
-                    )
+                    ) != UiaResult.SUCCESS
             ) {
                 Timber.d("## UIA: propagate failure")
                 throw throwable
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
index eefdd25044eeef01cd730ac0bb091ddc86b774a7..53190c43ff8105a7c21a873a3b60bd7246f152ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
@@ -20,6 +20,7 @@ import dagger.Lazy
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
 import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage
+import org.matrix.android.sdk.api.session.uia.UiaResult
 import org.matrix.android.sdk.api.util.toBase64NoPadding
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
@@ -126,13 +127,13 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
                 uploadSigningKeysTask.execute(uploadSigningKeysParams)
             } catch (failure: Throwable) {
                 if (params.interactiveAuthInterceptor == null ||
-                        !handleUIA(
+                        handleUIA(
                                 failure = failure,
                                 interceptor = params.interactiveAuthInterceptor,
                                 retryBlock = { authUpdate ->
                                     uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
                                 }
-                        )
+                        ) != UiaResult.SUCCESS
                 ) {
                     Timber.d("## UIA: propagate failure")
                     throw failure
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
index e203f03b06ff7329bced3394133aa67653b0250e..e0d912a9a62a1e544b3bc8273ec65e736871fac2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
@@ -123,7 +123,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
 //        val requestMessage = KeyVerificationRequest(
 //                fromDevice = session.sessionParams.deviceId ?: "",
 //                methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
-//                timestamp = System.currentTimeMillis().toInt(),
+//                timestamp = clock.epochMillis().toInt(),
 //                transactionId = transactionId
 //        )
 //
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
index 28bf1d70f7fb09f867916430d93214baae04123c..d62ca5503d953aac844e79b5369213dd9dc92a39 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
@@ -84,6 +84,7 @@ import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.UUID
 import javax.inject.Inject
@@ -104,7 +105,8 @@ internal class DefaultVerificationService @Inject constructor(
         private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
         private val crossSigningService: CrossSigningService,
         private val cryptoCoroutineScope: CoroutineScope,
-        private val taskExecutor: TaskExecutor
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : DefaultVerificationTransaction.Listener, VerificationService {
 
     private val uiHandler = Handler(Looper.getMainLooper())
@@ -261,9 +263,11 @@ internal class DefaultVerificationService @Inject constructor(
     }
 
     override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
-        setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
+        setDeviceVerificationAction.handle(
+                DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
                 userId,
-                deviceID)
+                deviceID
+        )
 
         listeners.forEach {
             try {
@@ -313,7 +317,7 @@ internal class DefaultVerificationService @Inject constructor(
         val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
 
         val pendingVerificationRequest = PendingVerificationRequest(
-                ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
+                ageLocalTs = event.ageLocalTs ?: clock.epochMillis(),
                 isIncoming = true,
                 otherUserId = senderId, // requestInfo.toUserId,
                 roomId = null,
@@ -352,7 +356,7 @@ internal class DefaultVerificationService @Inject constructor(
         val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
 
         val pendingVerificationRequest = PendingVerificationRequest(
-                ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
+                ageLocalTs = event.ageLocalTs ?: clock.epochMillis(),
                 isIncoming = true,
                 otherUserId = senderId, // requestInfo.toUserId,
                 roomId = event.roomId,
@@ -552,7 +556,8 @@ internal class DefaultVerificationService @Inject constructor(
                             myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                             startReq.transactionId,
                             otherUserId,
-                            autoAccept).also { txConfigure(it) }
+                            autoAccept
+                    ).also { txConfigure(it) }
                     addTransaction(tx)
                     tx.onVerificationStart(startReq)
                     return null
@@ -644,9 +649,11 @@ internal class DefaultVerificationService @Inject constructor(
 
         if (existingRequest != null) {
             // Mark this request as cancelled
-            updatePendingRequest(existingRequest.copy(
-                    cancelConclusion = safeValueOf(cancelReq.code)
-            ))
+            updatePendingRequest(
+                    existingRequest.copy(
+                            cancelConclusion = safeValueOf(cancelReq.code)
+                    )
+            )
         }
 
         existingTransaction?.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false)
@@ -809,15 +816,19 @@ internal class DefaultVerificationService @Inject constructor(
                             ?.let { vt ->
                                 val otherDeviceId = vt.otherDeviceId
                                 if (!crossSigningService.canCrossSign()) {
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(MASTER_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
+                                    outgoingGossipingRequestManager.sendSecretShareRequest(
+                                            MASTER_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))
+                                    )
+                                    outgoingGossipingRequestManager.sendSecretShareRequest(
+                                            SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))
+                                    )
+                                    outgoingGossipingRequestManager.sendSecretShareRequest(
+                                            USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))
+                                    )
                                 }
-                                outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                        ?: "*")))
+                                outgoingGossipingRequestManager.sendSecretShareRequest(
+                                        KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))
+                                )
                             }
         }
     }
@@ -917,16 +928,19 @@ internal class DefaultVerificationService @Inject constructor(
                     qrCodeData = qrCodeData,
                     userId = userId,
                     deviceId = deviceId ?: "",
-                    isIncoming = false)
+                    isIncoming = false
+            )
 
             tx.transport = transportCreator.invoke(tx)
 
             addTransaction(tx)
         }
 
-        updatePendingRequest(existingRequest.copy(
-                readyInfo = readyReq
-        ))
+        updatePendingRequest(
+                existingRequest.copy(
+                        readyInfo = readyReq
+                )
+        )
     }
 
     private fun createQrCodeData(requestId: String?, otherUserId: String, otherDeviceId: String?): QrCodeData? {
@@ -1115,7 +1129,8 @@ internal class DefaultVerificationService @Inject constructor(
                     myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                     txID,
                     otherUserId,
-                    otherDeviceId)
+                    otherDeviceId
+            )
             tx.transport = verificationTransportToDeviceFactory.createTransport(tx)
             addTransaction(tx)
 
@@ -1150,7 +1165,7 @@ internal class DefaultVerificationService @Inject constructor(
         val validLocalId = localId ?: LocalEcho.createLocalEchoId()
 
         val verificationRequest = PendingVerificationRequest(
-                ageLocalTs = System.currentTimeMillis(),
+                ageLocalTs = clock.epochMillis(),
                 isIncoming = false,
                 roomId = roomId,
                 localId = validLocalId,
@@ -1175,11 +1190,13 @@ internal class DefaultVerificationService @Inject constructor(
 
         transport.sendVerificationRequest(methodValues, validLocalId, otherUserId, roomId, null) { syncedId, info ->
             // We need to update with the syncedID
-            updatePendingRequest(verificationRequest.copy(
-                    transactionId = syncedId,
-                    // localId stays different
-                    requestInfo = info
-            ))
+            updatePendingRequest(
+                    verificationRequest.copy(
+                            transactionId = syncedId,
+                            // localId stays different
+                            requestInfo = info
+                    )
+            )
         }
 
         requestsForUser.add(verificationRequest)
@@ -1228,7 +1245,7 @@ internal class DefaultVerificationService @Inject constructor(
 
         val verificationRequest = PendingVerificationRequest(
                 transactionId = localId,
-                ageLocalTs = System.currentTimeMillis(),
+                ageLocalTs = clock.epochMillis(),
                 isIncoming = false,
                 roomId = null,
                 localId = localId,
@@ -1254,10 +1271,12 @@ internal class DefaultVerificationService @Inject constructor(
 
         transport.sendVerificationRequest(methodValues, localId, otherUserId, null, targetDevices) { _, info ->
             // Nothing special to do in to device mode
-            updatePendingRequest(verificationRequest.copy(
-                    // localId stays different
-                    requestInfo = info
-            ))
+            updatePendingRequest(
+                    verificationRequest.copy(
+                            // localId stays different
+                            requestInfo = info
+                    )
+            )
         }
 
         requestsForUser.add(verificationRequest)
@@ -1271,9 +1290,11 @@ internal class DefaultVerificationService @Inject constructor(
                 .cancelTransaction(transactionId, otherUserId, null, CancelCode.User)
 
         getExistingVerificationRequest(otherUserId, transactionId)?.let {
-            updatePendingRequest(it.copy(
-                    cancelConclusion = CancelCode.User
-            ))
+            updatePendingRequest(
+                    it.copy(
+                            cancelConclusion = CancelCode.User
+                    )
+            )
         }
     }
 
@@ -1307,7 +1328,8 @@ internal class DefaultVerificationService @Inject constructor(
                     myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                     transactionId,
                     otherUserId,
-                    otherDeviceId)
+                    otherDeviceId
+            )
             tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx)
             addTransaction(tx)
 
@@ -1333,7 +1355,8 @@ internal class DefaultVerificationService @Inject constructor(
                     otherUserId,
                     existingRequest.requestInfo?.fromDevice ?: "",
                     existingRequest.requestInfo?.methods,
-                    methods) {
+                    methods
+            ) {
                 verificationTransportRoomMessageFactory.createTransport(roomId, it)
             }
             if (methods.isNullOrEmpty()) {
@@ -1343,7 +1366,8 @@ internal class DefaultVerificationService @Inject constructor(
             }
             // TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
             val readyMsg = transport.createReady(transactionId, deviceId ?: "", computedMethods)
-            transport.sendToOther(EventType.KEY_VERIFICATION_READY,
+            transport.sendToOther(
+                    EventType.KEY_VERIFICATION_READY,
                     readyMsg,
                     VerificationTxState.None,
                     CancelCode.User,
@@ -1372,7 +1396,8 @@ internal class DefaultVerificationService @Inject constructor(
                     otherUserId,
                     existingRequest.requestInfo?.fromDevice ?: "",
                     existingRequest.requestInfo?.methods,
-                    methods) {
+                    methods
+            ) {
                 verificationTransportToDeviceFactory.createTransport(it)
             }
             if (methods.isNullOrEmpty()) {
@@ -1446,7 +1471,8 @@ internal class DefaultVerificationService @Inject constructor(
                         qrCodeData = qrCodeData,
                         userId = userId,
                         deviceId = deviceId ?: "",
-                        isIncoming = false)
+                        isIncoming = false
+                )
 
                 tx.transport = transportCreator.invoke(tx)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
index a763c05e07696f6c20ae584214b3930229db840a..0a175ae3ca22a971dab0c84119fde3789b18aa60 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
@@ -35,7 +35,7 @@ import javax.inject.Inject
  * Possible next worker    : None
  */
 internal class SendVerificationMessageWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
index 52166761abbdf02b43a0e7767676a6adcf3781e2..ec4e1aa65cb58ac337f8345286ebb16723c515f6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
@@ -34,11 +34,13 @@ import org.matrix.android.sdk.internal.database.model.EventInsertType
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class VerificationMessageProcessor @Inject constructor(
         private val eventDecryptor: EventDecryptor,
+        private val clock: Clock,
         private val verificationService: DefaultVerificationService,
         @UserId private val userId: String,
         @DeviceId private val deviceId: String?
@@ -71,8 +73,7 @@ internal class VerificationMessageProcessor @Inject constructor(
         // If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
         // the message should be ignored by the receiver.
 
-        if (!VerificationService.isValidRequest(event.ageLocalTs
-                        ?: event.originServerTs)) return Unit.also {
+        if (!VerificationService.isValidRequest(event.ageLocalTs ?: event.originServerTs, clock.epochMillis())) return Unit.also {
             Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated")
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
index bd1186908c4cdcd70a802700d14bc2c005d89cdd..325a6f0ba2daef51f2dbc5914a1bce98d31de627 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
@@ -47,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_REC
 import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import timber.log.Timber
@@ -61,7 +62,8 @@ internal class VerificationTransportRoomMessage(
         private val roomId: String,
         private val localEchoEventFactory: LocalEchoEventFactory,
         private val tx: DefaultVerificationTransaction?,
-        private val coroutineScope: CoroutineScope
+        private val coroutineScope: CoroutineScope,
+        private val clock: Clock,
 ) : VerificationTransport {
 
     override fun <T> sendToOther(type: String,
@@ -77,10 +79,12 @@ internal class VerificationTransportRoomMessage(
                 content = verificationInfo.toEventContent()!!
         )
 
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
+        val workerParams = WorkerParamsFactory.toData(
+                SendVerificationMessageWorker.Params(
+                        sessionId = sessionId,
+                        eventId = event.eventId ?: ""
+                )
+        )
         val enqueueInfo = enqueueSendWork(workerParams)
 
         // I cannot just listen to the given work request, because when used in a uniqueWork,
@@ -155,7 +159,7 @@ internal class VerificationTransportRoomMessage(
                 transactionId = "",
                 fromDevice = userDeviceId ?: "",
                 methods = supportedMethods,
-                timestamp = System.currentTimeMillis()
+                timestamp = clock.epochMillis()
         )
 
         val info = MessageVerificationRequestContent(
@@ -175,10 +179,12 @@ internal class VerificationTransportRoomMessage(
                 content
         )
 
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
+        val workerParams = WorkerParamsFactory.toData(
+                SendVerificationMessageWorker.Params(
+                        sessionId = sessionId,
+                        eventId = event.eventId ?: ""
+                )
+        )
 
         val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
                 .setConstraints(WorkManagerProvider.workConstraints)
@@ -230,10 +236,12 @@ internal class VerificationTransportRoomMessage(
                 roomId = roomId,
                 content = MessageVerificationCancelContent.create(transactionId, code).toContent()
         )
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
+        val workerParams = WorkerParamsFactory.toData(
+                SendVerificationMessageWorker.Params(
+                        sessionId = sessionId,
+                        eventId = event.eventId ?: ""
+                )
+        )
         enqueueSendWork(workerParams)
     }
 
@@ -250,10 +258,12 @@ internal class VerificationTransportRoomMessage(
                         )
                 ).toContent()
         )
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
+        val workerParams = WorkerParamsFactory.toData(
+                SendVerificationMessageWorker.Params(
+                        sessionId = sessionId,
+                        eventId = event.eventId ?: ""
+                )
+        )
         val enqueueInfo = enqueueSendWork(workerParams)
 
         val workLiveData = workManagerProvider.workManager
@@ -296,13 +306,13 @@ internal class VerificationTransportRoomMessage(
                               messageAuthenticationCode: String,
                               shortAuthenticationStrings: List<String>): VerificationInfoAccept =
             MessageVerificationAcceptContent.create(
-            tid,
-            keyAgreementProtocol,
-            hash,
-            commitment,
-            messageAuthenticationCode,
-            shortAuthenticationStrings
-    )
+                    tid,
+                    keyAgreementProtocol,
+                    hash,
+                    commitment,
+                    messageAuthenticationCode,
+                    shortAuthenticationStrings
+            )
 
     override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey)
 
@@ -361,7 +371,7 @@ internal class VerificationTransportRoomMessage(
     private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
         return Event(
                 roomId = roomId,
-                originServerTs = System.currentTimeMillis(),
+                originServerTs = clock.epochMillis(),
                 senderId = userId,
                 eventId = localId,
                 type = type,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
index f89127273b8d0ced5ada3d5277155bf986290425..b1b7ad7a98eb34cbf85ff214e5cef255d271ec2b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class VerificationTransportRoomMessageFactory @Inject constructor(
@@ -33,17 +34,21 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
         @DeviceId
         private val deviceId: String?,
         private val localEchoEventFactory: LocalEchoEventFactory,
-        private val taskExecutor: TaskExecutor
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) {
 
     fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
-        return VerificationTransportRoomMessage(workManagerProvider,
+        return VerificationTransportRoomMessage(
+                workManagerProvider,
                 sessionId,
                 userId,
                 deviceId,
                 roomId,
                 localEchoEventFactory,
                 tx,
-                taskExecutor.executorScope)
+                taskExecutor.executorScope,
+                clock
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
index 40deda2745531dbab494a28a32e8c93a1a6c3f19..bc24ef2966ef5e6b9549517568b973430328d58b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
@@ -35,13 +35,16 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
+// TODO var could be val
 internal class VerificationTransportToDevice(
         private var tx: DefaultVerificationTransaction?,
         private var sendToDeviceTask: SendToDeviceTask,
         private val myDeviceId: String?,
-        private var taskExecutor: TaskExecutor
+        private var taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : VerificationTransport {
 
     override fun sendVerificationRequest(supportedMethods: List<String>,
@@ -56,7 +59,7 @@ internal class VerificationTransportToDevice(
                 transactionId = localId,
                 fromDevice = myDeviceId ?: "",
                 methods = supportedMethods,
-                timestamp = System.currentTimeMillis()
+                timestamp = clock.epochMillis()
         )
         val keyReq = KeyVerificationRequest(
                 fromDevice = validKeyReq.fromDevice,
@@ -201,7 +204,8 @@ internal class VerificationTransportToDevice(
             hash,
             commitment,
             messageAuthenticationCode,
-            shortAuthenticationStrings)
+            shortAuthenticationStrings
+    )
 
     override fun createKey(tid: String, pubKey: String): VerificationInfoKey = KeyVerificationKey.create(tid, pubKey)
 
@@ -221,7 +225,8 @@ internal class VerificationTransportToDevice(
                 hashes,
                 messageAuthenticationCodes,
                 shortAuthenticationStrings,
-                null)
+                null
+        )
     }
 
     override fun createStartForQrCode(fromDevice: String,
@@ -235,7 +240,8 @@ internal class VerificationTransportToDevice(
                 null,
                 null,
                 null,
-                sharedSecret)
+                sharedSecret
+        )
     }
 
     override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
index e9a2c65ef753323b04414257d5cd3275a5ad7b2d..312d911822a1a28987e70943e7da74dfa726a328 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
@@ -19,14 +19,17 @@ package org.matrix.android.sdk.internal.crypto.verification
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class VerificationTransportToDeviceFactory @Inject constructor(
         private val sendToDeviceTask: SendToDeviceTask,
         @DeviceId val myDeviceId: String?,
-        private val taskExecutor: TaskExecutor) {
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
+) {
 
     fun createTransport(tx: DefaultVerificationTransaction?): VerificationTransportToDevice {
-        return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor)
+        return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor, clock)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
index 315d77d932e3733a7859ec3531c5086ea0746484..7d263f19372f81e9dc2b3513000cde37191d9790 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import timber.log.Timber
+import kotlin.system.measureTimeMillis
 
 internal fun <T> CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: suspend (realm: Realm) -> T) {
     asyncTransaction(monarchy.realmConfiguration, transaction)
@@ -41,13 +42,13 @@ internal suspend fun <T> awaitTransaction(config: RealmConfiguration, transactio
             bgRealm.beginTransaction()
             val result: T
             try {
-                val start = System.currentTimeMillis()
-                result = transaction(bgRealm)
-                if (isActive) {
-                    bgRealm.commitTransaction()
-                    val end = System.currentTimeMillis()
-                    val time = end - start
-                    Timber.v("Execute transaction in $time millis")
+                measureTimeMillis {
+                    result = transaction(bgRealm)
+                    if (isActive) {
+                        bgRealm.commitTransaction()
+                    }
+                }.also {
+                    Timber.v("Execute transaction in $it millis")
                 }
             } finally {
                 if (bgRealm.isInTransaction) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
index 115025cc7d88ea5efe9fa71d721bf059b92a49ad..751992fa7f8839c614dd11a2eeb5c219679ef66f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
@@ -19,6 +19,9 @@ package org.matrix.android.sdk.internal.database
 import com.zhuinden.monarchy.Monarchy
 import io.realm.RealmConfiguration
 import io.realm.RealmResults
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.EventEntity
@@ -32,16 +35,28 @@ import javax.inject.Inject
 
 internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
                                                            private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>) :
-    RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
+        RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
 
     override val query = Monarchy.Query {
         it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true)
     }
 
+    private val onResultsChangedFlow = MutableSharedFlow<RealmResults<EventInsertEntity>>()
+
+    init {
+        onResultsChangedFlow
+                .onEach { handleChange(it) }
+                .launchIn(observerScope)
+    }
+
     override fun onChange(results: RealmResults<EventInsertEntity>) {
         if (!results.isLoaded || results.isEmpty()) {
             return
         }
+        observerScope.launch { onResultsChangedFlow.emit(results) }
+    }
+
+    private suspend fun handleChange(results: RealmResults<EventInsertEntity>) {
         val idsToDeleteAfterProcess = ArrayList<String>()
         val filteredEvents = ArrayList<EventInsertEntity>(results.size)
         Timber.v("EventInsertEntity updated with ${results.size} results in db")
@@ -58,30 +73,29 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
             }
             idsToDeleteAfterProcess.add(it.eventId)
         }
-        observerScope.launch {
-            awaitTransaction(realmConfiguration) { realm ->
-                Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
-                filteredEvents.forEach { eventInsert ->
-                    val eventId = eventInsert.eventId
-                    val event = EventEntity.where(realm, eventId).findFirst()
-                    if (event == null) {
-                        Timber.v("Event $eventId not found")
-                        return@forEach
-                    }
-                    val domainEvent = event.asDomain()
-                    processors.filter {
-                        it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
-                    }.forEach {
-                        it.process(realm, domainEvent)
-                    }
+
+        awaitTransaction(realmConfiguration) { realm ->
+            Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
+            filteredEvents.forEach { eventInsert ->
+                val eventId = eventInsert.eventId
+                val event = EventEntity.where(realm, eventId).findFirst()
+                if (event == null) {
+                    Timber.v("Event $eventId not found")
+                    return@forEach
+                }
+                val domainEvent = event.asDomain()
+                processors.filter {
+                    it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
+                }.forEach {
+                    it.process(realm, domainEvent)
                 }
-                realm.where(EventInsertEntity::class.java)
-                        .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
-                        .findAll()
-                        .deleteAllFromRealm()
             }
-            processors.forEach { it.onPostProcess() }
+            realm.where(EventInsertEntity::class.java)
+                    .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
+                    .findAll()
+                    .deleteAllFromRealm()
         }
+        processors.forEach { it.onPostProcess() }
     }
 
     private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
index 50eb086f9ab47b578294b20ebfe590b6e82c938d..f2f88e216bcc98e7f67d9b564e1c913e1cc4e193 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
@@ -35,7 +35,7 @@ import java.util.concurrent.atomic.AtomicReference
 internal interface LiveEntityObserver : SessionLifecycleObserver
 
 internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmConfiguration: RealmConfiguration) :
-    LiveEntityObserver, RealmChangeListener<RealmResults<T>> {
+        LiveEntityObserver, RealmChangeListener<RealmResults<T>> {
 
     private companion object {
         val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
index 8c62c345d0cc75336077a06d722462ae2d9484ce..e5b59155902fae23cfb7111dfd370c0a22c6ec1a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
@@ -33,7 +33,7 @@ import kotlin.concurrent.getOrSet
  */
 @SessionScope
 internal class RealmSessionProvider @Inject constructor(@SessionDatabase private val monarchy: Monarchy) :
-    SessionLifecycleObserver {
+        SessionLifecycleObserver {
 
     private val realmThreadLocal = ThreadLocal<Realm>()
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index a57397dad5f10595c3c4fa72d1e32ced108f73e2..04a6e83ea17723fee7b5a6ff7aa8efaf9f12e31a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -44,6 +44,8 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028
 import org.matrix.android.sdk.internal.util.Normalizer
 import timber.log.Timber
 import javax.inject.Inject
@@ -58,7 +60,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
     override fun equals(other: Any?) = other is RealmSessionStoreMigration
     override fun hashCode() = 1000
 
-    val schemaVersion = 26L
+    val schemaVersion = 28L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
@@ -89,5 +91,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion < 24) MigrateSessionTo024(realm).perform()
         if (oldVersion < 25) MigrateSessionTo025(realm).perform()
         if (oldVersion < 26) MigrateSessionTo026(realm).perform()
+        if (oldVersion < 27) MigrateSessionTo027(realm).perform()
+        if (oldVersion < 28) MigrateSessionTo028(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
index 24de26eeea9e701268181671fb0e35f745536686..d052a7dea497660a8dd83bb7ff9a530d4bf44351 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
@@ -20,6 +20,7 @@ import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.Sort
 import io.realm.kotlin.createObject
+import kotlinx.coroutines.runBlocking
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
@@ -127,7 +128,7 @@ private fun EventEntity.toTimelineEventEntity(roomMemberContentsByUser: HashMap<
     return timelineEventEntity
 }
 
-internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
+internal fun ThreadSummaryEntity.Companion.createOrUpdate(
         threadSummaryType: ThreadSummaryUpdateType,
         realm: Realm,
         roomId: String,
@@ -136,7 +137,8 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
         roomMemberContentsByUser: HashMap<String, RoomMemberContent?>,
         roomEntity: RoomEntity,
         userId: String,
-        cryptoService: CryptoService? = null
+        cryptoService: CryptoService? = null,
+        currentTimeMillis: Long,
 ) {
     when (threadSummaryType) {
         ThreadSummaryUpdateType.REPLACE -> {
@@ -152,11 +154,19 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
                 Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ")
             }
 
-            val rootThreadEventEntity = createEventEntity(roomId, rootThreadEvent, realm).also {
-                decryptIfNeeded(cryptoService, it, roomId)
+            val rootThreadEventEntity = createEventEntity(realm, roomId, rootThreadEvent, currentTimeMillis).also {
+                try {
+                    decryptIfNeeded(cryptoService, it, roomId)
+                } catch (e: InterruptedException) {
+                    Timber.i("Decryption got interrupted")
+                }
             }
-            val latestThreadEventEntity = createLatestEventEntity(roomId, rootThreadEvent, roomMemberContentsByUser, realm)?.also {
-                decryptIfNeeded(cryptoService, it, roomId)
+            val latestThreadEventEntity = createLatestEventEntity(realm, roomId, rootThreadEvent, roomMemberContentsByUser, currentTimeMillis)?.also {
+                try {
+                    decryptIfNeeded(cryptoService, it, roomId)
+                } catch (e: InterruptedException) {
+                    Timber.i("Decryption got interrupted")
+                }
             }
             val isUserParticipating = rootThreadEvent.unsignedData.relations.latestThread.isUserParticipating == true || rootThreadEvent.senderId == userId
             roomMemberContentsByUser.addSenderState(realm, roomId, rootThreadEvent.senderId)
@@ -204,14 +214,15 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
     }
 }
 
-private suspend fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
+private fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
     cryptoService ?: return
     val event = eventEntity.asDomain()
     if (event.isEncrypted() && event.mxDecryptionResult == null && event.eventId != null) {
         try {
             Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}")
             // Event from sync does not have roomId, so add it to the event first
-            val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+            // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
+            val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
             event.mxDecryptionResult = OlmDecryptionResult(
                     payload = result.clearEvent,
                     senderKey = result.senderCurve25519Key,
@@ -258,8 +269,8 @@ private fun HashMap<String, RoomMemberContent?>.addSenderState(realm: Realm, roo
 /**
  * Create an EventEntity for the root thread event or get an existing one
  */
-private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity {
-    val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity {
+    val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it }
     return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
 }
 
@@ -268,15 +279,17 @@ private fun createEventEntity(roomId: String, event: Event, realm: Realm): Event
  * state
  */
 private fun createLatestEventEntity(
+        realm: Realm,
         roomId: String,
         rootThreadEvent: Event,
         roomMemberContentsByUser: HashMap<String, RoomMemberContent?>,
-        realm: Realm): EventEntity? {
+        currentTimeMillis: Long,
+): EventEntity? {
     return getLatestEvent(rootThreadEvent)?.let {
         it.senderId?.let { senderId ->
             roomMemberContentsByUser.addSenderState(realm, roomId, senderId)
         }
-        createEventEntity(roomId, it, realm)
+        createEventEntity(realm, roomId, it, currentTimeMillis)
     }
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
index ea508731b1f6200bebc6c784a552d0023be3da58..8a5d08cd30a7c073ae2f6a1b133a61d9e0bea21e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
@@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database.helper
 import io.realm.Realm
 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.where
 
 internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
-    val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
+    val currentIdNum = TimelineEventEntity.where(realm).max(TimelineEventEntityFields.LOCAL_ID)
     return if (currentIdNum == null) {
         1
     } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
index 4a26b4c4bf940c94c7b9d25037cefc15d3d81899..c747ad334fee19ff858b40b69edfe901149b6236 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
 internal object EventAnnotationsSummaryMapper {
     fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
         return EventAnnotationsSummary(
-                eventId = annotationsSummary.eventId,
                 reactionsSummary = annotationsSummary.reactionsSummary.toList().map {
                     ReactionAggregatedSummary(
                             it.key,
@@ -50,7 +49,6 @@ internal object EventAnnotationsSummaryMapper {
                         },
                 referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
                     ReferencesAggregatedSummary(
-                            it.eventId,
                             ContentMapper.map(it.content),
                             it.sourceEvents.toList(),
                             it.sourceLocalEcho.toList()
@@ -58,8 +56,10 @@ internal object EventAnnotationsSummaryMapper {
                 },
                 pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
                     PollResponseAggregatedSummaryEntityMapper.map(it)
+                },
+                liveLocationShareAggregatedSummary = annotationsSummary.liveLocationShareAggregatedSummary?.let {
+                    LiveLocationShareAggregatedSummaryMapper.map(it)
                 }
-
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
index 3083df062e622ce2599a1ef9f3b1ae8c08a7dc46..bc7d40bf6f1dcec8977063f41d4b92197d8ce9aa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
@@ -30,13 +30,14 @@ import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.internal.database.model.EventEntity
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import timber.log.Timber
+import kotlin.random.Random
 
 internal object EventMapper {
 
     fun map(event: Event, roomId: String): EventEntity {
         val eventEntity = EventEntity()
         // TODO change this as we shouldn't use event everywhere
-        eventEntity.eventId = event.eventId ?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}"
+        eventEntity.eventId = event.eventId ?: "$$roomId-${Random.nextLong()}-${event.hashCode()}"
         eventEntity.roomId = event.roomId ?: roomId
         eventEntity.content = ContentMapper.map(event.content)
         eventEntity.prevContent = ContentMapper.map(event.resolvedPrevContent())
@@ -126,7 +127,10 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
     return EventMapper.map(this, castJsonNumbers)
 }
 
-internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long?, contentToInject: String? = null): EventEntity {
+internal fun Event.toEntity(roomId: String,
+                            sendState: SendState,
+                            ageLocalTs: Long?,
+                            contentToInject: String? = null): EventEntity {
     return EventMapper.map(this, roomId).apply {
         this.sendState = sendState
         this.ageLocalTs = ageLocalTs
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..71b36f88bd8f30a60f01c2e8655e094850a80de4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.mapper
+
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
+
+internal object LiveLocationShareAggregatedSummaryMapper {
+
+    fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
+        return LiveLocationShareAggregatedSummary(
+                isActive = entity.isActive,
+                endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis,
+                lastLocationDataContent = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>()
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
index 5c0a2ba9021ca6f5b120d240c309205a2f678daa..6521bf62d97cb792cf89335d3f7828b3044f9a79 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
@@ -16,7 +16,7 @@
 
 package org.matrix.android.sdk.internal.database.mapper
 
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
 import org.matrix.android.sdk.internal.database.model.PushConditionEntity
 
 internal object PushConditionMapper {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
index 12eff8efa1b1442a5d19b0865388f88d4c36b8a2..0b07754126277ea59eb78b7f45fbbaa721f79848 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
@@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.database.mapper
 
 import com.squareup.moshi.Types
 import io.realm.RealmList
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import timber.log.Timber
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
index d63ef62889b71db469b79e3c2eb1b460b40afe31..754a66bb4b9de9059aa0fadc98e8c010a57acdd8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
@@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.util.Normalizer
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
 internal class MigrateSessionTo019(realm: DynamicRealm,
-                          private val normalizer: Normalizer) : RealmMigrator(realm, 19) {
+                                   private val normalizer: Normalizer) : RealmMigrator(realm, 19) {
 
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.get("RoomSummaryEntity")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fdd8c46d027ae4b4f6654e6d5799df1d3c7e115e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import io.realm.FieldAttribute
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+/**
+ * Migrating to:
+ * Live location sharing aggregated summary
+ */
+internal class MigrateSessionTo027(realm: DynamicRealm) : RealmMigrator(realm, 27) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        val liveLocationSummaryEntity = realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
+                ?: realm.schema.create("LiveLocationShareAggregatedSummaryEntity")
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, String::class.java, FieldAttribute.REQUIRED)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, String::class.java, FieldAttribute.REQUIRED)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, Boolean::class.java)
+                        .setNullable(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, Long::class.java)
+                        .setNullable(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, true)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT, String::class.java)
+                ?: return
+
+        realm.schema.get("EventAnnotationsSummaryEntity")
+                ?.addRealmObjectField(EventAnnotationsSummaryEntityFields.LIVE_LOCATION_SHARE_AGGREGATED_SUMMARY.`$`, liveLocationSummaryEntity)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1d0c638d7b8fc902a4ea192aebbb6dcc6b8e6b98
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+/**
+ * Migrating to:
+ * Live location sharing aggregated summary
+ */
+internal class MigrateSessionTo028(realm: DynamicRealm) : RealmMigrator(realm, 28) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
+                ?.takeIf { !it.hasPrimaryKey() }
+                ?.addPrimaryKey(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
index 88eb821aa9d9088a2241a30d0309cc30a3983dcf..822bc1bd8f45c9e5f0326a61371b2e073af4354d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
@@ -24,19 +24,20 @@ import io.realm.annotations.LinkingObjects
 import org.matrix.android.sdk.internal.extensions.assertIsManaged
 import org.matrix.android.sdk.internal.extensions.clearWith
 
-internal open class ChunkEntity(@Index var prevToken: String? = null,
+internal open class ChunkEntity(
+        @Index var prevToken: String? = null,
         // Because of gaps we can have several chunks with nextToken == null
-                                @Index var nextToken: String? = null,
-                                var prevChunk: ChunkEntity? = null,
-                                var nextChunk: ChunkEntity? = null,
-                                var stateEvents: RealmList<EventEntity> = RealmList(),
-                                var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
+        @Index var nextToken: String? = null,
+        var prevChunk: ChunkEntity? = null,
+        var nextChunk: ChunkEntity? = null,
+        var stateEvents: RealmList<EventEntity> = RealmList(),
+        var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
         // Only one chunk will have isLastForward == true
-                                @Index var isLastForward: Boolean = false,
-                                @Index var isLastBackward: Boolean = false,
+        @Index var isLastForward: Boolean = false,
+        @Index var isLastBackward: Boolean = false,
         // Threads
-                                @Index var rootThreadEventId: String? = null,
-                                @Index var isLastForwardThread: Boolean = false,
+        @Index var rootThreadEventId: String? = null,
+        @Index var isLastForwardThread: Boolean = false,
 ) : RealmObject() {
 
     fun identifier() = "${prevToken}_$nextToken"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
index 3e8813042004e6fd9c31c33449b185b2923e2d50..c3abd8b028b92a211607f5bb116cd15332835c16 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
 import io.realm.RealmList
 import io.realm.RealmObject
 import io.realm.annotations.PrimaryKey
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
 import timber.log.Timber
 
 internal open class EventAnnotationsSummaryEntity(
@@ -27,7 +28,8 @@ internal open class EventAnnotationsSummaryEntity(
         var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
         var editSummary: EditAggregatedSummaryEntity? = null,
         var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
-        var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
+        var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null,
+        var liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummaryEntity? = null,
 ) : RealmObject() {
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
index 527f782359022ab2fb29e42b5a8d0984e055a00d..0120bb91d3ef2edcd5a8ee49b093aed01fb94234 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
@@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
  * Then GetGroupDataTask is called regularly to fetch group information from the homeserver.
  */
 internal open class GroupEntity(@PrimaryKey var groupId: String = "") :
-    RealmObject() {
+        RealmObject() {
 
     private var membershipStr: String = Membership.NONE.name
     var membership: Membership
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
index 4125d90891233ac99948f3a8ce9d1c0ffe49d25b..62bf40c1d2e9d9eb846cd8896db644841e3309e1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
@@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.database.model
 
 import io.realm.RealmList
 import io.realm.RealmObject
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.extensions.clearWith
 
 internal open class PushRulesEntity(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
index 4a6f6a7bf8970a893e6f018431db61ee57a0a7ee..d8e6b8af0faaa0fcc22562b10ec993bb6f01903c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
@@ -48,8 +48,10 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
         set(value) {
             membersLoadStatusStr = value.name
         }
+
     companion object
 }
+
 internal fun RoomEntity.removeThreadSummaryIfNeeded(eventId: String) {
     assertIsManaged()
     threadSummaries.findRootOrLatest(eventId)?.let {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index d0d23dd491b3336676cd4561a184cf8d5655c17e..9a92b145101f0da1adf7a83c37447dea783b8c64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.database.model
 
 import io.realm.annotations.RealmModule
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
 import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
 
@@ -47,6 +48,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
             EditAggregatedSummaryEntity::class,
             EditionOfEvent::class,
             PollResponseAggregatedSummaryEntity::class,
+            LiveLocationShareAggregatedSummaryEntity::class,
             ReferencesAggregatedSummaryEntity::class,
             PushRulesEntity::class,
             PushRuleEntity::class,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
index aacd6570bc4768ed37c7ea26e292b3f3e3051cef..477c04fe518097b6aef604a9214b09ab52337cf8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
@@ -32,8 +32,8 @@ internal open class TimelineEventEntity(var localId: Long = 0,
                                         var isUniqueDisplayName: Boolean = false,
                                         var senderAvatar: String? = null,
                                         var senderMembershipEventId: String? = null,
-                                        // ownedByThreadChunk indicates that the current TimelineEventEntity belongs
-                                        // to a thread chunk and is a temporarily event.
+        // ownedByThreadChunk indicates that the current TimelineEventEntity belongs
+        // to a thread chunk and is a temporarily event.
                                         var ownedByThreadChunk: Boolean = false,
                                         var readReceipts: ReadReceiptsSummaryEntity? = null
 ) : RealmObject() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1376839f9375d0ce7ee5c1507e552492d4fd07f6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.model.livelocation
+
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+
+/**
+ * Aggregation info concerning a live location share.
+ */
+internal open class LiveLocationShareAggregatedSummaryEntity(
+        /**
+         * Event id of the event that started the live.
+         */
+        @PrimaryKey
+        var eventId: String = "",
+
+        var roomId: String = "",
+
+        var isActive: Boolean? = null,
+
+        var endOfLiveTimestampMillis: Long? = null,
+
+        /**
+         * For now we persist this as a JSON for greater flexibility
+         * @see [org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
+         */
+        var lastLocationContent: String? = null,
+) : RealmObject() {
+    companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
index a33ba82f7aa000df11165e3c028b5273c48fa97f..9350102137042b6b35c6e94762143f9c07da0914 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
@@ -56,18 +56,21 @@ internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, room
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findLastForwardChunkOfThread(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity? {
     return where(realm, roomId)
             .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findEventInThreadChunk(realm: Realm, roomId: String, event: String): ChunkEntity? {
     return where(realm, roomId)
             .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, arrayListOf(event).toTypedArray())
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List<String>): RealmResults<ChunkEntity> {
     return realm.where<ChunkEntity>()
             .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray())
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2e2e939fa2003e29e12902e1255eedb591723928
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.query
+
+import io.realm.Realm
+import io.realm.RealmQuery
+import io.realm.kotlin.where
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): RealmQuery<LiveLocationShareAggregatedSummaryEntity> {
+    return realm.where<LiveLocationShareAggregatedSummaryEntity>()
+            .equalTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId)
+            .equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
+}
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.create(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): LiveLocationShareAggregatedSummaryEntity {
+    val obj = realm.createObject(LiveLocationShareAggregatedSummaryEntity::class.java, eventId).apply {
+        this.roomId = roomId
+    }
+    val annotationSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId = roomId, eventId = eventId)
+    annotationSummary.liveLocationShareAggregatedSummary = obj
+
+    return obj
+}
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): LiveLocationShareAggregatedSummaryEntity {
+    return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
+            ?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
index 1f6b210252a10d5d30a0d1d042ac0c549a4affe5..3cea19a690f8d87e13bafb6a4b67b4c8d3559606 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.database.query
 import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.model.PushRuleEntityFields
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
index 517d43d7cf965efe4593be5144d3028e45a2ddfe..eab27404330f875aecc30af47f67b834cfd2e61a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
@@ -39,9 +39,11 @@ internal fun ThreadSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Str
         this.rootThreadEventId = rootThreadEventId
     }
 }
+
 internal fun ThreadSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String, rootThreadEventId: String): ThreadSummaryEntity? {
     return where(realm, roomId, rootThreadEventId).findFirst()
 }
+
 internal fun RealmList<ThreadSummaryEntity>.find(rootThreadEventId: String): ThreadSummaryEntity? {
     return this.where()
             .equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
index 81d5ac835f1465b7249a786ab224a3e409eab6cd..215ab34f95438a7c697f5b7f48274e4e4920bdb0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
@@ -29,26 +29,35 @@ import org.matrix.android.sdk.internal.database.model.RoomEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 
-internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<TimelineEventEntity> {
-    return realm.where<TimelineEventEntity>()
+internal fun TimelineEventEntity.Companion.where(realm: Realm): RealmQuery<TimelineEventEntity> {
+    return realm.where()
+}
+
+internal fun TimelineEventEntity.Companion.where(realm: Realm,
+                                                 roomId: String,
+                                                 eventId: String): RealmQuery<TimelineEventEntity> {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
             .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
 }
 
-internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventIds: List<String>): RealmQuery<TimelineEventEntity> {
-    return realm.where<TimelineEventEntity>()
+internal fun TimelineEventEntity.Companion.where(realm: Realm,
+                                                 roomId: String,
+                                                 eventIds: List<String>): RealmQuery<TimelineEventEntity> {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
             .`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray())
 }
 
 internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm,
                                                        roomId: String): RealmQuery<TimelineEventEntity> {
-    return realm.where<TimelineEventEntity>()
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
 }
 
-internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
-    return realm.where<TimelineEventEntity>()
+internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm,
+                                                                         senderMembershipEventId: String): List<TimelineEventEntity> {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT_ID, senderMembershipEventId)
             .findAll()
 }
@@ -110,12 +119,12 @@ internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<Strin
     return if (filterTypes.isEmpty()) {
         this
     } else {
-        this.`in`(TimelineEventEntityFields.ROOT.TYPE, filterTypes.toTypedArray())
+        `in`(TimelineEventEntityFields.ROOT.TYPE, filterTypes.toTypedArray())
     }
 }
 
 internal fun RealmList<TimelineEventEntity>.find(eventId: String): TimelineEventEntity? {
-    return this.where()
+    return where()
             .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
             .findFirst()
 }
@@ -132,3 +141,14 @@ internal fun RealmQuery<TimelineEventEntity>.filterSendStates(sendStates: List<S
     val sendStatesStr = sendStates.map { it.name }.toTypedArray()
     return `in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr)
 }
+
+/**
+ * Find all TimelineEventEntity items where sender is in senderIds collection, excluding state events
+ */
+internal fun TimelineEventEntity.Companion.findAllFrom(realm: Realm,
+                                                       senderIds: Collection<String>): RealmResults<TimelineEventEntity> {
+    return where(realm)
+            .`in`(TimelineEventEntityFields.ROOT.SENDER, senderIds.toTypedArray())
+            .isNull(TimelineEventEntityFields.ROOT.STATE_KEY)
+            .findAll()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
index cd7c99b8f964e6adff32c5fb242f5d91abd83a30..3d2b2bfbfb8c19f7e4cad09f8b2949c2eaaad3af 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
@@ -44,7 +44,7 @@ internal interface NetworkConnectivityChecker {
 internal class DefaultNetworkConnectivityChecker @Inject constructor(private val homeServerPinger: HomeServerPinger,
                                                                      private val backgroundDetectionObserver: BackgroundDetectionObserver,
                                                                      private val networkCallbackStrategy: NetworkCallbackStrategy) :
-    NetworkConnectivityChecker {
+        NetworkConnectivityChecker {
 
     private val hasInternetAccess = AtomicBoolean(true)
     private val listeners = Collections.synchronizedSet(LinkedHashSet<NetworkConnectivityChecker.Listener>())
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
index 5cd2d880003ba2caf9d6f956b11d60c587e58e7e..71df7c08be9c63607e1d622619bbaf857f70410d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
@@ -15,6 +15,7 @@
  */
 
 package org.matrix.android.sdk.internal.network
+
 import org.matrix.android.sdk.internal.network.executeRequest as internalExecuteRequest
 
 internal interface RequestExecutor {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
index ac097f57ee604159f9b6447ade65fa6aae2b3d47..78f1c84f3dd417d99cc4c5620581fda8bc46476b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
 import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
 import org.matrix.android.sdk.internal.util.file.AtomicFileCreator
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.util.writeToFile
 import timber.log.Timber
 import java.io.File
@@ -51,7 +52,8 @@ internal class DefaultFileService @Inject constructor(
         private val contentUrlResolver: ContentUrlResolver,
         @UnauthenticatedWithCertificateWithProgress
         private val okHttpClient: OkHttpClient,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
 ) : FileService {
 
     // Legacy folder, will be deleted
@@ -123,7 +125,7 @@ internal class DefaultFileService @Inject constructor(
                     val resolvedUrl = contentUrlResolver.resolveForDownload(url, elementToDecrypt) ?: throw IllegalArgumentException("url is null")
 
                     val request = when (resolvedUrl) {
-                        is ContentUrlResolver.ResolvedMethod.GET -> {
+                        is ContentUrlResolver.ResolvedMethod.GET  -> {
                             Request.Builder()
                                     .url(resolvedUrl.url)
                                     .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
@@ -182,7 +184,8 @@ internal class DefaultFileService @Inject constructor(
                             MXEncryptedAttachments.decryptAttachment(
                                     inputStream,
                                     elementToDecrypt,
-                                    outputStream
+                                    outputStream,
+                                    clock
                             )
                         }
                     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index 1e533158a76d2454996e0d4574265ffc2710a4ba..5f77cfb23a702ec54ab3cdb615897e5b899eb4fa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.api.federation.FederationService
-import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.EventStreamService
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.SessionLifecycleObserver
@@ -53,6 +52,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService
 import org.matrix.android.sdk.api.session.presence.PresenceService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.pushers.PushersService
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
 import org.matrix.android.sdk.api.session.room.RoomService
 import org.matrix.android.sdk.api.session.search.SearchService
@@ -124,12 +124,12 @@ internal class DefaultSession @Inject constructor(
         private val syncStatusService: Lazy<SyncStatusService>,
         private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
         private val accountDataService: Lazy<SessionAccountDataService>,
-        private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
+        private val sharedSecretStorageService: Lazy<SharedSecretStorageService>,
         private val accountService: Lazy<AccountService>,
         private val eventService: Lazy<EventService>,
         private val contentScannerService: Lazy<ContentScannerService>,
-        private val identityService: IdentityService,
-        private val integrationManagerService: IntegrationManagerService,
+        private val identityService: Lazy<IdentityService>,
+        private val integrationManagerService: Lazy<IntegrationManagerService>,
         private val thirdPartyService: Lazy<ThirdPartyService>,
         private val callSignalingService: Lazy<CallSignalingService>,
         private val spaceService: Lazy<SpaceService>,
@@ -140,28 +140,7 @@ internal class DefaultSession @Inject constructor(
         @UnauthenticatedWithCertificate
         private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
 ) : Session,
-        GlobalErrorHandler.Listener,
-        RoomService by roomService.get(),
-        RoomDirectoryService by roomDirectoryService.get(),
-        GroupService by groupService.get(),
-        UserService by userService.get(),
-        SignOutService by signOutService.get(),
-        FilterService by filterService.get(),
-        PushRuleService by pushRuleService.get(),
-        PushersService by pushersService.get(),
-        EventService by eventService.get(),
-        TermsService by termsService.get(),
-        SyncStatusService by syncStatusService.get(),
-        SecureStorageService by secureStorageService.get(),
-        HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
-        ProfileService by profileService.get(),
-        PresenceService by presenceService.get(),
-        AccountService by accountService.get(),
-        ToDeviceService by toDeviceService.get(),
-        EventStreamService by eventStreamService.get() {
-
-    override val sharedSecretStorageService: SharedSecretStorageService
-        get() = _sharedSecretStorageService.get()
+        GlobalErrorHandler.Listener {
 
     private var isOpen = false
 
@@ -274,42 +253,44 @@ internal class DefaultSession @Inject constructor(
     }
 
     override fun contentUrlResolver() = contentUrlResolver
-
     override fun contentUploadProgressTracker() = contentUploadProgressTracker
-
     override fun typingUsersTracker() = typingUsersTracker
-
     override fun contentDownloadProgressTracker(): ContentDownloadStateTracker = contentDownloadStateTracker
 
     override fun cryptoService(): CryptoService = cryptoService.get()
-
     override fun contentScannerService(): ContentScannerService = contentScannerService.get()
-
-    override fun identityService() = identityService
-
+    override fun identityService(): IdentityService = identityService.get()
+    override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = homeServerCapabilitiesService.get()
+    override fun roomService(): RoomService = roomService.get()
+    override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get()
+    override fun groupService(): GroupService = groupService.get()
+    override fun userService(): UserService = userService.get()
+    override fun signOutService(): SignOutService = signOutService.get()
+    override fun filterService(): FilterService = filterService.get()
+    override fun pushRuleService(): PushRuleService = pushRuleService.get()
+    override fun pushersService(): PushersService = pushersService.get()
+    override fun eventService(): EventService = eventService.get()
+    override fun termsService(): TermsService = termsService.get()
+    override fun syncStatusService(): SyncStatusService = syncStatusService.get()
+    override fun secureStorageService(): SecureStorageService = secureStorageService.get()
+    override fun profileService(): ProfileService = profileService.get()
+    override fun presenceService(): PresenceService = presenceService.get()
+    override fun accountService(): AccountService = accountService.get()
+    override fun toDeviceService(): ToDeviceService = toDeviceService.get()
+    override fun eventStreamService(): EventStreamService = eventStreamService.get()
     override fun fileService(): FileService = defaultFileService.get()
-
     override fun permalinkService(): PermalinkService = permalinkService.get()
-
     override fun widgetService(): WidgetService = widgetService.get()
-
     override fun mediaService(): MediaService = mediaService.get()
-
-    override fun integrationManagerService() = integrationManagerService
-
+    override fun integrationManagerService(): IntegrationManagerService = integrationManagerService.get()
     override fun callSignalingService(): CallSignalingService = callSignalingService.get()
-
     override fun searchService(): SearchService = searchService.get()
-
     override fun federationService(): FederationService = federationService.get()
-
     override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
-
     override fun spaceService(): SpaceService = spaceService.get()
-
     override fun openIdService(): OpenIdService = openIdService.get()
-
     override fun accountDataService(): SessionAccountDataService = accountDataService.get()
+    override fun sharedSecretStorageService(): SharedSecretStorageService = sharedSecretStorageService.get()
 
     override fun getOkHttpClient(): OkHttpClient {
         return unauthenticatedWithCertificateOkHttpClient.get()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
index 752856b931aac41d6e16e5d08e2b6098bb1cba47..9f3f1f649eea158658c51d6fa3f2e74cb60a2553 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.account
 
 import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.api.session.uia.UiaResult
+import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
@@ -51,18 +53,24 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
             }
             true
         } catch (throwable: Throwable) {
-            if (!handleUIA(
-                            failure = throwable,
-                            interceptor = params.userInteractiveAuthInterceptor,
-                            retryBlock = { authUpdate ->
-                                execute(params.copy(userAuthParam = authUpdate))
-                            }
-                    )
-            ) {
-                Timber.d("## UIA: propagate failure")
-                throw throwable
-            } else {
-                false
+            when (handleUIA(
+                    failure = throwable,
+                    interceptor = params.userInteractiveAuthInterceptor,
+                    retryBlock = { authUpdate ->
+                        execute(params.copy(userAuthParam = authUpdate))
+                    }
+            )) {
+                UiaResult.SUCCESS   -> {
+                    false
+                }
+                UiaResult.FAILURE   -> {
+                    Timber.d("## UIA: propagate failure")
+                    throw throwable
+                }
+                UiaResult.CANCELLED -> {
+                    Timber.d("## UIA: cancelled")
+                    throw UiaCancelledException()
+                }
             }
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
index 3f199c5cce951ab274d31556b346f0c159717af3..b15a6474218db146a2204478e08fc00746000b1d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
@@ -30,7 +30,7 @@ private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP)
 
 @SessionScope
 internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) :
-    EventInsertLiveProcessor {
+        EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
             EventType.CALL_ANSWER,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
index 59058bf9767648cea08f3dea30bbfb7bfca60502..c4f711a9e6fb33966a55e93bfde2b4c59b4a80eb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerConten
 import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -41,9 +42,12 @@ private val loggerTag = LoggerTag("CallSignalingHandler", LoggerTag.VOIP)
 private const val MAX_AGE_TO_RING = 40_000
 
 @SessionScope
-internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler,
-                                                        private val mxCallFactory: MxCallFactory,
-                                                        @UserId private val userId: String) {
+internal class CallSignalingHandler @Inject constructor(
+        private val activeCallHandler: ActiveCallHandler,
+        private val mxCallFactory: MxCallFactory,
+        @UserId private val userId: String,
+        private val clock: Clock,
+) {
 
     private val invitedCallIds = mutableSetOf<String>()
     private val callListeners = mutableSetOf<CallListener>()
@@ -184,7 +188,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
         if (event.roomId == null || event.senderId == null) {
             return
         }
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         val age = now - (event.ageLocalTs ?: now)
         if (age > MAX_AGE_TO_RING) {
             Timber.tag(loggerTag.value).w("Call invite is too old to ring.")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
index 547be2253f80d60c28d613c41be91872a4565f68..9ec892b65db549942596b9b0d6e3f5068d0609e4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
 import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class MxCallFactory @Inject constructor(
@@ -36,7 +37,8 @@ internal class MxCallFactory @Inject constructor(
         private val eventSenderProcessor: EventSenderProcessor,
         private val matrixConfiguration: MatrixConfiguration,
         private val getProfileInfoTask: GetProfileInfoTask,
-        @UserId private val userId: String
+        @UserId private val userId: String,
+        private val clock: Clock,
 ) {
 
     fun createIncomingCall(roomId: String, opponentUserId: String, content: CallInviteContent): MxCall? {
@@ -51,7 +53,8 @@ internal class MxCallFactory @Inject constructor(
                 localEchoEventFactory = localEchoEventFactory,
                 eventSenderProcessor = eventSenderProcessor,
                 matrixConfiguration = matrixConfiguration,
-                getProfileInfoTask = getProfileInfoTask
+                getProfileInfoTask = getProfileInfoTask,
+                clock = clock,
         ).apply {
             updateOpponentData(opponentUserId, content, content.capabilities)
         }
@@ -68,7 +71,8 @@ internal class MxCallFactory @Inject constructor(
                 localEchoEventFactory = localEchoEventFactory,
                 eventSenderProcessor = eventSenderProcessor,
                 matrixConfiguration = matrixConfiguration,
-                getProfileInfoTask = getProfileInfoTask
+                getProfileInfoTask = getProfileInfoTask,
+                clock = clock,
         ).apply {
             // Setup with this userId, might be updated when processing the Answer event
             this.opponentUserId = opponentUserId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
index a89713870a7513bd4eabffb530bd0efe71ae677b..796e83311f89bde906a7c58eea9ca0112f1445cd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
 import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.math.BigDecimal
 
@@ -61,7 +62,8 @@ internal class MxCallImpl(
         private val localEchoEventFactory: LocalEchoEventFactory,
         private val eventSenderProcessor: EventSenderProcessor,
         private val matrixConfiguration: MatrixConfiguration,
-        private val getProfileInfoTask: GetProfileInfoTask
+        private val getProfileInfoTask: GetProfileInfoTask,
+        private val clock: Clock,
 ) : MxCall {
 
     override var opponentPartyId: Optional<String>? = null
@@ -250,7 +252,7 @@ internal class MxCallImpl(
     private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
         return Event(
                 roomId = roomId,
-                originServerTs = System.currentTimeMillis(),
+                originServerTs = clock.epochMillis(),
                 senderId = userId,
                 eventId = localId,
                 type = type,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index e9cb4238933d26d429b12ca51a820cde5a557fad..f96a019fe22aabfeaeae39b8b9401d44d46152e9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -148,8 +148,8 @@ internal class FileUploader @Inject constructor(
                 .post(requestBody)
                 .build()
 
-       return withContext(coroutineDispatchers.io) {
-             okHttpClient.newCall(request).awaitResponse().use { response ->
+        return withContext(coroutineDispatchers.io) {
+            okHttpClient.newCall(request).awaitResponse().use { response ->
                 if (!response.isSuccessful) {
                     throw response.toFailure(globalErrorReceiver)
                 } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
index 01eb52ff2270e875444f6ae7c4e18c583c6a4fbc..c5aa6cd5e717bc1864d32cbaf1f463161ccb8545 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
@@ -68,16 +68,16 @@ internal class ImageCompressor @Inject constructor(
                     val orientation = exifInfo.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
                     val matrix = Matrix()
                     when (orientation) {
-                        ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
-                        ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
-                        ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
+                        ExifInterface.ORIENTATION_ROTATE_270      -> matrix.postRotate(270f)
+                        ExifInterface.ORIENTATION_ROTATE_180      -> matrix.postRotate(180f)
+                        ExifInterface.ORIENTATION_ROTATE_90       -> matrix.postRotate(90f)
                         ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1f, 1f)
-                        ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f)
-                        ExifInterface.ORIENTATION_TRANSPOSE -> {
+                        ExifInterface.ORIENTATION_FLIP_VERTICAL   -> matrix.preScale(1f, -1f)
+                        ExifInterface.ORIENTATION_TRANSPOSE       -> {
                             matrix.preRotate(-90f)
                             matrix.preScale(-1f, 1f)
                         }
-                        ExifInterface.ORIENTATION_TRANSVERSE -> {
+                        ExifInterface.ORIENTATION_TRANSVERSE      -> {
                             matrix.preRotate(90f)
                             matrix.preScale(-1f, 1f)
                         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 75606f2e7a7cb407122db257cd6e95af70d50a33..75a79abcdbcd2aad7e828ebfe9b0ceb8de796da1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.room.send.LocalEchoIdentifiers
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
 import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
 import org.matrix.android.sdk.internal.util.TemporaryFileCreator
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.util.toMatrixErrorStr
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@@ -87,6 +88,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
     @Inject lateinit var thumbnailExtractor: ThumbnailExtractor
     @Inject lateinit var localEchoRepository: LocalEchoRepository
     @Inject lateinit var temporaryFileCreator: TemporaryFileCreator
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -243,7 +245,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                             .also { filesToDelete.add(it) }
 
                     uploadedFileEncryptedFileInfo =
-                            MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total ->
+                            MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile, clock) { read, total ->
                                 notifyTracker(params) {
                                     contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
                                 }
@@ -329,7 +331,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                         if (params.isEncrypted) {
                             Timber.v("Encrypt thumbnail")
                             notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
-                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream())
+                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), clock)
                             val contentUploadResponse = fileUploader.uploadByteArray(
                                     byteArray = encryptionResult.encryptedByteArray,
                                     filename = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
index da7e2d102e081ca99399cecbc38a855f54c342ed..5aaf0587571d48c800bcbf8b16344dd75f93caef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPub
 import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
 import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -46,7 +47,8 @@ internal class DefaultContentScannerService @Inject constructor(
         private val getServerPublicKeyTask: GetServerPublicKeyTask,
         private val scanEncryptedTask: ScanEncryptedTask,
         private val scanMediaTask: ScanMediaTask,
-        private val taskExecutor: TaskExecutor
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : ContentScannerService {
 
     // Cache public key in memory
@@ -71,11 +73,13 @@ internal class DefaultContentScannerService @Inject constructor(
 
     override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
         val result = if (fileInfo != null) {
-            scanEncryptedTask.execute(ScanEncryptedTask.Params(
-                    mxcUrl = mxcUrl,
-                    publicServerKey = getServerPublicKey(false),
-                    encryptedInfo = fileInfo
-            ))
+            scanEncryptedTask.execute(
+                    ScanEncryptedTask.Params(
+                            mxcUrl = mxcUrl,
+                            publicServerKey = getServerPublicKey(false),
+                            encryptedInfo = fileInfo
+                    )
+            )
         } else {
             scanMediaTask.execute(ScanMediaTask.Params(mxcUrl))
         }
@@ -83,7 +87,7 @@ internal class DefaultContentScannerService @Inject constructor(
         return ScanStatusInfo(
                 state = if (result.clean) ScanState.TRUSTED else ScanState.INFECTED,
                 humanReadableMessage = result.info,
-                scanDateTimestamp = System.currentTimeMillis()
+                scanDateTimestamp = clock.epochMillis()
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
index b47be235c602506f5313168d53d2628ca3e0fdac..e4b64a1a0e5442183d9183c7cb7cf74af40c4629 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
@@ -31,11 +31,14 @@ internal fun ContentScanResultEntity.Companion.get(realm: Realm, attachmentUrl:
             .findFirst()
 }
 
-internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity {
+internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm,
+                                                           attachmentUrl: String,
+                                                           contentScannerUrl: String?,
+                                                           currentTimeMillis: Long): ContentScanResultEntity {
     return ContentScanResultEntity.get(realm, attachmentUrl, contentScannerUrl)
             ?: realm.createObject<ContentScanResultEntity>().also {
                 it.mediaUrl = attachmentUrl
-                it.scanDateTimestamp = System.currentTimeMillis()
+                it.scanDateTimestamp = currentTimeMillis
                 it.scannerUrl = contentScannerUrl
             }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
index 947a66c8b9782f609c5aecefef53985b0f750b12..27729d38c78679eeba49c8ea3ae9389fed8377a9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
@@ -32,12 +32,14 @@ import org.matrix.android.sdk.internal.di.ContentScannerDatabase
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
 import org.matrix.android.sdk.internal.util.isValidUrl
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 @SessionScope
 internal class RealmContentScannerStore @Inject constructor(
         @ContentScannerDatabase
-        private val realmConfiguration: RealmConfiguration
+        private val realmConfiguration: RealmConfiguration,
+        private val clock: Clock,
 ) : ContentScannerStore {
 
     private val monarchy = Monarchy.Builder()
@@ -82,15 +84,15 @@ internal class RealmContentScannerStore @Inject constructor(
 
     override fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) {
         monarchy.runTransactionSync {
-            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).scanResult = state
+            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl, clock.epochMillis()).scanResult = state
         }
     }
 
     override fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) {
         monarchy.runTransactionSync {
-            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).apply {
+            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl, clock.epochMillis()).apply {
                 scanResult = state
-                scanDateTimestamp = System.currentTimeMillis()
+                scanDateTimestamp = clock.epochMillis()
                 humanReadableMessage = humanReadable
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
index 4a1e6661b0c8e8e19bd3d706d88238a8b21103e2..c138c1a40e666ee5e4d687b8228bccc028cac72d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
@@ -24,7 +24,7 @@ import javax.inject.Inject
 
 @SessionScope
 internal class DefaultSyncStatusService @Inject constructor() :
-    SyncStatusService,
+        SyncStatusService,
         ProgressReporter {
 
     private val status = MutableLiveData<SyncStatusService.Status>()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
index 30b1589169694d63bd8e0d34dc9c7c8e29646228..1b96931c6c8eaeaf3155ed298d6f73fbc076baca 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
@@ -59,7 +59,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
                                                       private val updateUserAccountDataTask: UpdateUserAccountDataTask,
                                                       private val accountDataDataSource: UserAccountDataDataSource,
                                                       private val widgetFactory: WidgetFactory) :
-    SessionLifecycleObserver {
+        SessionLifecycleObserver {
 
     private val currentConfigs = ArrayList<IntegrationManagerConfig>()
     private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
index dff82cb42e42a7e8407d9c7c9afed9d49993bcf2..d20cf8f140dd9d88594cc860c844d4c3ca16aa5a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
@@ -72,6 +72,7 @@ internal class ViaParameterFinder @Inject constructor(
      */
     private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
         return roomGetterProvider.get().getRoom(roomId)
+                ?.membershipService()
                 ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
                 ?.map { it.userId }
                 .orEmpty()
@@ -84,6 +85,7 @@ internal class ViaParameterFinder @Inject constructor(
     // It may not be possible for a user to join a room if there's no overlap between these
     fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
         val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
+                ?.membershipService()
                 ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
                 ?.map { it.userId }
                 ?.filter { userCanInvite(userId, roomId) }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
index 6ff4efaf113d99a48dca182841ebc4ef69fa43ed..501aff63bd91b11c6455b270552198a9046081d6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
 import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.api.session.uia.UiaResult
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
@@ -72,13 +73,13 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
                 true
             } catch (throwable: Throwable) {
                 if (params.userInteractiveAuthInterceptor == null ||
-                        !handleUIA(
+                        handleUIA(
                                 failure = throwable,
                                 interceptor = params.userInteractiveAuthInterceptor,
                                 retryBlock = { authUpdate ->
                                     execute(params.copy(userAuthParam = authUpdate))
                                 }
-                        )
+                        ) != UiaResult.SUCCESS
                 ) {
                     Timber.d("## UIA: propagate failure")
                     throw throwable.toRegistrationFlowResponse()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
index b2176871688bf631e5aa35f253169bc3ff0006d2..c46474cf90b951c8551abfd85f3dc27997796d91 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
@@ -15,8 +15,8 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
index ce29efaaac5160fd750de83f14185777a97c6141..0042558027746ef4422dd337825a27c21aeb083b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
@@ -26,7 +26,7 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
 
 internal class AddPusherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
index 84a05067bed8084b91a9fea1fef23ed27933c217..67fba390d00e1a5458060f005678a20a382fd047 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
@@ -15,14 +15,15 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
-import org.matrix.android.sdk.api.pushrules.EventMatchCondition
-import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition
+import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition
+import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
+import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.room.RoomGetter
@@ -60,7 +61,7 @@ internal class DefaultConditionResolver @Inject constructor(
                                                      condition: ContainsDisplayNameCondition): Boolean {
         val roomId = event.roomId ?: return false
         val room = roomGetter.getRoom(roomId) ?: return false
-        val myDisplayName = room.getRoomMember(userId)?.displayName ?: return false
+        val myDisplayName = room.membershipService().getRoomMember(userId)?.displayName ?: return false
         return condition.isSatisfied(event, myDisplayName)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
similarity index 90%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
index 35b4d77c0e4bc91a02552d6c998917810d4a9a96..de038196291efb2aca738291d6486cf9e2e09399 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.internal.session.pushers
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 
 /**
  * All push rulesets for a user.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
index 994b4860c6fd4511ca921a78ad7d09c1cc5ab3c3..dab6d373177b9f26062c70048d5be00c8a710633 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
@@ -15,8 +15,7 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import retrofit2.http.Body
 import retrofit2.http.DELETE
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
index d53a4eed65462a96b60c8db9a967ef0133d7ebd9..4528c95e6914d88e2b90b88edfc7d7221fb1a4ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
@@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.session.pushers
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.pushers.PushersService
-import org.matrix.android.sdk.internal.session.notification.DefaultProcessEventForPushTask
-import org.matrix.android.sdk.internal.session.notification.DefaultPushRuleService
-import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.internal.session.pushers.gateway.DefaultPushGatewayNotifyTask
 import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask
+import org.matrix.android.sdk.internal.session.pushrules.DefaultProcessEventForPushTask
+import org.matrix.android.sdk.internal.session.pushrules.DefaultPushRuleService
+import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask
 import org.matrix.android.sdk.internal.session.room.notification.DefaultSetRoomNotificationStateTask
 import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask
 import retrofit2.Retrofit
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
index bae893608bc0e6426b9a8b01e08b430f1ab2511f..9b0bf7934bf843e7e9ad2f0d4a2cfe346931ceab 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
@@ -15,7 +15,7 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
index 6a4b891ecffe36b6735b68ace3354e9425939da5..ff685e9281554754fe04128c17bd73e2aeeaaa4a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
@@ -16,9 +16,8 @@
 package org.matrix.android.sdk.internal.session.pushers
 
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
 import org.matrix.android.sdk.internal.database.model.deleteOnCascade
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
index 33589dc55bc5d384eaf6c0392038175261810b82..454b9cdd80b8fb6a9a966a0b80e03fcd5d4137a9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
@@ -15,9 +15,9 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.toJson
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
@@ -38,18 +38,18 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
 ) : UpdatePushRuleActionsTask {
 
     override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
+        executeRequest(globalErrorReceiver) {
+            pushRulesApi.updateEnableRuleStatus(
+                    params.kind.value,
+                    params.ruleId,
+                    EnabledBody(params.enable)
+            )
+        }
+        if (params.actions != null) {
+            val body = mapOf("actions" to params.actions.toJson())
             executeRequest(globalErrorReceiver) {
-                pushRulesApi.updateEnableRuleStatus(
-                        params.kind.value,
-                        params.ruleId,
-                        EnabledBody(params.enable)
-                )
-            }
-            if (params.actions != null) {
-                val body = mapOf("actions" to params.actions.toJson())
-                executeRequest(globalErrorReceiver) {
-                    pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
-                }
+                pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
             }
+        }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
index 3fe1614678b302d7e8fd361cb021fa6af1dde2ff..815661a1ce9863170b2d10e29ee6aa120f8b1d9c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
@@ -15,8 +15,8 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
similarity index 90%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
index cdc7350f8bcde907aef51c8d5640e67502a36a66..ace23f1fe5e8cdd8a64fbdeedadbcb5b8b908e73 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
@@ -13,23 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.PushEvents
-import org.matrix.android.sdk.api.pushrules.PushRuleService
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.rest.RuleSet
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.PushEvents
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
similarity index 95%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
index 899bce4c8ddf318effb7c810ff6fae207b768702..91d092a2b4b552f9b2e1185f01d24a1fb07edbe3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.PushEvents
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.isInvitation
+import org.matrix.android.sdk.api.session.pushrules.PushEvents
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
similarity index 86%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
index 6e302d373d819dfe5944685165bd39c06200c729..b9d06a934dd628ca5a05d824897b1c2d0846f3d7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import javax.inject.Inject
 
 internal class PushRuleFinder @Inject constructor(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index 3f129c4d71be31af769053f0979d085610086b72..7326adee4c7a1a1d68ad1a3283046c57f3056606 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -18,13 +18,11 @@ package org.matrix.android.sdk.internal.session.room
 
 import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
-import org.matrix.android.sdk.api.session.crypto.CryptoService
-import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService
 import org.matrix.android.sdk.api.session.room.alias.AliasService
 import org.matrix.android.sdk.api.session.room.call.RoomCallService
+import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
 import org.matrix.android.sdk.api.session.room.members.MembershipService
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomType
@@ -42,62 +40,37 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
 import org.matrix.android.sdk.api.session.room.version.RoomVersionService
-import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.api.util.awaitCallback
 import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
-import org.matrix.android.sdk.internal.session.room.state.SendStateTask
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
-import org.matrix.android.sdk.internal.session.search.SearchTask
 import org.matrix.android.sdk.internal.session.space.DefaultSpace
-import java.security.InvalidParameterException
 
-internal class DefaultRoom(override val roomId: String,
-                           private val roomSummaryDataSource: RoomSummaryDataSource,
-                           private val timelineService: TimelineService,
-                           private val threadsService: ThreadsService,
-                           private val threadsLocalService: ThreadsLocalService,
-                           private val sendService: SendService,
-                           private val draftService: DraftService,
-                           private val stateService: StateService,
-                           private val uploadsService: UploadsService,
-                           private val reportingService: ReportingService,
-                           private val roomCallService: RoomCallService,
-                           private val readService: ReadService,
-                           private val typingService: TypingService,
-                           private val aliasService: AliasService,
-                           private val tagsService: TagsService,
-                           private val cryptoService: CryptoService,
-                           private val relationService: RelationService,
-                           private val roomMembersService: MembershipService,
-                           private val roomPushRuleService: RoomPushRuleService,
-                           private val roomAccountDataService: RoomAccountDataService,
-                           private val roomVersionService: RoomVersionService,
-                           private val sendStateTask: SendStateTask,
-                           private val viaParameterFinder: ViaParameterFinder,
-                           private val searchTask: SearchTask,
-                           override val coroutineDispatchers: MatrixCoroutineDispatchers
-) :
-        Room,
-        TimelineService by timelineService,
-        ThreadsService by threadsService,
-        ThreadsLocalService by threadsLocalService,
-        SendService by sendService,
-        DraftService by draftService,
-        StateService by stateService,
-        UploadsService by uploadsService,
-        ReportingService by reportingService,
-        RoomCallService by roomCallService,
-        ReadService by readService,
-        TypingService by typingService,
-        AliasService by aliasService,
-        TagsService by tagsService,
-        RelationService by relationService,
-        MembershipService by roomMembersService,
-        RoomPushRuleService by roomPushRuleService,
-        RoomAccountDataService by roomAccountDataService,
-        RoomVersionService by roomVersionService {
+internal class DefaultRoom(
+        override val roomId: String,
+        private val roomSummaryDataSource: RoomSummaryDataSource,
+        private val roomCryptoService: RoomCryptoService,
+        private val timelineService: TimelineService,
+        private val threadsService: ThreadsService,
+        private val threadsLocalService: ThreadsLocalService,
+        private val sendService: SendService,
+        private val draftService: DraftService,
+        private val stateService: StateService,
+        private val uploadsService: UploadsService,
+        private val reportingService: ReportingService,
+        private val roomCallService: RoomCallService,
+        private val readService: ReadService,
+        private val typingService: TypingService,
+        private val aliasService: AliasService,
+        private val tagsService: TagsService,
+        private val relationService: RelationService,
+        private val roomMembersService: MembershipService,
+        private val roomPushRuleService: RoomPushRuleService,
+        private val roomAccountDataService: RoomAccountDataService,
+        private val roomVersionService: RoomVersionService,
+        private val viaParameterFinder: ViaParameterFinder,
+        override val coroutineDispatchers: MatrixCoroutineDispatchers
+) : Room {
 
     override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
         return roomSummaryDataSource.getRoomSummaryLive(roomId)
@@ -107,69 +80,28 @@ internal class DefaultRoom(override val roomId: String,
         return roomSummaryDataSource.getRoomSummary(roomId)
     }
 
-    override fun isEncrypted(): Boolean {
-        return cryptoService.isRoomEncrypted(roomId)
-    }
-
-    override fun encryptionAlgorithm(): String? {
-        return cryptoService.getEncryptionAlgorithm(roomId)
-    }
-
-    override fun shouldEncryptForInvitedMembers(): Boolean {
-        return cryptoService.shouldEncryptForInvitedMembers(roomId)
-    }
-
-    override suspend fun prepareToEncrypt() {
-        awaitCallback<Unit> {
-            cryptoService.prepareToEncrypt(roomId, it)
-        }
-    }
-
-    override suspend fun enableEncryption(algorithm: String, force: Boolean) {
-        when {
-            (!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> {
-                throw IllegalStateException("Encryption is already enabled for this room")
-            }
-            (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM)                              -> {
-                throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")
-            }
-            else                                                                            -> {
-                val params = SendStateTask.Params(
-                        roomId = roomId,
-                        stateKey = "",
-                        eventType = EventType.STATE_ROOM_ENCRYPTION,
-                        body = mapOf(
-                                "algorithm" to algorithm
-                        ))
-
-                sendStateTask.execute(params)
-            }
-        }
-    }
-
-    override suspend fun search(searchTerm: String,
-                                nextBatch: String?,
-                                orderByRecent: Boolean,
-                                limit: Int,
-                                beforeLimit: Int,
-                                afterLimit: Int,
-                                includeProfile: Boolean): SearchResult {
-        return searchTask.execute(
-                SearchTask.Params(
-                        searchTerm = searchTerm,
-                        roomId = roomId,
-                        nextBatch = nextBatch,
-                        orderByRecent = orderByRecent,
-                        limit = limit,
-                        beforeLimit = beforeLimit,
-                        afterLimit = afterLimit,
-                        includeProfile = includeProfile
-                )
-        )
-    }
-
     override fun asSpace(): Space? {
         if (roomSummary()?.roomType != RoomType.SPACE) return null
         return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
     }
+
+    override fun timelineService() = timelineService
+    override fun threadsService() = threadsService
+    override fun threadsLocalService() = threadsLocalService
+    override fun sendService() = sendService
+    override fun draftService() = draftService
+    override fun stateService() = stateService
+    override fun uploadsService() = uploadsService
+    override fun reportingService() = reportingService
+    override fun roomCallService() = roomCallService
+    override fun readService() = readService
+    override fun typingService() = typingService
+    override fun aliasService() = aliasService
+    override fun tagsService() = tagsService
+    override fun relationService() = relationService
+    override fun roomCryptoService() = roomCryptoService
+    override fun membershipService() = roomMembersService
+    override fun roomPushRuleService() = roomPushRuleService
+    override fun roomAccountDataService() = roomAccountDataService
+    override fun roomVersionService() = roomVersionService
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index 15ce5810c812abf53427dfc0502bd16cfdbd7417..7e0b44a3149fd0bce90e99f1f049c8dc016ec5d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -28,14 +28,16 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
 import org.matrix.android.sdk.api.session.events.model.getRelationContent
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
 import org.matrix.android.sdk.api.session.room.model.VoteInfo
 import org.matrix.android.sdk.api.session.room.model.VoteSummary
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
@@ -67,6 +69,7 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
 import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor
 import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -75,7 +78,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
         private val stateEventDataSource: StateEventDataSource,
         @SessionId private val sessionId: String,
         private val sessionManager: SessionManager,
-        private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor
+        private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor,
+        private val clock: Clock,
 ) : EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
@@ -91,7 +95,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             // EventType.KEY_VERIFICATION_READY,
             EventType.KEY_VERIFICATION_KEY,
             EventType.ENCRYPTED
-    ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.BEACON_LOCATION_DATA
+    ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA
 
     override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
         return allowedTypes.contains(eventType)
@@ -106,12 +110,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             }
             val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
             when (event.type) {
-                EventType.REACTION                -> {
+                EventType.REACTION                  -> {
                     // we got a reaction!!
                     Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
                     handleReaction(realm, event, roomId, isLocalEcho)
                 }
-                EventType.MESSAGE                 -> {
+                EventType.MESSAGE                   -> {
                     if (event.unsignedData?.relations?.annotations != null) {
                         Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
                         handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
@@ -137,7 +141,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 EventType.KEY_VERIFICATION_START,
                 EventType.KEY_VERIFICATION_MAC,
                 EventType.KEY_VERIFICATION_READY,
-                EventType.KEY_VERIFICATION_KEY    -> {
+                EventType.KEY_VERIFICATION_KEY      -> {
                     Timber.v("## SAS REF in room $roomId for event ${event.eventId}")
                     event.content.toModel<MessageRelationContent>()?.relatesTo?.let {
                         if (it.type == RelationType.REFERENCE && it.eventId != null) {
@@ -146,7 +150,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                     }
                 }
 
-                EventType.ENCRYPTED               -> {
+                EventType.ENCRYPTED                 -> {
                     // Relation type is in clear
                     val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
                     if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE ||
@@ -189,8 +193,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                 }
                             }
                             in EventType.BEACON_LOCATION_DATA -> {
-                                event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let {
-                                    liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho)
+                                event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let {
+                                    liveLocationAggregationProcessor.handleBeaconLocationData(realm, event, it, roomId, isLocalEcho)
                                 }
                             }
                         }
@@ -213,7 +217,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
 //                                 }
 //                    }
                 }
-                EventType.REDACTION               -> {
+                EventType.REDACTION                 -> {
                     val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
                             ?: return
                     when (eventToPrune.type) {
@@ -233,7 +237,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                         }
                     }
                 }
-                in EventType.POLL_START           -> {
+                in EventType.POLL_START             -> {
                     val content: MessagePollContent? = event.content.toModel()
                     if (content?.relatesTo?.type == RelationType.REPLACE) {
                         Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
@@ -241,22 +245,22 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                         handleReplace(realm, event, content, roomId, isLocalEcho)
                     }
                 }
-                in EventType.POLL_RESPONSE        -> {
+                in EventType.POLL_RESPONSE          -> {
                     event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
                         handleResponse(realm, event, it, roomId, isLocalEcho)
                     }
                 }
-                in EventType.POLL_END             -> {
+                in EventType.POLL_END               -> {
                     event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
                         handleEndPoll(realm, event, it, roomId, isLocalEcho)
                     }
                 }
-                in EventType.BEACON_LOCATION_DATA -> {
-                    event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let {
-                        liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho)
+                in EventType.STATE_ROOM_BEACON_INFO -> {
+                    event.content.toModel<MessageBeaconInfoContent>(catchError = true)?.let {
+                        liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho)
                     }
                 }
-                else                              -> Timber.v("UnHandled event ${event.eventId}")
+                else                                -> Timber.v("UnHandled event ${event.eventId}")
             }
         } catch (t: Throwable) {
             Timber.e(t, "## Should not happen ")
@@ -325,7 +329,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                         totalVotes = 0,
                                         winnerVoteCount = 0,
                                 )
-                                        .toContent())
+                                        .toContent()
+                        )
                     }
 
             val txId = event.unsignedData?.transactionId
@@ -335,7 +340,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
                 existingSummary.editions.firstOrNull { it.eventId == txId }?.let {
                     it.eventId = event.eventId
-                    it.timestamp = event.originServerTs ?: System.currentTimeMillis()
+                    it.timestamp = event.originServerTs ?: clock.epochMillis()
                     it.isLocalEcho = false
                 }
             } else {
@@ -346,10 +351,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                 eventId = event.eventId,
                                 content = ContentMapper.map(newContent),
                                 timestamp = if (isLocalEcho) {
-                                    System.currentTimeMillis()
+                                    clock.epochMillis()
                                 } else {
                                     // Do not take local echo originServerTs here, could mess up ordering (keep old ts)
-                                    event.originServerTs ?: System.currentTimeMillis()
+                                    event.originServerTs ?: clock.epochMillis()
                                 },
                                 isLocalEcho = isLocalEcho
                         )
@@ -542,7 +547,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
 
     private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
         val session = sessionManager.getSessionComponent(sessionId)?.session()
-        return session?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
+        return session?.roomService()?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
             Timber.v("## POLL target poll event $eventId not found in room $roomId")
         }
     }
@@ -729,11 +734,13 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 EventType.KEY_VERIFICATION_READY,
                 EventType.KEY_VERIFICATION_KEY,
                 EventType.KEY_VERIFICATION_MAC    -> currentState.toState(VerificationState.WAITING)
-                EventType.KEY_VERIFICATION_CANCEL -> currentState.toState(if (event.senderId == userId) {
-                    VerificationState.CANCELED_BY_ME
-                } else {
-                    VerificationState.CANCELED_BY_OTHER
-                })
+                EventType.KEY_VERIFICATION_CANCEL -> currentState.toState(
+                        if (event.senderId == userId) {
+                            VerificationState.CANCELED_BY_ME
+                        } else {
+                            VerificationState.CANCELED_BY_OTHER
+                        }
+                )
                 EventType.KEY_VERIFICATION_DONE   -> currentState.toState(VerificationState.DONE)
                 else                              -> VerificationState.REQUEST
             }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index 10f75473b71e3256c85e486bcf9238a4b6bf57f2..65ef94999fc2e79b43aeda24cd56f79d987f0c0f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -194,7 +194,8 @@ internal interface RoomAPI {
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}")
     suspend fun sendStateEvent(@Path("roomId") roomId: String,
                                @Path("state_event_type") stateEventType: String,
-                               @Body params: JsonDict)
+                               @Body params: JsonDict
+    ): SendResponse
 
     /**
      * Send a generic state event
@@ -208,7 +209,8 @@ internal interface RoomAPI {
     suspend fun sendStateEvent(@Path("roomId") roomId: String,
                                @Path("state_event_type") stateEventType: String,
                                @Path("state_key") stateKey: String,
-                               @Body params: JsonDict)
+                               @Body params: JsonDict
+    ): SendResponse
 
     /**
      * Get state events of a room
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
index 72a3f9ab22036910d5624c0aa109fc47ab194cad..01c4fd1501dba0eb9895409b8d7a8e36f509e7f6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
@@ -17,13 +17,13 @@
 package org.matrix.android.sdk.internal.session.room
 
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
 import org.matrix.android.sdk.internal.session.room.accountdata.DefaultRoomAccountDataService
 import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
 import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
+import org.matrix.android.sdk.internal.session.room.crypto.DefaultRoomCryptoService
 import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService
 import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService
 import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService
@@ -32,7 +32,6 @@ import org.matrix.android.sdk.internal.session.room.relation.DefaultRelationServ
 import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportingService
 import org.matrix.android.sdk.internal.session.room.send.DefaultSendService
 import org.matrix.android.sdk.internal.session.room.state.DefaultStateService
-import org.matrix.android.sdk.internal.session.room.state.SendStateTask
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
 import org.matrix.android.sdk.internal.session.room.threads.DefaultThreadsService
@@ -41,7 +40,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineServ
 import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
 import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
 import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
-import org.matrix.android.sdk.internal.session.search.SearchTask
 import javax.inject.Inject
 
 internal interface RoomFactory {
@@ -49,36 +47,36 @@ internal interface RoomFactory {
 }
 
 @SessionScope
-internal class DefaultRoomFactory @Inject constructor(private val cryptoService: CryptoService,
-                                                      private val roomSummaryDataSource: RoomSummaryDataSource,
-                                                      private val timelineServiceFactory: DefaultTimelineService.Factory,
-                                                      private val threadsServiceFactory: DefaultThreadsService.Factory,
-                                                      private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory,
-                                                      private val sendServiceFactory: DefaultSendService.Factory,
-                                                      private val draftServiceFactory: DefaultDraftService.Factory,
-                                                      private val stateServiceFactory: DefaultStateService.Factory,
-                                                      private val uploadsServiceFactory: DefaultUploadsService.Factory,
-                                                      private val reportingServiceFactory: DefaultReportingService.Factory,
-                                                      private val roomCallServiceFactory: DefaultRoomCallService.Factory,
-                                                      private val readServiceFactory: DefaultReadService.Factory,
-                                                      private val typingServiceFactory: DefaultTypingService.Factory,
-                                                      private val aliasServiceFactory: DefaultAliasService.Factory,
-                                                      private val tagsServiceFactory: DefaultTagsService.Factory,
-                                                      private val relationServiceFactory: DefaultRelationService.Factory,
-                                                      private val membershipServiceFactory: DefaultMembershipService.Factory,
-                                                      private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
-                                                      private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
-                                                      private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
-                                                      private val sendStateTask: SendStateTask,
-                                                      private val viaParameterFinder: ViaParameterFinder,
-                                                      private val searchTask: SearchTask,
-                                                      private val coroutineDispatchers: MatrixCoroutineDispatchers) :
-        RoomFactory {
+internal class DefaultRoomFactory @Inject constructor(
+        private val roomSummaryDataSource: RoomSummaryDataSource,
+        private val timelineServiceFactory: DefaultTimelineService.Factory,
+        private val threadsServiceFactory: DefaultThreadsService.Factory,
+        private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory,
+        private val sendServiceFactory: DefaultSendService.Factory,
+        private val draftServiceFactory: DefaultDraftService.Factory,
+        private val stateServiceFactory: DefaultStateService.Factory,
+        private val uploadsServiceFactory: DefaultUploadsService.Factory,
+        private val reportingServiceFactory: DefaultReportingService.Factory,
+        private val roomCallServiceFactory: DefaultRoomCallService.Factory,
+        private val readServiceFactory: DefaultReadService.Factory,
+        private val typingServiceFactory: DefaultTypingService.Factory,
+        private val aliasServiceFactory: DefaultAliasService.Factory,
+        private val tagsServiceFactory: DefaultTagsService.Factory,
+        private val relationServiceFactory: DefaultRelationService.Factory,
+        private val roomCryptoServiceFactory: DefaultRoomCryptoService.Factory,
+        private val membershipServiceFactory: DefaultMembershipService.Factory,
+        private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
+        private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
+        private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
+        private val viaParameterFinder: ViaParameterFinder,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers
+) : RoomFactory {
 
     override fun create(roomId: String): Room {
         return DefaultRoom(
                 roomId = roomId,
                 roomSummaryDataSource = roomSummaryDataSource,
+                roomCryptoService = roomCryptoServiceFactory.create(roomId),
                 timelineService = timelineServiceFactory.create(roomId),
                 threadsService = threadsServiceFactory.create(roomId),
                 threadsLocalService = threadsLocalServiceFactory.create(roomId),
@@ -92,14 +90,11 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                 typingService = typingServiceFactory.create(roomId),
                 aliasService = aliasServiceFactory.create(roomId),
                 tagsService = tagsServiceFactory.create(roomId),
-                cryptoService = cryptoService,
                 relationService = relationServiceFactory.create(roomId),
                 roomMembersService = membershipServiceFactory.create(roomId),
                 roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
                 roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
                 roomVersionService = roomVersionServiceFactory.create(roomId),
-                sendStateTask = sendStateTask,
-                searchTask = searchTask,
                 viaParameterFinder = viaParameterFinder,
                 coroutineDispatchers = coroutineDispatchers
         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt
index 95e196c7621303292ce101357c20596daf38fabc..997e31a1091e51b62c75d8086225d60e0346d252 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt
@@ -17,69 +17,78 @@
 package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
 
 import io.realm.Realm
-import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.extensions.orTrue
 import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
-import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
-import org.matrix.android.sdk.internal.database.query.getOrNull
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.database.query.getOrCreate
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor {
 
-    override fun handleLiveLocation(realm: Realm, event: Event, content: MessageLiveLocationContent, roomId: String, isLocalEcho: Boolean) {
-        val locationSenderId = event.senderId ?: return
-
-        // We shouldn't process local echos
-        if (isLocalEcho) {
+    override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
+        if (event.senderId.isNullOrEmpty() || isLocalEcho) {
             return
         }
 
-        // A beacon info state event has to be sent before sending location
-        // TODO handle missing check of m_relatesTo field
-        var beaconInfoEntity: CurrentStateEventEntity? = null
-        val eventTypesIterator = EventType.STATE_ROOM_BEACON_INFO.iterator()
-        while (beaconInfoEntity == null && eventTypesIterator.hasNext()) {
-            beaconInfoEntity = CurrentStateEventEntity.getOrNull(realm, roomId, locationSenderId, eventTypesIterator.next())
+        val targetEventId = if (content.isLive.orTrue()) {
+            event.eventId
+        } else {
+            // when live is set to false, we use the id of the event that should have been replaced
+            event.unsignedData?.replacesState
         }
 
-        if (beaconInfoEntity == null) {
-            Timber.v("## LIVE LOCATION. There is not any beacon info which should be emitted before sending location updates")
+        if (targetEventId.isNullOrEmpty()) {
+            Timber.w("no target event id found for the beacon content")
             return
         }
-        val beaconInfoContent = ContentMapper.map(beaconInfoEntity.root?.content)?.toModel<LiveLocationBeaconContent>(catchError = true)
-        if (beaconInfoContent == null) {
-            Timber.v("## LIVE LOCATION. Beacon info content is invalid")
+
+        val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
+                realm = realm,
+                roomId = roomId,
+                eventId = targetEventId
+        )
+
+        Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")
+
+        aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
+        aggregatedSummary.isActive = content.isLive
+    }
+
+    override fun handleBeaconLocationData(realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, isLocalEcho: Boolean) {
+        if (event.senderId.isNullOrEmpty() || isLocalEcho) {
             return
         }
 
-        // Check if live location is ended
-        if (!beaconInfoContent.getBestBeaconInfo()?.isLive.orFalse()) {
-            Timber.v("## LIVE LOCATION. Beacon info is not live anymore")
+        val targetEventId = content.relatesTo?.eventId
+
+        if (targetEventId.isNullOrEmpty()) {
+            Timber.w("no target event id found for the live location content")
             return
         }
 
-        // Check if beacon info is outdated
-        if (isBeaconInfoOutdated(beaconInfoContent, content)) {
-            Timber.v("## LIVE LOCATION. Beacon info has timeout")
-            beaconInfoContent.hasTimedOut = true
-        } else {
-            beaconInfoContent.lastLocationContent = content
-        }
+        val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
+                realm = realm,
+                roomId = roomId,
+                eventId = targetEventId
+        )
+        val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0
+        val currentLocationTimestamp = ContentMapper
+                .map(aggregatedSummary.lastLocationContent)
+                .toModel<MessageBeaconLocationDataContent>()
+                ?.getBestTimestampMillis()
+                ?: 0
 
-        beaconInfoEntity.root?.content = ContentMapper.map(beaconInfoContent.toContent())
+        if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) {
+            Timber.d("updating last location of the summary of id=$targetEventId")
+            aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent())
+        }
     }
 
-    private fun isBeaconInfoOutdated(beaconInfoContent: LiveLocationBeaconContent,
-                                     liveLocationContent: MessageLiveLocationContent): Boolean {
-        val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0
-        val liveLocationEventTime = liveLocationContent.getBestTimestampAsMilliseconds() ?: 0
-        val timeout = beaconInfoContent.getBestBeaconInfo()?.timeout ?: 0
-        return liveLocationEventTime - beaconInfoStartTime > timeout
-    }
+    private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
index 7b5f23e2433cf7c567642a7e9c416a1759cff48b..c0be96f83d6af59a3084055df0bc7b89f6bdccce 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
@@ -18,12 +18,23 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
 
 import io.realm.Realm
 import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 
 internal interface LiveLocationAggregationProcessor {
-    fun handleLiveLocation(realm: Realm,
-                           event: Event,
-                           content: MessageLiveLocationContent,
-                           roomId: String,
-                           isLocalEcho: Boolean)
+    fun handleBeaconInfo(
+            realm: Realm,
+            event: Event,
+            content: MessageBeaconInfoContent,
+            roomId: String,
+            isLocalEcho: Boolean,
+    )
+
+    fun handleBeaconLocationData(
+            realm: Realm,
+            event: Event,
+            content: MessageBeaconLocationDataContent,
+            roomId: String,
+            isLocalEcho: Boolean,
+    )
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
index dc3ea55a019854d675f1f1a11341ee72128ed6ec..b25ef7ba0f8f357432f7d73905dc0b728bef3e13 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
@@ -52,7 +52,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
         } else if (!params.searchOnServer) {
             Optional.from(null)
         } else {
-            val description  = tryOrNull("## Failed to get roomId from alias") {
+            val description = tryOrNull("## Failed to get roomId from alias") {
                 executeRequest(globalErrorReceiver) {
                     directoryAPI.getRoomIdByAlias(params.roomAlias)
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
index 9bd15a02671487dadf0ce21d14d6f9d299a86812..6dd2c91048519738626cd6e85fc5db4e2ff72aca 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
@@ -41,6 +41,7 @@ import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelpe
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -56,7 +57,8 @@ internal class DefaultCreateRoomTask @Inject constructor(
         @SessionDatabase
         private val realmConfiguration: RealmConfiguration,
         private val createRoomBodyBuilder: CreateRoomBodyBuilder,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val clock: Clock,
 ) : CreateRoomTask {
 
     override suspend fun execute(params: CreateRoomParams): String {
@@ -106,7 +108,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
         }
 
         awaitTransaction(realmConfiguration) {
-            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
+            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis()
         }
 
         if (otherUserId != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2546c58cc7c64974d3b3a5b921651fc1614ea435
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.session.room.crypto
+
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.session.crypto.CryptoService
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
+import org.matrix.android.sdk.api.util.awaitCallback
+import org.matrix.android.sdk.internal.session.room.state.SendStateTask
+import java.security.InvalidParameterException
+
+internal class DefaultRoomCryptoService @AssistedInject constructor(
+        @Assisted private val roomId: String,
+        private val cryptoService: CryptoService,
+        private val sendStateTask: SendStateTask,
+) : RoomCryptoService {
+
+    @AssistedFactory
+    interface Factory {
+        fun create(roomId: String): DefaultRoomCryptoService
+    }
+
+    override fun isEncrypted(): Boolean {
+        return cryptoService.isRoomEncrypted(roomId)
+    }
+
+    override fun encryptionAlgorithm(): String? {
+        return cryptoService.getEncryptionAlgorithm(roomId)
+    }
+
+    override fun shouldEncryptForInvitedMembers(): Boolean {
+        return cryptoService.shouldEncryptForInvitedMembers(roomId)
+    }
+
+    override suspend fun prepareToEncrypt() {
+        awaitCallback<Unit> {
+            cryptoService.prepareToEncrypt(roomId, it)
+        }
+    }
+
+    override suspend fun enableEncryption(algorithm: String, force: Boolean) {
+        when {
+            (!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> {
+                throw IllegalStateException("Encryption is already enabled for this room")
+            }
+            (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM)                              -> {
+                throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")
+            }
+            else                                                                            -> {
+                val params = SendStateTask.Params(
+                        roomId = roomId,
+                        stateKey = "",
+                        eventType = EventType.STATE_ROOM_ENCRYPTION,
+                        body = mapOf(
+                                "algorithm" to algorithm
+                        )
+                )
+
+                sendStateTask.execute(params)
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
index 70ba9287a2a37dee85f8f0c5c16463a80f774f4e..d3d1cb856a05e970a85ac891dbc730058d1f9475 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
@@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -61,7 +62,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
         private val roomMemberEventHandler: RoomMemberEventHandler,
         private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
         private val deviceListManager: DeviceListManager,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val clock: Clock,
 ) : LoadRoomMembersTask {
 
     override suspend fun execute(params: LoadRoomMembersTask.Params) {
@@ -107,7 +109,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
             // We ignore all the already known members
             val roomEntity = RoomEntity.where(realm, roomId).findFirst()
                     ?: realm.createObject(roomId)
-            val now = System.currentTimeMillis()
+            val now = clock.epochMillis()
             for (roomMemberEvent in response.roomMemberEvents) {
                 if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) {
                     continue
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
index f883cc33ec6cd87e0ef3c43ccf793974e365dfb0..6306f3c6fee3b8a126d5e7cea13ae2fabbfc22c2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
 import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
 import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.time.Clock
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -53,7 +54,8 @@ internal class DefaultJoinRoomTask @Inject constructor(
         @SessionDatabase
         private val realmConfiguration: RealmConfiguration,
         private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val clock: Clock,
 ) : JoinRoomTask {
 
     override suspend fun execute(params: JoinRoomTask.Params) {
@@ -90,7 +92,7 @@ internal class DefaultJoinRoomTask @Inject constructor(
             throw JoinRoomFailure.JoinedWithTimeout
         }
         awaitTransaction(realmConfiguration) {
-            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
+            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis()
         }
         setReadMarkers(roomId)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
index 8f1aefb731efb7e70d28a8479fd2776389bb6b4a..85f53e13469ebc527500e24f6bf47dad8a8df37c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
@@ -22,7 +22,7 @@ import com.zhuinden.monarchy.Monarchy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import org.matrix.android.sdk.api.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
@@ -32,7 +32,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
 internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                       private val setRoomNotificationStateTask: SetRoomNotificationStateTask,
                                                                       @SessionDatabase private val monarchy: Monarchy) :
-    RoomPushRuleService {
+        RoomPushRuleService {
 
     @AssistedFactory
     interface Factory {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
index d2c0d13b5160e0a4eb7654bd921108c2a90ad35b..2c74e2a1e5d48e68ca40b03e281e8b42ef04ef77 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.room.notification
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 
 internal data class RoomPushRule(
         val kind: RuleKind,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
index 86d2e6c619f4fbb15fa2701ad79c9bdeb0ac9fe6..a5a5ab58ba1736718094dd9e340904b1921351df 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
@@ -16,13 +16,13 @@
 
 package org.matrix.android.sdk.internal.session.room.notification
 
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.toJson
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
index feb8c27b09e332bd816122e8d3626f443833c128..021d7dbefb104ce7734f3b5fc2f2d2d83139ce91 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.room.notification
 
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
-import org.matrix.android.sdk.api.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.query.where
@@ -38,7 +38,7 @@ internal interface SetRoomNotificationStateTask : Task<SetRoomNotificationStateT
 internal class DefaultSetRoomNotificationStateTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
                                                                        private val removePushRuleTask: RemovePushRuleTask,
                                                                        private val addPushRuleTask: AddPushRuleTask) :
-    SetRoomNotificationStateTask {
+        SetRoomNotificationStateTask {
 
     override suspend fun execute(params: SetRoomNotificationStateTask.Params) {
         val currentRoomPushRule = Realm.getInstance(monarchy.realmConfiguration).use {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
index 64f1cc34f12896ac21d39c241ae93c2a142e2527..a124a8a4c2509ffe6159717b6f14d552bc98c608 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHand
 import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 import kotlin.collections.set
@@ -58,7 +59,8 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
         private val roomFullyReadHandler: RoomFullyReadHandler,
         private val readReceiptHandler: ReadReceiptHandler,
         @UserId private val userId: String,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val clock: Clock,
 ) : SetReadMarkersTask {
 
     override suspend fun execute(params: SetReadMarkersTask.Params) {
@@ -125,7 +127,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
                 roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId))
             }
             if (readReceiptId != null) {
-                val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId)
+                val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis())
                 readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null)
             }
             if (shouldUpdateRoomSummary) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
index b54cd71e5082a612ec115b1dd43057e6c45ee834..7bf7d6b587584285cdfc697337b9730c7f424377 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
@@ -27,12 +27,16 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class EventEditor @Inject constructor(private val eventSenderProcessor: EventSenderProcessor,
-                                               private val eventFactory: LocalEchoEventFactory,
-                                               private val localEchoRepository: LocalEchoRepository) {
+internal class EventEditor @Inject constructor(
+        private val eventSenderProcessor: EventSenderProcessor,
+        private val eventFactory: LocalEchoEventFactory,
+        private val localEchoRepository: LocalEchoRepository,
+        private val clock: Clock,
+) {
 
     fun editTextMessage(targetEvent: TimelineEvent,
                         msgType: String,
@@ -126,7 +130,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
     }
 
     private fun updateFailedEchoWithEvent(roomId: String, failedEchoEventId: String, editedEvent: Event) {
-        val editedEventEntity = editedEvent.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis())
+        val editedEventEntity = editedEvent.toEntity(roomId, SendState.UNSENT, clock.epochMillis())
         localEchoRepository.updateEchoAsync(failedEchoEventId) { _, entity ->
             entity.content = editedEventEntity.content
             entity.ageLocalTs = editedEventEntity.ageLocalTs
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt
index b596f2288ec82f849ca708b5a0a7225ea83cff78..c5f9bd13fd59b889ea18b5e2ab6597a63a22d6df 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -55,12 +56,14 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val cryptoService: DefaultCryptoService,
         @UserId private val userId: String,
+        private val clock: Clock,
 ) : FetchThreadSummariesTask {
 
     override suspend fun execute(params: FetchThreadSummariesTask.Params): Result {
         val filter = FilterFactory.createThreadsFilter(
                 numberOfEvents = params.limit,
-                userId = if (params.isUserParticipating) userId else null).toJSONString()
+                userId = if (params.isUserParticipating) userId else null
+        ).toJSONString()
 
         val response = executeRequest(
                 globalErrorReceiver,
@@ -94,7 +97,9 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
                         roomMemberContentsByUser = roomMemberContentsByUser,
                         roomEntity = roomEntity,
                         userId = userId,
-                        cryptoService = cryptoService)
+                        cryptoService = cryptoService,
+                        currentTimeMillis = clock.epochMillis(),
+                )
             }
         }
         return Result.SUCCESS
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
index 116d4aa0a1ab5f427fe0e1e8737e8b13a8cdb633..8d35a8fea4cb4fec267ee82051fa85baa78d9f33 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.send.SendState
-import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
 import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
 import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
 import org.matrix.android.sdk.internal.database.mapper.asDomain
@@ -42,7 +41,6 @@ import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.getOrNull
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
@@ -51,6 +49,7 @@ import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -85,10 +84,9 @@ internal interface FetchThreadTimelineTask : Task<FetchThreadTimelineTask.Params
 internal class DefaultFetchThreadTimelineTask @Inject constructor(
         private val roomAPI: RoomAPI,
         private val globalErrorReceiver: GlobalErrorReceiver,
-        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
         @SessionDatabase private val monarchy: Monarchy,
-        @UserId private val userId: String,
-        private val cryptoService: DefaultCryptoService
+        private val cryptoService: DefaultCryptoService,
+        private val clock: Clock,
 ) : FetchThreadTimelineTask {
 
     enum class Result {
@@ -156,7 +154,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
                             eventEntity = eventEntity,
                             direction = PaginationDirection.FORWARDS,
                             ownedByThreadChunk = true,
-                            roomMemberContentsByUser = roomMemberContentsByUser)
+                            roomMemberContentsByUser = roomMemberContentsByUser
+                    )
                 }
             }
 
@@ -178,7 +177,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
                             eventEntity = eventEntity,
                             direction = PaginationDirection.FORWARDS,
                             ownedByThreadChunk = true,
-                            roomMemberContentsByUser = roomMemberContentsByUser)
+                            roomMemberContentsByUser = roomMemberContentsByUser
+                    )
                 }
             }
         }
@@ -207,7 +207,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
      * Create an EventEntity to be added in the TimelineEventEntity
      */
     private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity {
-        val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+        val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it }
         return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index bb16563f96ac622d8ca9057040d30d5de4d2517e..d019ffada62a2a6815b13eb72014314b746fe25a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -37,13 +37,13 @@ import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
 import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
 import org.matrix.android.sdk.api.session.room.model.message.LocationInfo
 import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
 import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
 import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
@@ -70,8 +70,8 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor
 import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
 import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
+import org.matrix.android.sdk.internal.util.time.Clock
 import java.util.UUID
-import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
 /**
@@ -91,7 +91,8 @@ internal class LocalEchoEventFactory @Inject constructor(
         private val thumbnailExtractor: ThumbnailExtractor,
         private val waveformSanitizer: WaveFormSanitizer,
         private val localEchoRepository: LocalEchoRepository,
-        private val permalinkFactory: PermalinkFactory
+        private val permalinkFactory: PermalinkFactory,
+        private val clock: Clock,
 ) {
     fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event {
         if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) {
@@ -124,7 +125,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                                newBodyAutoMarkdown: Boolean,
                                msgType: String,
                                compatibilityText: String): Event {
-        return createMessageEvent(roomId,
+        return createMessageEvent(
+                roomId,
                 MessageTextContent(
                         msgType = msgType,
                         body = compatibilityText,
@@ -132,7 +134,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                         newContent = createTextContent(newBodyText, newBodyAutoMarkdown)
                                 .toMessageTextContent(msgType)
                                 .toContent()
-                ))
+                )
+        )
     }
 
     private fun createPollContent(question: String,
@@ -188,7 +191,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.POLL_RESPONSE.first(),
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     fun createPollEvent(roomId: String,
@@ -204,7 +208,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.POLL_START.first(),
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     fun createEndPollEvent(roomId: String,
@@ -223,7 +228,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.POLL_END.first(),
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     fun createLocationEvent(roomId: String,
@@ -238,7 +244,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                 body = geoUri,
                 unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
                 unstableLocationAsset = LocationAsset(type = assetType),
-                unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()),
+                unstableTimestampMillis = clock.epochMillis(),
                 unstableText = geoUri
         )
         return createMessageEvent(roomId, content)
@@ -250,14 +256,14 @@ internal class LocalEchoEventFactory @Inject constructor(
                                 longitude: Double,
                                 uncertainty: Double?): Event {
         val geoUri = buildGeoUri(latitude, longitude, uncertainty)
-        val content = MessageLiveLocationContent(
+        val content = MessageBeaconLocationDataContent(
                 body = geoUri,
                 relatesTo = RelationDefaultContent(
                         type = RelationType.REFERENCE,
                         eventId = beaconInfoEventId
                 ),
                 unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
-                unstableTimestampAsMilliseconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()),
+                unstableTimestampMillis = clock.epochMillis(),
         )
         val localId = LocalEcho.createLocalEchoId()
         return Event(
@@ -267,7 +273,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.BEACON_LOCATION_DATA.first(),
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     fun createReplaceTextOfReply(roomId: String,
@@ -297,7 +304,8 @@ internal class LocalEchoEventFactory @Inject constructor(
         //
         val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
 
-        return createMessageEvent(roomId,
+        return createMessageEvent(
+                roomId,
                 MessageTextContent(
                         msgType = msgType,
                         body = compatibilityText,
@@ -309,7 +317,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                                 formattedBody = replyFormatted
                         )
                                 .toContent()
-                ))
+                )
+        )
     }
 
     fun createMediaEvent(roomId: String,
@@ -341,7 +350,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.REACTION,
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
@@ -532,12 +542,14 @@ internal class LocalEchoEventFactory @Inject constructor(
                 content.toThreadTextContent(
                         rootThreadEventId = rootThreadEventId,
                         latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
-                        msgType = msgType)
-                        .toContent())
+                        msgType = msgType
+                )
+                        .toContent()
+        )
     }
 
     private fun dummyOriginServerTs(): Long {
-        return System.currentTimeMillis()
+        return clock.epochMillis()
     }
 
     /**
@@ -582,7 +594,9 @@ internal class LocalEchoEventFactory @Inject constructor(
                 relatesTo = generateReplyRelationContent(
                         eventId = eventId,
                         rootThreadEventId = rootThreadEventId,
-                        showInThread = showInThread))
+                        showInThread = showInThread
+                )
+        )
         return createMessageEvent(roomId, content)
     }
 
@@ -605,7 +619,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                         eventId = it,
                         isFallingBack = showInThread,
                         // False when is a rich reply from within a thread, and true when is a reply that should be visible from threads
-                        inReplyTo = ReplyToContent(eventId = eventId))
+                        inReplyTo = ReplyToContent(eventId = eventId)
+                )
             } ?: RelationDefaultContent(null, null, ReplyToContent(eventId = eventId))
 
     private fun buildFormattedReply(permalink: String, userLink: String, userId: String, bodyFormatted: String, newBodyFormatted: String): String {
@@ -740,13 +755,15 @@ internal class LocalEchoEventFactory @Inject constructor(
                             .toThreadTextContent(
                                     rootThreadEventId = rootThreadEventId,
                                     latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
-                                    msgType = MessageType.MSGTYPE_TEXT)
+                                    msgType = MessageType.MSGTYPE_TEXT
+                            )
             )
         } else {
             createFormattedTextEvent(
                     roomId,
                     markdownParser.parse(quoteText, force = true, advanced = autoMarkdown),
-                    MessageType.MSGTYPE_TEXT)
+                    MessageType.MSGTYPE_TEXT
+            )
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
index 1b1a66a1c418acae50f4e109b1403346e1e76cbf..9fd45b917fa28f3a0df38abb6994f57de30c4043 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
@@ -43,16 +43,20 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
 import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.UUID
 import javax.inject.Inject
 
-internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
-                                                       private val taskExecutor: TaskExecutor,
-                                                       private val realmSessionProvider: RealmSessionProvider,
-                                                       private val roomSummaryUpdater: RoomSummaryUpdater,
-                                                       private val timelineInput: TimelineInput,
-                                                       private val timelineEventMapper: TimelineEventMapper) {
+internal class LocalEchoRepository @Inject constructor(
+        @SessionDatabase private val monarchy: Monarchy,
+        private val taskExecutor: TaskExecutor,
+        private val realmSessionProvider: RealmSessionProvider,
+        private val roomSummaryUpdater: RoomSummaryUpdater,
+        private val timelineInput: TimelineInput,
+        private val timelineEventMapper: TimelineEventMapper,
+        private val clock: Clock,
+) {
 
     fun createLocalEcho(event: Event) {
         val roomId = event.roomId ?: throw IllegalStateException("You should have set a roomId for your event")
@@ -61,7 +65,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
         event.type ?: throw IllegalStateException("You should have set a type for your event")
 
         val timelineEventEntity = realmSessionProvider.withRealm { realm ->
-            val eventEntity = event.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis())
+            val eventEntity = event.toEntity(roomId, SendState.UNSENT, clock.epochMillis())
             val roomMemberHelper = RoomMemberHelper(realm, roomId)
             val myUser = roomMemberHelper.getLastRoomMember(senderId)
             val localId = UUID.randomUUID().mostSignificantBits
@@ -88,7 +92,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
     }
 
     fun updateSendState(eventId: String, roomId: String?, sendState: SendState, sendStateDetails: String? = null) {
-        Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
+        Timber.v("## SendEvent: [${clock.epochMillis()}] Update local state of $eventId to ${sendState.name}")
         timelineInput.onLocalEchoUpdated(roomId = roomId ?: "", eventId = eventId, sendState = sendState)
         updateEchoAsync(eventId) { realm, sendingEventEntity ->
             if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
index b59f1b174f2d16f7a321834aac1b0566bd1c10d3..ecc81492554838d1fdbebb1757003db4bc65dce3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
@@ -39,7 +39,7 @@ import javax.inject.Inject
  * Possible next worker    : None, but it will post new work to send events, encrypted or not
  */
 internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -76,10 +76,10 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
         params.localEchoIds.forEach { localEchoIds ->
             val roomId = localEchoIds.roomId
             val eventId = localEchoIds.eventId
-                localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING)
-                Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId")
-                val sendWork = createSendEventWork(params.sessionId, eventId, true)
-                timelineSendEventWorkCommon.postWork(roomId, sendWork)
+            localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING)
+            Timber.v("## SendEvent: Schedule send event $eventId")
+            val sendWork = createSendEventWork(params.sessionId, eventId, true)
+            timelineSendEventWorkCommon.postWork(roomId, sendWork)
         }
 
         return Result.success()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
index c03d1fa81e1dba968ad255ad852d6ffc63721290..83c61d28451b3718b190264df985d659e0c5bda7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
@@ -34,7 +34,7 @@ import javax.inject.Inject
  * Possible next worker    : None
  */
 internal class RedactEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
index 7f24688ecea1cb17d3570cfd900c8dee9426883d..ddbe8a61a0f0ef4a45a52933a2a5c8e9a41f824f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
@@ -40,7 +40,7 @@ import javax.inject.Inject
  * Possible next worker    : None
  */
 internal class SendEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -89,13 +89,13 @@ internal class SendEventWorker(context: Context, params: WorkerParameters, sessi
                     .also { Timber.e("Work cancelled due to input error from parent") }
         }
 
-        Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}")
+        Timber.v("## SendEvent: Send event ${params.eventId}")
         return try {
             sendEventTask.execute(SendEventTask.Params(event, params.isEncrypted ?: cryptoService.isRoomEncrypted(event.roomId)))
             Result.success()
         } catch (exception: Throwable) {
             if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
-                Timber.e("## SendEvent: [${System.currentTimeMillis()}]  Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
+                Timber.e("## SendEvent: Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
                 localEchoRepository.updateSendState(
                         eventId = event.eventId,
                         roomId = event.roomId,
@@ -104,7 +104,7 @@ internal class SendEventWorker(context: Context, params: WorkerParameters, sessi
                 )
                 Result.success()
             } else {
-                Timber.e("## SendEvent: [${System.currentTimeMillis()}]  Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
+                Timber.e("## SendEvent: Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
                 Result.retry()
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
index 5b4efa5df6fdf26ccfaddb7ff145043cafc26d7a..a1d3e2c0ac45ea670c85a38cf2f065e6c464cb24 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
@@ -191,7 +191,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
 
     private suspend fun QueuedTask.waitForNetwork() = waitForNetworkSequencer.post {
         while (!canReachServer.get()) {
-            Timber.v("## $this cannot reach server wait ts:${System.currentTimeMillis()}")
+            Timber.v("## $this cannot reach server wait for $$RETRY_WAIT_TIME_MS ms")
             delay(RETRY_WAIT_TIME_MS)
             withContext(Dispatchers.IO) {
                 val hostAvailable = HomeServerAvailabilityChecker(sessionParams).check()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
index 1ee313919401fcd72917d9d14ba048ad9bd71132..301f8cb9d67f811840fd98977e1a585894063d15 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
@@ -136,7 +136,7 @@ internal class EventSenderProcessorThread @Inject constructor(
     private var retryNoNetworkTask: TimerTask? = null
 
     override fun run() {
-        Timber.v("## SendThread started ts:${System.currentTimeMillis()}")
+        Timber.v("## SendThread started")
         try {
             while (!isInterrupted) {
                 Timber.v("## SendThread wait for task to process")
@@ -151,7 +151,7 @@ internal class EventSenderProcessorThread @Inject constructor(
                 }
                 // we check for network connectivity
                 while (!canReachServer) {
-                    Timber.v("## SendThread cannot reach server, wait ts:${System.currentTimeMillis()}")
+                    Timber.v("## SendThread cannot reach server")
                     // schedule to retry
                     waitForNetwork()
                     // if thread as been killed meanwhile
@@ -175,7 +175,7 @@ internal class EventSenderProcessorThread @Inject constructor(
                                     canReachServer = false
                                     if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed()
                                     while (!canReachServer) {
-                                        Timber.v("## SendThread retryLoop cannot reach server, wait ts:${System.currentTimeMillis()}")
+                                        Timber.v("## SendThread retryLoop cannot reach server")
                                         // schedule to retry
                                         waitForNetwork()
                                     }
@@ -218,7 +218,7 @@ internal class EventSenderProcessorThread @Inject constructor(
 //        state = State.KILLED
         // is this needed?
         retryNoNetworkTask?.cancel()
-        Timber.w("## SendThread finished ${System.currentTimeMillis()}")
+        Timber.w("## SendThread finished")
     }
 
     private fun waitForNetwork() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
index 116c8d5c6b6c8a0ddf6e2ffff9b2e02c09c7e019..545fc4173729fa91381a99c4faac96a0b091e254 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
@@ -74,7 +74,7 @@ internal class QueueMemento @Inject constructor(context: Context,
                     encrypt = task.encrypt,
                     order = order
             )
-            is RedactQueuedTask -> RedactEventTaskInfo(
+            is RedactQueuedTask    -> RedactEventTaskInfo(
                     redactionLocalEcho = task.redactionLocalEchoId,
                     order = order
             )
@@ -92,7 +92,7 @@ internal class QueueMemento @Inject constructor(context: Context,
                 ?.forEach { info ->
                     try {
                         when (info) {
-                            is SendEventTaskInfo -> {
+                            is SendEventTaskInfo   -> {
                                 localEchoRepository.getUpToDateEcho(info.localEchoId)?.let {
                                     if (it.sendState.isSending() && it.eventId != null && it.roomId != null) {
                                         localEchoRepository.updateSendState(it.eventId, it.roomId, SendState.UNSENT)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index 89d33f98d219a7d054d578e61bc9db4586064d11..e5c7a75cb6bd19e7ccf66e171344c91bc059972d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
@@ -33,8 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
-import org.matrix.android.sdk.api.session.room.model.livelocation.BeaconInfo
-import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
 import org.matrix.android.sdk.api.session.room.state.StateService
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.MimeTypes
@@ -74,14 +73,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
             eventType: String,
             stateKey: String,
             body: JsonDict
-    ) {
+    ): String {
         val params = SendStateTask.Params(
                 roomId = roomId,
                 stateKey = stateKey,
                 eventType = eventType,
                 body = body.toSafeJson(eventType)
         )
-        sendStateTask.executeRetry(params, 3)
+        return sendStateTask.executeRetry(params, 3)
     }
 
     private fun JsonDict.toSafeJson(eventType: String): JsonDict {
@@ -193,20 +192,13 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
 
     override suspend fun stopLiveLocation(userId: String) {
         getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent ->
-            beaconInfoStateEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.let { content ->
-                val beaconContent = LiveLocationBeaconContent(
-                        unstableBeaconInfo = BeaconInfo(
-                                description = content.getBestBeaconInfo()?.description,
-                                timeout = content.getBestBeaconInfo()?.timeout,
-                                isLive = false,
-                        ),
-                        unstableTimestampAsMilliseconds = System.currentTimeMillis()
-                ).toContent()
+            beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.let { content ->
+                val updatedContent = content.copy(isLive = false).toContent()
 
                 beaconInfoStateEvent.stateKey?.let {
                     sendStateEvent(
                             eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
-                            body = beaconContent,
+                            body = updatedContent,
                             stateKey = it
                     )
                 }
@@ -225,7 +217,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
                 }
                 .firstOrNull { beaconInfoEvent ->
                     !filterOnlyLive ||
-                            beaconInfoEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.getBestBeaconInfo()?.isLive.orFalse()
+                            beaconInfoEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.isLive.orFalse()
                 }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
index 197b4f8688c7d38cf244785b21bb39d96bd40df3..1f2ec09367d4c996c70445b6dab0a27bf9ff3181 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
@@ -51,7 +51,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
                         usersDefault = content.usersDefault,
                         users = content.users,
                         stateDefault = content.stateDefault,
-                        notifications = content.notifications?.mapValues { content.notificationLevel(it.key)  }
+                        notifications = content.notifications?.mapValues { content.notificationLevel(it.key) }
                 )
             }
             ?.toContent()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
index 56c69a05a682e7cbcb89763d586ed4317d15be55..59c9de29328b6d634bbcebc7552e8f477001737d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
@@ -21,9 +21,10 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
 import javax.inject.Inject
 
-internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
+internal interface SendStateTask : Task<SendStateTask.Params, String> {
     data class Params(
             val roomId: String,
             val stateKey: String,
@@ -37,9 +38,9 @@ internal class DefaultSendStateTask @Inject constructor(
         private val globalErrorReceiver: GlobalErrorReceiver
 ) : SendStateTask {
 
-    override suspend fun execute(params: SendStateTask.Params) {
+    override suspend fun execute(params: SendStateTask.Params): String {
         return executeRequest(globalErrorReceiver) {
-            if (params.stateKey.isEmpty()) {
+            val response = if (params.stateKey.isEmpty()) {
                 roomAPI.sendStateEvent(
                         roomId = params.roomId,
                         stateEventType = params.eventType,
@@ -53,6 +54,9 @@ internal class DefaultSendStateTask @Inject constructor(
                         params = params.body
                 )
             }
+            response.eventId.also {
+                Timber.d("State event: $it just sent in room ${params.roomId}")
+            }
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
index e3a215445db74897fe080aa22a32d705d8edb7a5..52879d71219264e422502d08e3dab0f163e54566 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
@@ -101,11 +101,11 @@ internal class Graph {
                         // it's a candidate
                         destination = it.destination
                     }
-                    inPath -> {
+                    inPath     -> {
                         // Cycle!!
                         backwardEdges.add(it)
                     }
-                    completed -> {
+                    completed  -> {
                         // dead end
                     }
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index 18a4f80547cb23e23914a686d466b8967bd0994c..96e8d3c73f451c8ea1fa2c412258df8710ff3a83 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -26,7 +26,6 @@ import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.query.ActiveSpaceFilter
 import org.matrix.android.sdk.api.query.RoomCategoryFilter
 import org.matrix.android.sdk.api.query.isNormalized
@@ -43,7 +42,6 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification
 import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
-import org.matrix.android.sdk.internal.database.RealmSessionProvider
 import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@@ -57,10 +55,8 @@ import javax.inject.Inject
 
 internal class RoomSummaryDataSource @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
-        private val realmSessionProvider: RealmSessionProvider,
         private val roomSummaryMapper: RoomSummaryMapper,
         private val queryStringValueProcessor: QueryStringValueProcessor,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers
 ) {
 
     fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 5064ebf49b3d36696726277fef5c5a95309f5ca3..18fbe73c098e58b831eed28eb4caacd2df24e9e9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -43,28 +43,32 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHand
 import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
 import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
 import org.matrix.android.sdk.internal.util.createBackgroundHandler
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.UUID
 import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicReference
 
-internal class DefaultTimeline(private val roomId: String,
-                               private val initialEventId: String?,
-                               private val realmConfiguration: RealmConfiguration,
-                               private val loadRoomMembersTask: LoadRoomMembersTask,
-                               private val readReceiptHandler: ReadReceiptHandler,
-                               private val settings: TimelineSettings,
-                               private val coroutineDispatchers: MatrixCoroutineDispatchers,
-                               paginationTask: PaginationTask,
-                               getEventTask: GetContextOfEventTask,
-                               fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
-                               fetchThreadTimelineTask: FetchThreadTimelineTask,
-                               timelineEventMapper: TimelineEventMapper,
-                               timelineInput: TimelineInput,
-                               threadsAwarenessHandler: ThreadsAwarenessHandler,
-                               lightweightSettingsStorage: LightweightSettingsStorage,
-                               eventDecryptor: TimelineEventDecryptor) : Timeline {
+internal class DefaultTimeline(
+        private val roomId: String,
+        private val initialEventId: String?,
+        private val realmConfiguration: RealmConfiguration,
+        private val loadRoomMembersTask: LoadRoomMembersTask,
+        private val readReceiptHandler: ReadReceiptHandler,
+        private val settings: TimelineSettings,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
+        paginationTask: PaginationTask,
+        getEventTask: GetContextOfEventTask,
+        fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
+        fetchThreadTimelineTask: FetchThreadTimelineTask,
+        timelineEventMapper: TimelineEventMapper,
+        timelineInput: TimelineInput,
+        threadsAwarenessHandler: ThreadsAwarenessHandler,
+        lightweightSettingsStorage: LightweightSettingsStorage,
+        eventDecryptor: TimelineEventDecryptor,
+) : Timeline {
 
     companion object {
         val BACKGROUND_HANDLER = createBackgroundHandler("DefaultTimeline_Thread")
@@ -100,6 +104,7 @@ internal class DefaultTimeline(private val roomId: String,
             threadsAwarenessHandler = threadsAwarenessHandler,
             lightweightSettingsStorage = lightweightSettingsStorage,
             onEventsUpdated = this::sendSignalToPostSnapshot,
+            onEventsDeleted = this::onEventsDeleted,
             onLimitedTimeline = this::onLimitedTimeline,
             onNewTimelineEvents = this::onNewTimelineEvents
     )
@@ -304,6 +309,12 @@ internal class DefaultTimeline(private val roomId: String,
         }
     }
 
+    private fun onEventsDeleted() {
+        // Some event have been deleted, for instance when a user has been ignored.
+        // Restart the timeline (live)
+        restartWithEventId(null)
+    }
+
     private suspend fun postSnapshot() {
         val snapshot = strategy.buildSnapshot()
         Timber.v("Post snapshot of ${snapshot.size} events")
@@ -363,7 +374,8 @@ internal class DefaultTimeline(private val roomId: String,
                 roomId = roomId,
                 timelineId = timelineID,
                 mode = mode,
-                dependencies = strategyDependencies
+                dependencies = strategyDependencies,
+                clock = clock,
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
index 826c9d7c48ef60800c13cdd3db5e355ba61d557d..849d7bd7ab6b195b244b3173dbec48a4a5757620 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTa
 import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
 import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
 import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
+import org.matrix.android.sdk.internal.util.time.Clock
 
 internal class DefaultTimelineService @AssistedInject constructor(
         @Assisted private val roomId: String,
@@ -50,8 +51,9 @@ internal class DefaultTimelineService @AssistedInject constructor(
         private val lightweightSettingsStorage: LightweightSettingsStorage,
         private val readReceiptHandler: ReadReceiptHandler,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val timelineEventDataSource: TimelineEventDataSource
-) : TimelineService {
+        private val timelineEventDataSource: TimelineEventDataSource,
+        private val clock: Clock,
+        ) : TimelineService {
 
     @AssistedFactory
     interface Factory {
@@ -75,7 +77,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
                 readReceiptHandler = readReceiptHandler,
                 getEventTask = contextOfEventTask,
                 threadsAwarenessHandler = threadsAwarenessHandler,
-                lightweightSettingsStorage = lightweightSettingsStorage
+                lightweightSettingsStorage = lightweightSettingsStorage,
+                clock = clock
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
index 5bca5118b82e20478569e6a7500348a37ee4f28b..aef9e24c8b912d7a80dc02a082a55fe81c839a35 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal interface GetEventTask : Task<GetEventTask.Params, Event> {
@@ -36,7 +37,8 @@ internal interface GetEventTask : Task<GetEventTask.Params, Event> {
 internal class DefaultGetEventTask @Inject constructor(
         private val roomAPI: RoomAPI,
         private val globalErrorReceiver: GlobalErrorReceiver,
-        private val eventDecryptor: EventDecryptor
+        private val eventDecryptor: EventDecryptor,
+        private val clock: Clock,
 ) : GetEventTask {
 
     override suspend fun execute(params: GetEventTask.Params): Event {
@@ -59,7 +61,7 @@ internal class DefaultGetEventTask @Inject constructor(
                     }
         }
 
-        event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+        event.ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it }
 
         return event
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
index 64b1a4ff1d5df72056367b388f826eb90df2776f..e765e05578833c577c1fdcc9955318307ca04704 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
@@ -41,7 +41,7 @@ internal class LiveTimelineEvent(private val monarchy: Monarchy,
                                  private val timelineEventMapper: TimelineEventMapper,
                                  private val roomId: String,
                                  private val eventId: String) :
-    MediatorLiveData<Optional<TimelineEvent>>() {
+        MediatorLiveData<Optional<TimelineEvent>>() {
 
     init {
         buildAndObserveQuery()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
index ff986d04af15e43262112340518d49bff5c76c6d..bcf202962c9399a88c8d51b14948137e555c5e31 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
@@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThre
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
 import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.concurrent.atomic.AtomicReference
 
@@ -53,11 +54,13 @@ import java.util.concurrent.atomic.AtomicReference
  * Once we got a ChunkEntity we wrap it with TimelineChunk class so we dispatch any methods for loading data.
  */
 
-internal class LoadTimelineStrategy(
+internal class LoadTimelineStrategy constructor(
         private val roomId: String,
         private val timelineId: String,
         private val mode: Mode,
-        private val dependencies: Dependencies) {
+        private val dependencies: Dependencies,
+        clock: Clock,
+) {
 
     sealed interface Mode {
         object Live : Mode
@@ -95,6 +98,7 @@ internal class LoadTimelineStrategy(
             val threadsAwarenessHandler: ThreadsAwarenessHandler,
             val lightweightSettingsStorage: LightweightSettingsStorage,
             val onEventsUpdated: (Boolean) -> Unit,
+            val onEventsDeleted: () -> Unit,
             val onLimitedTimeline: () -> Unit,
             val onNewTimelineEvents: (List<String>) -> Unit
     )
@@ -152,7 +156,7 @@ internal class LoadTimelineStrategy(
         }
     }
 
-    private val uiEchoManager = UIEchoManager(uiEchoManagerListener)
+    private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock)
     private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource(
             roomId = roomId,
             realm = dependencies.realm,
@@ -302,7 +306,8 @@ internal class LoadTimelineStrategy(
                     threadsAwarenessHandler = dependencies.threadsAwarenessHandler,
                     lightweightSettingsStorage = dependencies.lightweightSettingsStorage,
                     initialEventId = mode.originEventId(),
-                    onBuiltEvents = dependencies.onEventsUpdated
+                    onBuiltEvents = dependencies.onEventsUpdated,
+                    onEventsDeleted = dependencies.onEventsDeleted,
             )
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index 4ead1d4ecba2805ca9e9622fd7dffdd1254b6cf0..27f4245b22803d070865454db93e5478247bab35 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -49,21 +49,24 @@ import java.util.concurrent.atomic.AtomicBoolean
  * It does mainly listen to the db timeline events.
  * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any.
  */
-internal class TimelineChunk(private val chunkEntity: ChunkEntity,
-                             private val timelineSettings: TimelineSettings,
-                             private val roomId: String,
-                             private val timelineId: String,
-                             private val fetchThreadTimelineTask: FetchThreadTimelineTask,
-                             private val eventDecryptor: TimelineEventDecryptor,
-                             private val paginationTask: PaginationTask,
-                             private val realmConfiguration: RealmConfiguration,
-                             private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
-                             private val timelineEventMapper: TimelineEventMapper,
-                             private val uiEchoManager: UIEchoManager? = null,
-                             private val threadsAwarenessHandler: ThreadsAwarenessHandler,
-                             private val lightweightSettingsStorage: LightweightSettingsStorage,
-                             private val initialEventId: String?,
-                             private val onBuiltEvents: (Boolean) -> Unit) {
+internal class TimelineChunk(
+        private val chunkEntity: ChunkEntity,
+        private val timelineSettings: TimelineSettings,
+        private val roomId: String,
+        private val timelineId: String,
+        private val fetchThreadTimelineTask: FetchThreadTimelineTask,
+        private val eventDecryptor: TimelineEventDecryptor,
+        private val paginationTask: PaginationTask,
+        private val realmConfiguration: RealmConfiguration,
+        private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
+        private val timelineEventMapper: TimelineEventMapper,
+        private val uiEchoManager: UIEchoManager?,
+        private val threadsAwarenessHandler: ThreadsAwarenessHandler,
+        private val lightweightSettingsStorage: LightweightSettingsStorage,
+        private val initialEventId: String?,
+        private val onBuiltEvents: (Boolean) -> Unit,
+        private val onEventsDeleted: () -> Unit,
+) {
 
     private val isLastForward = AtomicBoolean(chunkEntity.isLastForward)
     private val isLastBackward = AtomicBoolean(chunkEntity.isLastBackward)
@@ -505,6 +508,11 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         if (insertions.isNotEmpty() || modifications.isNotEmpty()) {
             onBuiltEvents(true)
         }
+
+        val deletions = changeSet.deletions
+        if (deletions.isNotEmpty()) {
+            onEventsDeleted()
+        }
     }
 
     private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {
@@ -543,7 +551,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
                 threadsAwarenessHandler = threadsAwarenessHandler,
                 lightweightSettingsStorage = lightweightSettingsStorage,
                 initialEventId = null,
-                onBuiltEvents = this.onBuiltEvents
+                onBuiltEvents = this.onBuiltEvents,
+                onEventsDeleted = this.onEventsDeleted
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
index 638866a46efbf7865c45387301dbc5dc1fd7efec..8b58d3ca5c111064da10d2b813a68e80724b2a24 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.timeline
 import androidx.lifecycle.LiveData
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Sort
-import io.realm.kotlin.where
 import org.matrix.android.sdk.api.session.events.model.isImageMessage
 import org.matrix.android.sdk.api.session.events.model.isVideoMessage
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -29,6 +28,7 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 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.where
+import org.matrix.android.sdk.internal.database.query.whereRoomId
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import javax.inject.Inject
@@ -53,8 +53,7 @@ internal class TimelineEventDataSource @Inject constructor(private val realmSess
     fun getAttachmentMessages(roomId: String): List<TimelineEvent> {
         // TODO pretty bad query.. maybe we should denormalize clear type in base?
         return realmSessionProvider.withRealm { realm ->
-            realm.where<TimelineEventEntity>()
-                    .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
+            TimelineEventEntity.whereRoomId(realm, roomId)
                     .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
                     .findAll()
                     ?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index 5c30dc20d9df8e44ef8dc366feb0633ea2fd7b0c..de79661de05d5d7bb734d856ab092b706a7f71a9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -99,9 +99,7 @@ internal class TimelineEventDecryptor @Inject constructor(
         executor?.execute {
             Realm.getInstance(realmConfiguration).use { realm ->
                 try {
-                    runBlocking {
-                        processDecryptRequest(request, realm)
-                    }
+                    processDecryptRequest(request, realm)
                 } catch (e: InterruptedException) {
                     Timber.i("Decryption got interrupted")
                 }
@@ -121,7 +119,7 @@ internal class TimelineEventDecryptor @Inject constructor(
         }
     }
 
-    private suspend fun processDecryptRequest(request: DecryptionRequest, realm: Realm) {
+    private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) {
         val event = request.event
         val timelineId = request.timelineId
 
@@ -132,7 +130,8 @@ internal class TimelineEventDecryptor @Inject constructor(
             return
         }
         try {
-            val result = cryptoService.decryptEvent(request.event, timelineId)
+            // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
+            val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) }
             Timber.v("Successfully decrypted event ${event.eventId}")
             realm.executeTransaction {
                 val eventId = event.eventId ?: return@executeTransaction
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index d3f24a8568a4a8ba5107f20668890b826019932f..e5dd8aab304bd88ce008425b706e5eb9a494f666 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.StreamEventsManager
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -54,7 +55,9 @@ internal class TokenChunkEventPersistor @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         @UserId private val userId: String,
         private val lightweightSettingsStorage: LightweightSettingsStorage,
-        private val liveEventManager: Lazy<StreamEventsManager>) {
+        private val liveEventManager: Lazy<StreamEventsManager>,
+        private val clock: Clock,
+) {
 
     enum class Result {
         SHOULD_FETCH_MORE,
@@ -166,7 +169,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
         val eventList = receivedChunk.events
         val stateEvents = receivedChunk.stateEvents
 
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
 
         stateEvents?.forEach { stateEvent ->
             val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
index bb926232493733b2f3d5e4203b2d524f2b017c14..828e01955a0ab6cedc2d744f1ac249bf1f30b7ae 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
@@ -24,10 +24,14 @@ import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
 import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.Collections
 
-internal class UIEchoManager(private val listener: Listener) {
+internal class UIEchoManager(
+        private val listener: Listener,
+        private val clock: Clock,
+) {
 
     interface Listener {
         fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean
@@ -98,9 +102,7 @@ internal class UIEchoManager(private val listener: Listener) {
         val relatedEventID = timelineEvent.eventId
         val contents = inMemoryReactions[relatedEventID] ?: return timelineEvent
 
-        var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary(
-                relatedEventID
-        )
+        var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary()
         val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList()
 
         contents.forEach { uiEchoReaction ->
@@ -111,7 +113,7 @@ internal class UIEchoManager(private val listener: Listener) {
                         key = uiEchoReaction.reaction,
                         count = 1,
                         addedByMe = true,
-                        firstTimestamp = System.currentTimeMillis(),
+                        firstTimestamp = clock.epochMillis(),
                         sourceEvents = emptyList(),
                         localEchoEvents = listOf(uiEchoReaction.localEchoId)
                 ).let { updateReactions.add(it) }
@@ -145,7 +147,7 @@ internal class UIEchoManager(private val listener: Listener) {
     fun updateSentStateWithUiEcho(timelineEvent: TimelineEvent): TimelineEvent {
         if (timelineEvent.root.sendState.isSent()) return timelineEvent
         val inMemoryState = inMemorySendingStates[timelineEvent.eventId] ?: return timelineEvent
-        // Timber.v("## ${System.currentTimeMillis()} Send event refresh echo with live state $inMemoryState from state ${element.root.sendState}")
+        // Timber.v("## ${clock.epochMillis()} Send event refresh echo with live state $inMemoryState from state ${element.root.sendState}")
         return timelineEvent.copy(
                 root = timelineEvent.root.copyAll()
                         .also { it.sendState = inMemoryState }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
index 3ba7d11c3df2b7d7d9eeb235f06385680a8c1297..fcaf3b60a761a1f156d91fb5c4d2dc06eeaa3432 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
@@ -87,7 +87,7 @@ internal class DefaultSearchTask @Inject constructor(
                 results = searchCategories.roomEvents?.results?.map { searchResponseItem ->
 
                     val localThreadEventDetails = localTimelineEvents
-                            ?.firstOrNull { it.eventId ==  searchResponseItem.event.eventId }
+                            ?.firstOrNull { it.eventId == searchResponseItem.event.eventId }
                             ?.root
                             ?.asDomain()
                             ?.threadDetails
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
index 303eda49d89d216e94a4faab02fd5add6e53ee00..178a29a5a01c06ea7d31de8142ad173c18b53be4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
@@ -60,7 +60,7 @@ internal class DefaultSpace(
                         }
                         ?: viaParameterFinder.computeViaParams(roomId, 3))
 
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
@@ -79,7 +79,7 @@ internal class DefaultSpace(
 //                return
 
         // edit state event and set via to null
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
@@ -91,19 +91,19 @@ internal class DefaultSpace(
     }
 
     override fun getChildInfo(roomId: String): SpaceChildContent? {
-        return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+        return room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
                 .firstOrNull()
                 ?.content.toModel<SpaceChildContent>()
     }
 
     override suspend fun setChildrenOrder(roomId: String, order: String?) {
-        val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+        val existing = room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
                 .firstOrNull()
                 ?.content.toModel<SpaceChildContent>()
                 ?: throw IllegalArgumentException("$roomId is not a child of this space")
 
         // edit state event and set via to null
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
@@ -140,7 +140,7 @@ internal class DefaultSpace(
 //    }
 
     override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) {
-        val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+        val existing = room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
                 .firstOrNull()
                 ?.content.toModel<SpaceChildContent>()
                 ?: throw IllegalArgumentException("$roomId is not a child of this space")
@@ -150,7 +150,7 @@ internal class DefaultSpace(
             return
         }
         // edit state event and set via to null
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
index 355039b22c704d8dce8038764f60b7e362a3cd0b..9320665688af7281e01c0182aa683be5e3516b35 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.RoomSortOrder
+import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
@@ -258,7 +259,7 @@ internal class DefaultSpaceService @Inject constructor(
         val room = roomGetter.getRoom(childRoomId)
                 ?: throw IllegalArgumentException("Unknown Room $childRoomId")
 
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_PARENT,
                 stateKey = parentSpaceId,
                 body = SpaceParentContent(
@@ -276,7 +277,7 @@ internal class DefaultSpaceService @Inject constructor(
         if (existingEvent != null) {
             // Should i check if it was sent by me?
             // we don't check power level, it will throw if you cannot do that
-            room.sendStateEvent(
+            room.stateService().sendStateEvent(
                     eventType = EventType.STATE_SPACE_PARENT,
                     stateKey = parentSpaceId,
                     body = SpaceParentContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
index bd20ada28b03e9d6eeaf7f484b518732d08d8ed3..8e0c3422b933d81806fdfa9126a07f30b19fa6fa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
@@ -20,6 +20,7 @@ import com.squareup.moshi.JsonClass
 import okio.buffer
 import okio.source
 import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.io.File
 
@@ -46,7 +47,10 @@ internal interface InitialSyncStatusRepository {
 /**
  * This class handle the current status of an initial sync and persist it on the disk, to be robust against crash
  */
-internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncStatusRepository {
+internal class FileInitialSyncStatusRepository(
+        directory: File,
+        private val clock: Clock,
+) : InitialSyncStatusRepository {
 
     companion object {
         // After 2 hours, we consider that the downloaded file is outdated:
@@ -64,7 +68,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta
         ensureCache()
         val state = cache?.step ?: InitialSyncStatus.STEP_INIT
         return if (state >= InitialSyncStatus.STEP_DOWNLOADED &&
-                System.currentTimeMillis() > (cache?.downloadedDate ?: 0) + INIT_SYNC_FILE_LIFETIME) {
+                clock.epochMillis() > (cache?.downloadedDate ?: 0) + INIT_SYNC_FILE_LIFETIME) {
             Timber.d("INIT_SYNC downloaded file is outdated, download it again")
             // The downloaded file is outdated
             setStep(InitialSyncStatus.STEP_INIT)
@@ -79,7 +83,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta
         if (step == InitialSyncStatus.STEP_DOWNLOADED) {
             // Also store the downloaded date
             newStatus = newStatus.copy(
-                    downloadedDate = System.currentTimeMillis()
+                    downloadedDate = clock.epochMillis()
             )
         }
         cache = newStatus
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt
index 4f1fe43b7ddcc24dee4b7b7548055c8193f8ac04..42cd972e0cf93bda572d5ed58d801feba5bfcc20 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt
@@ -34,11 +34,12 @@ internal enum class SyncPresence(val value: String) {
     companion object {
         fun from(presenceEnum: PresenceEnum): SyncPresence {
             return when (presenceEnum) {
-                PresenceEnum.ONLINE -> Online
-                PresenceEnum.OFFLINE -> Offline
+                PresenceEnum.ONLINE      -> Online
+                PresenceEnum.OFFLINE     -> Offline
                 PresenceEnum.UNAVAILABLE -> Unavailable
             }
         }
+
         fun from(s: String?): SyncPresence? = values().find { it.value == s }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 97850e81d32084ea8a5fac97043359aa46cc4694..02a7a9a37fe13aef18d645a7e732b530c0cbb5a2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync
 
 import androidx.work.ExistingPeriodicWorkPolicy
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.PushRuleService
-import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.initsync.InitSyncStep
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
@@ -34,7 +34,7 @@ import org.matrix.android.sdk.internal.session.dispatchTo
 import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
 import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
 import org.matrix.android.sdk.internal.session.initsync.reportSubtask
-import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask
+import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask
 import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler
 import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler
 import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
index b56f89774983a8e453e79b4cc16aed6c6974080a..f88d9731015b2fc504ceda7e0b0782a82532a264 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
@@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseP
 import org.matrix.android.sdk.internal.session.user.UserStore
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.logDuration
+import org.matrix.android.sdk.internal.util.time.Clock
 import retrofit2.Response
 import retrofit2.awaitResponse
 import timber.log.Timber
@@ -78,11 +79,12 @@ internal class DefaultSyncTask @Inject constructor(
         @SessionFilesDirectory
         private val fileDirectory: File,
         private val syncResponseParser: InitialSyncResponseParser,
-        private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore
+        private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore,
+        private val clock: Clock,
 ) : SyncTask {
 
     private val workingDir = File(fileDirectory, "is")
-    private val initialSyncStatusRepository: InitialSyncStatusRepository = FileInitialSyncStatusRepository(workingDir)
+    private val initialSyncStatusRepository: InitialSyncStatusRepository = FileInitialSyncStatusRepository(workingDir, clock)
 
     override suspend fun execute(params: SyncTask.Params): SyncResponse {
         return syncTaskSequencer.post {
@@ -107,11 +109,12 @@ internal class DefaultSyncTask @Inject constructor(
         val isInitialSync = token == null
         if (isInitialSync) {
             // We might want to get the user information in parallel too
-            val user = tryOrNull { session.getProfileAsUser(userId) }
+            val user = tryOrNull { session.profileService().getProfileAsUser(userId) }
             userStore.createOrUpdate(
                     userId = userId,
                     displayName = user?.displayName,
-                    avatarUrl = user?.avatarUrl)
+                    avatarUrl = user?.avatarUrl
+            )
             defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
         }
         // Maybe refresh the homeserver capabilities data we know
@@ -124,7 +127,7 @@ internal class DefaultSyncTask @Inject constructor(
         if (isInitialSync) {
             Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}")
             val initSyncStrategy = initialSyncStrategy
-            logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) {
+            logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag, clock) {
                 if (initSyncStrategy is InitialSyncStrategy.Optimized) {
                     roomSyncEphemeralTemporaryStore.reset()
                     workingDir.mkdirs()
@@ -135,7 +138,7 @@ internal class DefaultSyncTask @Inject constructor(
                     // Delete all files
                     workingDir.deleteRecursively()
                 } else {
-                    val syncResponse = logDuration("INIT_SYNC Request", loggerTag) {
+                    val syncResponse = logDuration("INIT_SYNC Request", loggerTag, clock) {
                         executeRequest(globalErrorReceiver) {
                             syncAPI.sync(
                                     params = requestParams,
@@ -146,7 +149,7 @@ internal class DefaultSyncTask @Inject constructor(
                     // We cannot distinguish request and download in this case.
                     syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime()
                     syncStatisticsData.downloadInitSyncTime = syncStatisticsData.requestInitSyncTime
-                    logDuration("INIT_SYNC Database insertion", loggerTag) {
+                    logDuration("INIT_SYNC Database insertion", loggerTag, clock) {
                         syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService)
                     }
                     syncResponseToReturn = syncResponse
@@ -172,12 +175,14 @@ internal class DefaultSyncTask @Inject constructor(
             val nbToDevice = syncResponse.toDevice?.events.orEmpty().size
             val nextBatch = syncResponse.nextBatch
             Timber.tag(loggerTag.value).d(
-                "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch"
+                    "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch"
+            )
+            defaultSyncStatusService.setStatus(
+                    SyncStatusService.Status.IncrementalSyncParsing(
+                            rooms = nbRooms,
+                            toDevice = nbToDevice
+                    )
             )
-            defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncParsing(
-                    rooms = nbRooms,
-                    toDevice = nbToDevice
-            ))
             syncResponseHandler.handleResponse(syncResponse, token, null)
             syncResponseToReturn = syncResponse
             Timber.tag(loggerTag.value).d("Incremental sync done")
@@ -201,14 +206,14 @@ internal class DefaultSyncTask @Inject constructor(
             }
         } else {
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
-            val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) {
+            val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag, clock) {
                 reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) {
                     getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
                 }
             }
             syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime()
             if (syncResponse.isSuccessful) {
-                logDuration("INIT_SYNC Download and save to file", loggerTag) {
+                logDuration("INIT_SYNC Download and save to file", loggerTag, clock) {
                     reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) {
                         syncResponse.body()?.byteStream()?.use { inputStream ->
                             workingFile.outputStream().use { outputStream ->
@@ -247,8 +252,8 @@ internal class DefaultSyncTask @Inject constructor(
     }
 
     private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized): SyncResponse {
-        return logDuration("INIT_SYNC handleSyncFile()", loggerTag) {
-            val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) {
+        return logDuration("INIT_SYNC handleSyncFile()", loggerTag, clock) {
+            val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag, clock) {
                 syncResponseParser.parse(initSyncStrategy, workingFile)
             }
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
@@ -257,7 +262,7 @@ internal class DefaultSyncTask @Inject constructor(
             val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored }
             Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
 
-            logDuration("INIT_SYNC Database insertion", loggerTag) {
+            logDuration("INIT_SYNC Database insertion", loggerTag, clock) {
                 syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService)
             }
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
index 7f80486c70dee176651556b260236d72a1ba8516..c213ea4bcfdbac5a68f5b19ab46e4b98cc4513c9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
@@ -20,17 +20,19 @@ import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmList
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
+import org.matrix.android.sdk.api.failure.GlobalError
+import org.matrix.android.sdk.api.failure.InitialSyncRequestReason
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync
 import org.matrix.android.sdk.api.session.sync.model.UserAccountDataSync
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
@@ -39,14 +41,20 @@ import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
 import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields
 import org.matrix.android.sdk.internal.database.model.deleteOnCascade
+import org.matrix.android.sdk.internal.database.query.findAllFrom
 import org.matrix.android.sdk.internal.database.query.getDirectRooms
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.session.SessionListeners
+import org.matrix.android.sdk.internal.session.dispatchTo
+import org.matrix.android.sdk.internal.session.pushers.GetPushRulesResponse
 import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
@@ -65,7 +73,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         private val directChatsHelper: DirectChatsHelper,
         private val updateUserAccountDataTask: UpdateUserAccountDataTask,
         private val roomAvatarResolver: RoomAvatarResolver,
-        private val roomDisplayNameResolver: RoomDisplayNameResolver
+        private val roomDisplayNameResolver: RoomDisplayNameResolver,
+        @SessionId private val sessionId: String,
+        private val sessionManager: SessionManager,
+        private val sessionListeners: SessionListeners
 ) {
 
     fun handle(realm: Realm, accountData: UserAccountDataSync?) {
@@ -184,12 +195,36 @@ internal class UserAccountDataSyncHandler @Inject constructor(
 
     private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) {
         val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return
-        realm.where(IgnoredUserEntity::class.java)
-                .findAll()
-                .deleteAllFromRealm()
+        val currentIgnoredUsers = realm.where(IgnoredUserEntity::class.java).findAll()
+        val currentIgnoredUserIds = currentIgnoredUsers.map { it.userId }
+        // Delete the previous list
+        currentIgnoredUsers.deleteAllFromRealm()
         // And save the new received list
         userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } }
-        // TODO If not initial sync, we should execute a init sync
+
+        // Delete all the TimelineEvents for all the ignored users
+        // See https://spec.matrix.org/latest/client-server-api/#client-behaviour-22 :
+        // "Once ignored, the client will no longer receive events sent by that user, with the exception of state events"
+        // So just delete all non-state events from our local storage.
+        TimelineEventEntity.findAllFrom(realm, userIds)
+                .also { Timber.d("Deleting ${it.size} TimelineEventEntity from ignored users") }
+                .forEach {
+                    it.deleteOnCascade(true)
+                }
+
+        // Handle the case when some users are unignored from another session
+        val mustRefreshCache = currentIgnoredUserIds.any { currentIgnoredUserId -> currentIgnoredUserId !in userIds }
+        if (mustRefreshCache) {
+            Timber.d("A user has been unignored from another session, an initial sync should be performed")
+            dispatchMustRefresh()
+        }
+    }
+
+    private fun dispatchMustRefresh() {
+        val session = sessionManager.getSessionComponent(sessionId)?.session()
+        session.dispatchTo(sessionListeners) { safeSession, listener ->
+            listener.onGlobalError(safeSession, GlobalError.InitialSyncRequest(InitialSyncRequestReason.IGNORED_USERS_LIST_CHANGE))
+        }
     }
 
     private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
index 2c84f1e692f523406a8ca3abd41c25ab52ad00f6..77bee18df932814a3bab3fbbcdf2f11712cf90dc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
@@ -44,12 +44,14 @@ internal class ReadReceiptHandler @Inject constructor(
 
     companion object {
 
-        fun createContent(userId: String, eventId: String): ReadReceiptContent {
+        fun createContent(userId: String,
+                          eventId: String,
+                          currentTimeMillis: Long): ReadReceiptContent {
             return mapOf(
                     eventId to mapOf(
                             READ_KEY to mapOf(
                                     userId to mapOf(
-                                            TIMESTAMP_KEY to System.currentTimeMillis().toDouble()
+                                            TIMESTAMP_KEY to currentTimeMillis.toDouble()
                                     )
                             )
                     )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index afd8e1bb99a5b163d410181ae32e335bddd79678..5437a015fdf25d17dfb7bec7796c7c1cbc423d7b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -79,22 +79,26 @@ import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent
 import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
 import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler
 import org.matrix.android.sdk.internal.util.computeBestChunkSize
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler,
-                                                   private val roomSummaryUpdater: RoomSummaryUpdater,
-                                                   private val roomAccountDataHandler: RoomSyncAccountDataHandler,
-                                                   private val cryptoService: DefaultCryptoService,
-                                                   private val roomMemberEventHandler: RoomMemberEventHandler,
-                                                   private val roomTypingUsersHandler: RoomTypingUsersHandler,
-                                                   private val threadsAwarenessHandler: ThreadsAwarenessHandler,
-                                                   private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
-                                                   @UserId private val userId: String,
-                                                   private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
-                                                   private val lightweightSettingsStorage: LightweightSettingsStorage,
-                                                   private val timelineInput: TimelineInput,
-                                                   private val liveEventService: Lazy<StreamEventsManager>) {
+internal class RoomSyncHandler @Inject constructor(
+        private val readReceiptHandler: ReadReceiptHandler,
+        private val roomSummaryUpdater: RoomSummaryUpdater,
+        private val roomAccountDataHandler: RoomSyncAccountDataHandler,
+        private val cryptoService: DefaultCryptoService,
+        private val roomMemberEventHandler: RoomMemberEventHandler,
+        private val roomTypingUsersHandler: RoomTypingUsersHandler,
+        private val threadsAwarenessHandler: ThreadsAwarenessHandler,
+        private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
+        @UserId private val userId: String,
+        private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
+        private val lightweightSettingsStorage: LightweightSettingsStorage,
+        private val timelineInput: TimelineInput,
+        private val liveEventService: Lazy<StreamEventsManager>,
+        private val clock: Clock,
+) {
 
     sealed class HandlingStrategy {
         data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
@@ -102,11 +106,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
     }
 
-    suspend fun handle(realm: Realm,
-                       roomsSyncResponse: RoomsSyncResponse,
-                       isInitialSync: Boolean,
-                       aggregator: SyncResponsePostTreatmentAggregator,
-                       reporter: ProgressReporter? = null) {
+    fun handle(realm: Realm,
+               roomsSyncResponse: RoomsSyncResponse,
+               isInitialSync: Boolean,
+               aggregator: SyncResponsePostTreatmentAggregator,
+               reporter: ProgressReporter? = null) {
         handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter)
         handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter)
         handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter)
@@ -120,17 +124,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
     }
     // PRIVATE METHODS *****************************************************************************
 
-    private suspend fun handleRoomSync(realm: Realm,
-                                       handlingStrategy: HandlingStrategy,
-                                       isInitialSync: Boolean,
-                                       aggregator: SyncResponsePostTreatmentAggregator,
-                                       reporter: ProgressReporter?) {
+    private fun handleRoomSync(realm: Realm,
+                               handlingStrategy: HandlingStrategy,
+                               isInitialSync: Boolean,
+                               aggregator: SyncResponsePostTreatmentAggregator,
+                               reporter: ProgressReporter?) {
         val insertType = if (isInitialSync) {
             EventInsertType.INITIAL_SYNC
         } else {
             EventInsertType.INCREMENTAL_SYNC
         }
-        val syncLocalTimeStampMillis = System.currentTimeMillis()
+        val syncLocalTimeStampMillis = clock.epochMillis()
         val rooms = when (handlingStrategy) {
             is HandlingStrategy.JOINED  -> {
                 if (isInitialSync && initialSyncStrategy is InitialSyncStrategy.Optimized) {
@@ -157,11 +161,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         realm.insertOrUpdate(rooms)
     }
 
-    private suspend fun insertJoinRoomsFromInitSync(realm: Realm,
-                                                    handlingStrategy: HandlingStrategy.JOINED,
-                                                    syncLocalTimeStampMillis: Long,
-                                                    aggregator: SyncResponsePostTreatmentAggregator,
-                                                    reporter: ProgressReporter?) {
+    private fun insertJoinRoomsFromInitSync(realm: Realm,
+                                            handlingStrategy: HandlingStrategy.JOINED,
+                                            syncLocalTimeStampMillis: Long,
+                                            aggregator: SyncResponsePostTreatmentAggregator,
+                                            reporter: ProgressReporter?) {
         val bestChunkSize = computeBestChunkSize(
                 listSize = handlingStrategy.data.keys.size,
                 limit = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE
@@ -199,12 +203,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         }
     }
 
-    private suspend fun handleJoinedRoom(realm: Realm,
-                                         roomId: String,
-                                         roomSync: RoomSync,
-                                         insertType: EventInsertType,
-                                         syncLocalTimestampMillis: Long,
-                                         aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
+    private fun handleJoinedRoom(realm: Realm,
+                                 roomId: String,
+                                 roomSync: RoomSync,
+                                 insertType: EventInsertType,
+                                 syncLocalTimestampMillis: Long,
+                                 aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
         Timber.v("Handle join sync for room $roomId")
         val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
 
@@ -353,15 +357,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         return roomEntity
     }
 
-    private suspend fun handleTimelineEvents(realm: Realm,
-                                             roomId: String,
-                                             roomEntity: RoomEntity,
-                                             eventList: List<Event>,
-                                             prevToken: String? = null,
-                                             isLimited: Boolean = true,
-                                             insertType: EventInsertType,
-                                             syncLocalTimestampMillis: Long,
-                                             aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity {
+    private fun handleTimelineEvents(realm: Realm,
+                                     roomId: String,
+                                     roomEntity: RoomEntity,
+                                     eventList: List<Event>,
+                                     prevToken: String? = null,
+                                     isLimited: Boolean = true,
+                                     insertType: EventInsertType,
+                                     syncLocalTimestampMillis: Long,
+                                     aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity {
         val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId)
         if (isLimited && lastChunk != null) {
             lastChunk.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = true)
@@ -389,8 +393,10 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
             liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync)
 
             if (event.isEncrypted() && !isInitialSync) {
-                runBlocking {
+                try {
                     decryptIfNeeded(event, roomId)
+                } catch (e: InterruptedException) {
+                    Timber.i("Decryption got interrupted")
                 }
             }
             var contentToInject: String? = null
@@ -421,7 +427,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     roomId = roomId,
                     eventEntity = eventEntity,
                     direction = PaginationDirection.FORWARDS,
-                    roomMemberContentsByUser = roomMemberContentsByUser)
+                    roomMemberContentsByUser = roomMemberContentsByUser
+            )
             if (lightweightSettingsStorage.areThreadMessagesEnabled()) {
                 eventEntity.rootThreadEventId?.let {
                     // This is a thread event
@@ -437,7 +444,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                 threadEventEntity = eventEntity,
                                 roomMemberContentsByUser = roomMemberContentsByUser,
                                 userId = userId,
-                                roomEntity = roomEntity)
+                                roomEntity = roomEntity,
+                                currentTimeMillis = clock.epochMillis(),
+                        )
                     }
                 } ?: run {
                     // This is a normal event or a root thread one
@@ -475,7 +484,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     roomId = roomId,
                     realm = realm,
                     chunkEntity = chunkEntity,
-                    currentUserId = userId)
+                    currentUserId = userId
+            )
         }
 
         // posting new events to timeline if any is registered
@@ -505,10 +515,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         }
     }
 
-    private suspend fun decryptIfNeeded(event: Event, roomId: String) {
+    private fun decryptIfNeeded(event: Event, roomId: String) {
         try {
             // Event from sync does not have roomId, so add it to the event first
-            val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+            // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
+            val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
             event.mxDecryptionResult = OlmDecryptionResult(
                     payload = result.clearEvent,
                     senderKey = result.senderCurve25519Key,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
index c67c0e350e09ee2e8bc975b517f6ee2c4df9b82c..f183c4cb28cd5c67f139270255114c817925e6f3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
@@ -42,7 +42,7 @@ private const val DEFAULT_DELAY_MILLIS = 30_000L
  * Possible next worker    : None
  */
 internal class SyncWorker(context: Context, workerParameters: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
index fdd5524fc22219696c581efcfb664b7233582413..210cb192e7f7c3c832432041d87982c025548a93 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
@@ -23,7 +23,7 @@ import javax.inject.Inject
 
 internal class DefaultThirdPartyService @Inject constructor(private val getThirdPartyProtocolTask: GetThirdPartyProtocolsTask,
                                                             private val getThirdPartyUserTask: GetThirdPartyUserTask) :
-    ThirdPartyService {
+        ThirdPartyService {
 
     override suspend fun getThirdPartyProtocols(): Map<String, ThirdPartyProtocol> {
         return getThirdPartyProtocolTask.execute(Unit)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
index 4dfc7586aecc7bf5af7485579d16fc0f181e866f..c205c4f1c6d31b512e304b56e5c00346e6a2f216 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
@@ -21,9 +21,7 @@ import dagger.Module
 import dagger.Provides
 import org.matrix.android.sdk.api.session.user.UserService
 import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSaveIgnoredUsersTask
 import org.matrix.android.sdk.internal.session.user.accountdata.DefaultUpdateIgnoredUserIdsTask
-import org.matrix.android.sdk.internal.session.user.accountdata.SaveIgnoredUsersTask
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask
 import org.matrix.android.sdk.internal.session.user.model.DefaultSearchUserTask
 import org.matrix.android.sdk.internal.session.user.model.SearchUserTask
@@ -48,9 +46,6 @@ internal abstract class UserModule {
     @Binds
     abstract fun bindSearchUserTask(task: DefaultSearchUserTask): SearchUserTask
 
-    @Binds
-    abstract fun bindSaveIgnoredUsersTask(task: DefaultSaveIgnoredUsersTask): SaveIgnoredUsersTask
-
     @Binds
     abstract fun bindUpdateIgnoredUserIdsTask(task: DefaultUpdateIgnoredUserIdsTask): UpdateIgnoredUserIdsTask
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt
deleted file mode 100644
index 63c0ce645eb9d792e5e8ab1c1047cdf9ac87eb4b..0000000000000000000000000000000000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.matrix.android.sdk.internal.session.user.accountdata
-
-import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
-import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.task.Task
-import org.matrix.android.sdk.internal.util.awaitTransaction
-import javax.inject.Inject
-
-/**
- * Save the ignored users list in DB
- */
-internal interface SaveIgnoredUsersTask : Task<SaveIgnoredUsersTask.Params, Unit> {
-    data class Params(
-            val userIds: List<String>
-    )
-}
-
-internal class DefaultSaveIgnoredUsersTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : SaveIgnoredUsersTask {
-
-    override suspend fun execute(params: SaveIgnoredUsersTask.Params) {
-        monarchy.awaitTransaction { realm ->
-            // clear current ignored users
-            realm.where(IgnoredUserEntity::class.java)
-                    .findAll()
-                    .deleteAllFromRealm()
-
-            // And save the new received list
-            params.userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } }
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
index 445b78104cfb7bd0431ab34a4296f9266a07bd13..173161f8aeb08fdc4bca5e5a8db938cd0e6e18da 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
@@ -38,7 +38,6 @@ internal interface UpdateIgnoredUserIdsTask : Task<UpdateIgnoredUserIdsTask.Para
 internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(
         private val accountDataApi: AccountDataAPI,
         @SessionDatabase private val monarchy: Monarchy,
-        private val saveIgnoredUsersTask: SaveIgnoredUsersTask,
         @UserId private val userId: String,
         private val globalErrorReceiver: GlobalErrorReceiver
 ) : UpdateIgnoredUserIdsTask {
@@ -66,8 +65,5 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(
         executeRequest(globalErrorReceiver) {
             accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body)
         }
-
-        // Update the DB right now (do not wait for the sync to come back with updated data, for a faster UI update)
-        saveIgnoredUsersTask.execute(SaveIgnoredUsersTask.Params(list))
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
index 18a043be453177a78d791e88ee854ad6a6eb1a0c..06508def1c4e2787e75874803d70d8da26951e65 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
@@ -29,9 +29,10 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
 import javax.inject.Inject
 
-internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, Unit> {
+internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, String> {
 
     data class Params(
             val roomId: String,
@@ -45,8 +46,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
                                                            @UserId private val userId: String,
                                                            private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask {
 
-    override suspend fun execute(params: CreateWidgetTask.Params) {
-        executeRequest(globalErrorReceiver) {
+    override suspend fun execute(params: CreateWidgetTask.Params): String {
+        val response = executeRequest(globalErrorReceiver) {
             roomAPI.sendStateEvent(
                     roomId = params.roomId,
                     stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY,
@@ -60,5 +61,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
                     .and()
                     .equalTo(CurrentStateEventEntityFields.ROOT.SENDER, userId)
         }
+        return response.eventId.also {
+            Timber.d("Widget state event: $it just sent in room ${params.roomId}")
+        }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
index 10b4f7f7d7d0c73e3d4d30f40b14d1793a22149f..9f42688f7d63ebf732f6328387711dba6c9af310 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
@@ -29,7 +29,7 @@ import javax.inject.Inject
 
 internal class DefaultWidgetPostAPIMediator @Inject constructor(private val moshi: Moshi,
                                                                 private val widgetPostMessageAPIProvider: WidgetPostMessageAPIProvider) :
-    WidgetPostAPIMediator {
+        WidgetPostAPIMediator {
 
     private val jsonAdapter = moshi.adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
index 89e827aea00bae3068a2e6c569a3db01d90f4310..53a435d2172c127b22120322974c4b55d66f0fce 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
@@ -29,7 +29,7 @@ import javax.inject.Provider
 internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager,
                                                         private val widgetURLFormatter: WidgetURLFormatter,
                                                         private val widgetPostAPIMediator: Provider<WidgetPostAPIMediator>) :
-    WidgetService {
+        WidgetService {
 
     override fun getWidgetURLFormatter(): WidgetURLFormatter {
         return widgetURLFormatter
@@ -52,7 +52,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
         return widgetManager.getWidgetComputedUrl(widget, isLightTheme)
     }
 
-override fun getRoomWidgetsLive(
+    override fun getRoomWidgetsLive(
             roomId: String,
             widgetId: QueryStringValue,
             widgetTypes: Set<String>?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
index e18117d2d7bded184ae2822cb54d24f8e81447ad..07ed91c1790ff82ac64864a968794ff3981f498f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
@@ -52,7 +52,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
                                                  private val widgetFactory: WidgetFactory,
                                                  @UserId private val userId: String) :
 
-    IntegrationManagerService.Listener, SessionLifecycleObserver {
+        IntegrationManagerService.Listener, SessionLifecycleObserver {
 
     private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
     private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
index 6fd907d397235cb45fe8981c64eb9659480b9108..ffad0b856c368d750256bfb254ae2643fa977e9f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.util
 
 import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
@@ -34,13 +35,14 @@ internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
 
 internal suspend fun <T> logDuration(message: String,
                                      loggerTag: LoggerTag,
+                                     clock: Clock,
                                      block: suspend () -> T): T {
     Timber.tag(loggerTag.value).d("$message -- BEGIN")
-    val start = System.currentTimeMillis()
+    val start = clock.epochMillis()
     val result = logRamUsage(message) {
         block()
     }
-    val duration = System.currentTimeMillis() - start
+    val duration = clock.epochMillis() - start
     Timber.tag(loggerTag.value).d("$message -- END duration: $duration ms")
 
     return result
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
index 2790ffba36f0d982ca4183106d010162b6e6de63..dddd7892125dae027322185c292014530b1257fe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
@@ -29,6 +29,7 @@ internal class TemporaryFileCreator @Inject constructor(
     suspend fun create(): File {
         return withContext(Dispatchers.IO) {
             File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
+                    .apply { mkdirs() }
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
index f22f0810a146c31176cb1d034286b9eed623fdb7..8f3c89f2d4282f9c058c9ab7654e5ccd1e8f429a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
@@ -21,7 +21,7 @@ import io.realm.RealmObjectSchema
 import timber.log.Timber
 
 internal abstract class RealmMigrator(private val realm: DynamicRealm,
-                             private val targetSchemaVersion: Int) {
+                                      private val targetSchemaVersion: Int) {
     fun perform() {
         Timber.d("Migrate ${realm.configuration.realmFileName} to $targetSchemaVersion")
         doMigrate(realm)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
index 2d5e4b944a50f90182b74ad2b5fc4d23e4310440..806c6e97351b4289fdef0a758fc938228eaa2839 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
@@ -20,6 +20,6 @@ import android.os.Build
 import javax.inject.Inject
 
 internal class DefaultBuildVersionSdkIntProvider @Inject constructor() :
-    BuildVersionSdkIntProvider {
+        BuildVersionSdkIntProvider {
     override fun get() = Build.VERSION.SDK_INT
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt
index 8a7b50175a2978335d76e83f58a32c5877b7865c..396d12f369c8fb611444f1549dfbc2504830339e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt
@@ -18,10 +18,15 @@ package org.matrix.android.sdk.internal.util.system
 
 import dagger.Binds
 import dagger.Module
+import org.matrix.android.sdk.internal.util.time.Clock
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 
 @Module
 internal abstract class SystemModule {
 
     @Binds
     abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider
+
+    @Binds
+    abstract fun bindClock(clock: DefaultClock): Clock
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4fe9069b4941b0febb9b18640603c233a0f26117
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.util.time
+
+import javax.inject.Inject
+
+internal interface Clock {
+    fun epochMillis(): Long
+}
+
+internal class DefaultClock @Inject constructor() : Clock {
+
+    /**
+     * Provides a UTC epoch in milliseconds
+     *
+     * This value is not guaranteed to be correct with reality
+     * as a User can override the system time and date to any values.
+     */
+    override fun epochMillis(): Long {
+        return System.currentTimeMillis()
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
index 856d3debcf368b80a4620bdf454b100cf14a89f7..c92b51fcb895370f066e5410a9792e62d31d614a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
@@ -20,7 +20,7 @@ import androidx.work.Worker
 import androidx.work.WorkerParameters
 
 internal class AlwaysSuccessfulWorker(context: Context, params: WorkerParameters) :
-    Worker(context, params) {
+        Worker(context, params) {
 
     override fun doWork(): Result {
         return Result.success()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
index 0b451e9c34f7621a0a02da13ad658c81e87d6836..e56b359f7ab08537c58effbf8ce9c1955ed13ed9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
@@ -93,7 +93,7 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage
     class CheckFactoryWorker(context: Context,
                              workerParameters: WorkerParameters,
                              private val isCreatedByMatrixWorkerFactory: Boolean) :
-        CoroutineWorker(context, workerParameters) {
+            CoroutineWorker(context, workerParameters) {
 
         // Called by WorkManager if there is no MatrixWorkerFactory
         constructor(context: Context, workerParameters: WorkerParameters) : this(context,
diff --git a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
index 47f6869479d041fa27517ad915b9082652abe19b..0b7bac0d6fb7e15b280fd695467ed241d778f861 100644
--- a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
+++ b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
@@ -27,7 +27,7 @@ import javax.inject.Inject
  */
 @MatrixScope
 internal class CurlLoggingInterceptor @Inject constructor() :
-    Interceptor {
+        Interceptor {
 
     @Throws(IOException::class)
     override fun intercept(chain: Interceptor.Chain): Response {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
similarity index 95%
rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
index 9bfdea54143970d36b450fb0a8c37f1672e6f79b..1879e8195cbacb3c69e00aade92fbbe417b19920 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.matrix.android.sdk.MatrixTest
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.di.MoshiProvider
 
 class PushRuleActionsTest : MatrixTest {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
similarity index 95%
rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
index c0b869a90ea878050b1d128850edfd44bdb43df8..95787173da3af05b95f59f40538e82e940a337d8 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import io.mockk.every
 import io.mockk.mockk
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.MatrixTest
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.members.MembershipService
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
@@ -148,14 +149,22 @@ class PushRulesConditionTest : MatrixTest {
         val room2JoinedId = "2joined"
         val room3JoinedId = "3joined"
 
-        val roomStub2Joined = mockk<Room> {
+        val roomMembershipService2 = mockk<MembershipService> {
             every { getNumberOfJoinedMembers() } returns 2
         }
 
-        val roomStub3Joined = mockk<Room> {
+        val roomMembershipService3 = mockk<MembershipService> {
             every { getNumberOfJoinedMembers() } returns 3
         }
 
+        val roomStub2Joined = mockk<Room> {
+            every { membershipService() } returns roomMembershipService2
+        }
+
+        val roomStub3Joined = mockk<Room> {
+            every { membershipService() } returns roomMembershipService3
+        }
+
         val roomGetterStub = mockk<RoomGetter> {
             every { getRoom(room2JoinedId) } returns roomStub2Joined
             every { getRoom(room3JoinedId) } returns roomStub3Joined