diff --git a/CHANGES.md b/CHANGES.md
index 43507f59d9168902529ad403fdaa73d9ca409049..bfc60932cfdb64e959ceef31a3b9932d5ad604a4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,11 @@
 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.1.9 (2021-06-10)
+===================================================
+
+Fix error with Jitpack on 1.1.8
+Imported from Element 1.1.9. (https://github.com/vector-im/element-android/releases/tag/v1.1.9)
+
 Changes in Matrix-SDK 1.1.8 (2021-05-26)
 ===================================================
 
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index b80affa308ef94226b0b7314ce3abefe127f0e76..fbd7f57ccdace83068848f2e894e9daf338f3f46 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -22,7 +22,7 @@ android {
         minSdkVersion 21
         targetSdkVersion 30
         versionCode 1
-        versionName "1.1.8"
+        versionName "1.1.9"
         // Multidex is useful for tests
         multiDexEnabled true
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -113,7 +113,7 @@ dependencies {
     def lifecycle_version = '2.2.0'
     def arch_version = '2.1.0'
     def markwon_version = '3.1.0'
-    def daggerVersion = '2.35.1'
+    def daggerVersion = '2.36'
     def work_version = '2.5.0'
     def retrofit_version = '2.9.0'
 
@@ -122,7 +122,7 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
 
     implementation "androidx.appcompat:appcompat:1.3.0"
-    implementation "androidx.core:core-ktx:1.3.2"
+    implementation "androidx.core:core-ktx:1.5.0"
 
     implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
     implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@@ -156,7 +156,7 @@ dependencies {
     implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
 
     // olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
-    implementation 'org.matrix.gitlab.matrix-org:olm:3.2.2'
+    implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4'
 
     // DI
     implementation "com.google.dagger:dagger:$daggerVersion"
@@ -170,7 +170,7 @@ dependencies {
     implementation 'com.otaliastudios:transcoder:0.10.3'
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.23'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24'
 
     testImplementation 'junit:junit:4.13.2'
     testImplementation 'org.robolectric:robolectric:4.5.1'
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 eb4773f3c86f1b13ef45e920d64949d8d66af30d..25c22bca57ddf74d1bc29b2326a99706ca4f1d7b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
@@ -33,7 +33,7 @@ import org.matrix.android.sdk.common.TestConstants
 import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
 import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
@@ -73,12 +73,12 @@ class QuadSTests : InstrumentedTest {
 
         // Assert Account data is updated
         val accountDataLock = CountDownLatch(1)
-        var accountData: UserAccountDataEvent? = null
+        var accountData: AccountDataEvent? = null
 
         val liveAccountData = runBlocking(Dispatchers.Main) {
-            aliceSession.getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
+            aliceSession.userAccountDataService().getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
         }
-        val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
+        val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
             if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
                 accountData = t.getOrNull()
                 accountDataLock.countDown()
@@ -100,13 +100,13 @@ class QuadSTests : InstrumentedTest {
             quadS.setDefaultKey(TEST_KEY_ID)
         }
 
-        var defaultKeyAccountData: UserAccountDataEvent? = null
+        var defaultKeyAccountData: AccountDataEvent? = null
         val defaultDataLock = CountDownLatch(1)
 
         val liveDefAccountData = runBlocking(Dispatchers.Main) {
-            aliceSession.getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
+            aliceSession.userAccountDataService().getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
         }
-        val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
+        val accountDefDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
             if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
                 defaultKeyAccountData = t.getOrNull()!!
                 defaultDataLock.countDown()
@@ -206,7 +206,7 @@ class QuadSTests : InstrumentedTest {
             )
         }
 
-        val accountDataEvent = aliceSession.getAccountDataEvent("my.secret")
+        val accountDataEvent = aliceSession.userAccountDataService().getAccountDataEvent("my.secret")
         val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
 
         assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
@@ -275,14 +275,14 @@ class QuadSTests : InstrumentedTest {
         mTestHelper.signOutAndClose(aliceSession)
     }
 
-    private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
+    private fun assertAccountData(session: Session, type: String): AccountDataEvent {
         val accountDataLock = CountDownLatch(1)
-        var accountData: UserAccountDataEvent? = null
+        var accountData: AccountDataEvent? = null
 
         val liveAccountData = runBlocking(Dispatchers.Main) {
-            session.getLiveAccountDataEvent(type)
+            session.userAccountDataService().getLiveAccountDataEvent(type)
         }
-        val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
+        val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
             if (t?.getOrNull()?.type == type) {
                 accountData = t.getOrNull()
                 accountDataLock.countDown()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt
index 64b3e180aac660ca16dbf6679f69b227d10988dd..a0733dda978263754d95b6b841864e356908243a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt
@@ -51,12 +51,12 @@ data class SsoIdentityProvider(
 ) : Parcelable, Comparable<SsoIdentityProvider> {
 
     companion object {
-        const val BRAND_GOOGLE = "org.matrix.google"
-        const val BRAND_GITHUB = "org.matrix.github"
-        const val BRAND_APPLE = "org.matrix.apple"
-        const val BRAND_FACEBOOK = "org.matrix.facebook"
-        const val BRAND_TWITTER = "org.matrix.twitter"
-        const val BRAND_GITLAB = "org.matrix.gitlab"
+        const val BRAND_GOOGLE = "google"
+        const val BRAND_GITHUB = "github"
+        const val BRAND_APPLE = "apple"
+        const val BRAND_FACEBOOK = "facebook"
+        const val BRAND_TWITTER = "twitter"
+        const val BRAND_GITLAB = "gitlab"
     }
 
     override fun compareTo(other: SsoIdentityProvider): Int {
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 b5f90e87ea92a4184e3f774fe8150442c7819b5f..e888e5d2de748027166540353478115574d7ca8a 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
@@ -78,7 +78,6 @@ interface Session :
         InitialSyncProgressService,
         HomeServerCapabilitiesService,
         SecureStorageService,
-        AccountDataService,
         AccountService {
 
     /**
@@ -239,6 +238,11 @@ interface Session :
      */
     fun openIdService(): OpenIdService
 
+    /**
+     * Returns the user account data service associated with the session
+     */
+    fun userAccountDataService(): AccountDataService
+
     /**
      * Add a listener to the session.
      * @param listener the listener to add.
@@ -262,12 +266,17 @@ interface Session :
      * A global session listener to get notified for some events.
      */
     interface Listener : SessionLifecycleObserver {
+        /**
+         * Called when the session received new invites to room so the client can react to it once.
+         */
+        fun onNewInvitedRoom(session: Session, roomId: String) = Unit
+
         /**
          * Possible cases:
          * - The access token is not valid anymore,
          * - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
          */
-        fun onGlobalError(session: Session, globalError: GlobalError)
+        fun onGlobalError(session: Session, globalError: GlobalError) = Unit
     }
 
     val sharedSecretStorageService: SharedSecretStorageService
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt
index 744e3e5379829f1383969e743057e1105578b883..e5cbd07aafdeb3bab7b3790e6c5a9871b6933424 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt
@@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.events.model.Content
  * Currently used types are defined in [UserAccountDataTypes].
  */
 @JsonClass(generateAdapter = true)
-data class UserAccountDataEvent(
+data class AccountDataEvent(
         @Json(name = "type") val type: String,
         @Json(name = "content") val content: Content
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt
index 5ebeaad3de6fb8a1819024b154973df3a80e9968..77f3eb0cd92f673619fe6fe8b528cc78e78b0111 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt
@@ -20,28 +20,31 @@ import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.util.Optional
 
+/**
+ * This service can be attached globally to the session so it represents user data or attached to a single room.
+ */
 interface AccountDataService {
     /**
      * Retrieve the account data with the provided type or null if not found
      */
-    fun getAccountDataEvent(type: String): UserAccountDataEvent?
+    fun getAccountDataEvent(type: String): AccountDataEvent?
 
     /**
      * Observe the account data with the provided type
      */
-    fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>>
+    fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>>
 
     /**
      * Retrieve the account data with the provided types. The return list can have a different size that
      * the size of the types set, because some AccountData may not exist.
      * If an empty set is provided, all the AccountData are retrieved
      */
-    fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent>
+    fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent>
 
     /**
      * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
      */
-    fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>>
+    fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>>
 
     /**
      * Update the account data with the provided type and the provided account data content
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallIdGenerator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallIdGenerator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..43e68725258283e732801538139480194a9c5029
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallIdGenerator.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.call
+
+import java.util.UUID
+
+object CallIdGenerator {
+    fun generate() = UUID.randomUUID().toString()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt
index dc67aa536afd0a5b295ed52dae2255c181a7095d..c34744e75f5f7ef13a6fa8edb68349897a36c309 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt
@@ -20,8 +20,6 @@ interface CallSignalingService {
 
     suspend fun getTurnServer(): TurnServerResponse
 
-    fun getPSTNProtocolChecker(): PSTNProtocolChecker
-
     /**
      * Create an outgoing call
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
index 7533619eb074b111279a771e1b211b0357195b58..fcc9f7072dee4ae863648e7418bde20a633a3a16 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
@@ -26,8 +26,12 @@ interface MxCallDetail {
     val callId: String
     val isOutgoing: Boolean
     val roomId: String
-    val opponentUserId: String
     val isVideoCall: Boolean
+    val ourPartyId: String
+    val opponentPartyId: Optional<String>?
+    val opponentVersion: Int
+    val opponentUserId: String
+    val capabilities: CallCapabilities?
 }
 
 /**
@@ -39,12 +43,6 @@ interface MxCall : MxCallDetail {
         const val VOIP_PROTO_VERSION = 1
     }
 
-    val ourPartyId: String
-    var opponentPartyId: Optional<String>?
-    var opponentVersion: Int
-
-    var capabilities: CallCapabilities?
-
     var state: CallState
 
     /**
@@ -91,8 +89,12 @@ interface MxCall : MxCallDetail {
 
     /**
      * Send a m.call.replaces event to initiate call transfer.
+     * See [org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent] for documentation about the parameters
      */
-    suspend fun transfer(targetUserId: String, targetRoomId: String?)
+    suspend fun transfer(targetUserId: String,
+                         targetRoomId: String?,
+                         createCallId: String?,
+                         awaitCallId: String?)
 
     fun addListener(listener: StateListener)
     fun removeListener(listener: StateListener)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt
deleted file mode 100644
index 6627f62e24afff5b0685273986888cc2b082bba2..0000000000000000000000000000000000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.api.session.call
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import org.matrix.android.sdk.api.extensions.tryOrNull
-import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
-import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.session.thirdparty.GetThirdPartyProtocolsTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import timber.log.Timber
-import java.util.concurrent.atomic.AtomicBoolean
-import javax.inject.Inject
-
-private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn"
-private const val PSTN_MATRIX_KEY = "m.protocol.pstn"
-
-/**
- * This class is responsible for checking if the HS support the PSTN protocol.
- * As long as the request succeed, it'll check only once by session.
- */
-@SessionScope
-class PSTNProtocolChecker @Inject internal constructor(private val taskExecutor: TaskExecutor,
-                                                       private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask) {
-
-    interface Listener {
-        fun onPSTNSupportUpdated()
-    }
-
-    private var alreadyChecked = AtomicBoolean(false)
-
-    private val pstnSupportListeners = mutableListOf<Listener>()
-
-    fun addListener(listener: Listener) {
-        pstnSupportListeners.add(listener)
-    }
-
-    fun removeListener(listener: Listener) {
-        pstnSupportListeners.remove(listener)
-    }
-
-    var supportedPSTNProtocol: String? = null
-        private set
-
-    fun checkForPSTNSupportIfNeeded() {
-        if (alreadyChecked.get()) return
-        taskExecutor.executorScope.checkForPSTNSupport()
-    }
-
-    private fun CoroutineScope.checkForPSTNSupport() = launch {
-        try {
-            supportedPSTNProtocol = getSupportedPSTN(3)
-            alreadyChecked.set(true)
-            if (supportedPSTNProtocol != null) {
-                pstnSupportListeners.forEach {
-                    tryOrNull { it.onPSTNSupportUpdated() }
-                }
-            }
-        } catch (failure: Throwable) {
-            Timber.v("Fail to get supported PSTN, will check again next time.")
-        }
-    }
-
-    private suspend fun getSupportedPSTN(maxTries: Int): String? {
-        val thirdPartyProtocols: Map<String, ThirdPartyProtocol> = try {
-            getThirdPartyProtocolsTask.execute(Unit)
-        } catch (failure: Throwable) {
-            if (maxTries == 1) {
-                throw failure
-            } else {
-                // Wait for 10s before trying again
-                delay(10_000L)
-                return getSupportedPSTN(maxTries - 1)
-            }
-        }
-        return when {
-            thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY
-            thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY
-            else                                             -> null
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
index d2befca1ee146e5a1c239320f9dcb875a30589c3..229a53fa9d71f231afe873a806f7d4fc6e72ab66 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
@@ -31,9 +31,7 @@ object EventType {
     const val TYPING = "m.typing"
     const val REDACTION = "m.room.redaction"
     const val RECEIPT = "m.receipt"
-    const val TAG = "m.tag"
     const val ROOM_KEY = "m.room_key"
-    const val FULLY_READ = "m.fully_read"
     const val PLUMBING = "m.room.plumbing"
     const val BOT_OPTIONS = "m.room.bot.options"
     const val PREVIEW_URLS = "org.matrix.room.preview_urls"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
index ac1d726d0324eaafc7ba3f014e5f787c3bc51fab..a6d4583c76f788f2c0bd38ba45b49a059474b225 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
@@ -54,7 +54,7 @@ interface PermalinkService {
      *
      * @return the permalink, or null in case of error
      */
-    fun createRoomPermalink(roomId: String): String?
+    fun createRoomPermalink(roomId: String, viaServers: List<String>? = null): String?
 
     /**
      * Creates a permalink for an event. If you have an event you can use [createPermalink]
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 8c434fc44016246de6cf3c867a226be9d8ad6bbd..41bcbf8ff6f7541974e6ce377cde5cb2cbf3705d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.api.session.room
 
 import androidx.lifecycle.LiveData
+import org.matrix.android.sdk.api.session.accountdata.AccountDataService
 import org.matrix.android.sdk.api.session.room.alias.AliasService
 import org.matrix.android.sdk.api.session.room.call.RoomCallService
 import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
@@ -55,7 +56,8 @@ interface Room :
         RoomCallService,
         RelationService,
         RoomCryptoService,
-        RoomPushRuleService {
+        RoomPushRuleService,
+        AccountDataService {
 
     /**
      * The roomId of this room
@@ -86,12 +88,12 @@ interface Room :
      * @return The search result
      */
     suspend fun search(searchTerm: String,
-               nextBatch: String?,
-               orderByRecent: Boolean,
-               limit: Int,
-               beforeLimit: Int,
-               afterLimit: Int,
-               includeProfile: Boolean): SearchResult
+                       nextBatch: String?,
+                       orderByRecent: Boolean,
+                       limit: Int,
+                       beforeLimit: Int,
+                       afterLimit: Int,
+                       includeProfile: Boolean): SearchResult
 
     /**
      * Use this room as a Space, if the type is correct.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0e80c307b4f9a7fb9bfb94101642fcc75eb9c849
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.accountdata
+
+object RoomAccountDataTypes {
+    const val EVENT_TYPE_VIRTUAL_ROOM = "im.vector.is_virtual_room"
+    const val EVENT_TYPE_TAG = "m.tag"
+    const val EVENT_TYPE_FULLY_READ = "m.fully_read"
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
index 66293bcb8c6107bf88e4c0b8c0e07b15b7e4e171..8cd2a0538dc2a12504ffa0a045577869af2b866f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
@@ -32,5 +32,7 @@ data class SpaceChildInfo(
         val parentRoomId: String?,
         val suggested: Boolean?,
         val canonicalAlias: String?,
-        val aliases: List<String>?
+        val aliases: List<String>?,
+        val worldReadable: Boolean
+
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
index 45a54bb379b58187445595641724fece2263a823..180b32db054229e57911525aae84e922a7df7265 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
-): CallSignallingContent  {
+): 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/CallCandidatesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCandidatesContent.kt
index 7bfe7a97acbdd6260c8d3eb183d83ae9599863b5..dc0a1e3b2e3f69ce3173372926042cf6c8107c7d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCandidatesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCandidatesContent.kt
@@ -41,4 +41,4 @@ data class CallCandidatesContent(
          * Required. The version of the VoIP specification this messages adheres to.
          */
         @Json(name = "version") override val version: String?
-): CallSignallingContent
+): CallSignalingContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
index 0acc409053d714d561e6077ee9f6820c4500560f..9d6e1a7eaeb09e39308235fdb543827a45694c70 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
@@ -44,7 +44,7 @@ data class CallHangupContent(
          * One of: ["ice_failed", "invite_timeout"]
          */
         @Json(name = "reason") val reason: Reason? = null
-) : CallSignallingContent {
+) : CallSignalingContent {
     @JsonClass(generateAdapter = false)
     enum class Reason {
         @Json(name = "ice_failed")
@@ -56,6 +56,9 @@ data class CallHangupContent(
         @Json(name = "user_hangup")
         USER_HANGUP,
 
+        @Json(name = "replaced")
+        REPLACED,
+
         @Json(name = "user_media_failed")
         USER_MEDIA_FAILED,
 
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 42489bc0ceb199000cf65471444f35ac4c81b111..e4332f0ea76fed180e4126b9e8d3282ef3892791 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
 
-): CallSignallingContent  {
+): 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 040993bb51dda98cba50c5730173e9d43ba6d68a..68dd5ef043e12f97ff068efacf2e171cdf740a94 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?
 
-        ): CallSignallingContent  {
+        ): 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/CallRejectContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
index 1da229b1799715ca934bf6f4c7b28dda909061ea..ea412fbe3eba6fd353c671e6d3486020b79dfefd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
@@ -37,4 +37,4 @@ data class CallRejectContent(
          * Required. The version of the VoIP specification this message adheres to.
          */
         @Json(name = "version") override val version: String?
-) : CallSignallingContent
+) : CallSignalingContent
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 97a3b8c7a72e576c5956852f1f2870e834275667..4559c5db6d5a6d57fd48381ec878118eb51e65a7 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
@@ -38,30 +38,30 @@ data class CallReplacesContent(
          */
         @Json(name = "replacement_id") val replacementId: String? = null,
         /**
-         *  Optional. If specified, the transferee client waits for an invite to this room and joins it
-         *  (possibly waiting for user confirmation) and then continues the transfer in this room.
-         *  If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing.
+         * Optional. If specified, the transferee client waits for an invite to this room and joins it
+         * (possibly waiting for user confirmation) and then continues the transfer in this room.
+         * If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing.
          */
-        @Json(name = "target_room") val targerRoomId: String? = null,
+        @Json(name = "target_room") val targetRoomId: String? = null,
         /**
-         *  An object giving information about the transfer target
+         * An object giving information about the transfer target
          */
         @Json(name = "target_user") val targetUser: TargetUser? = null,
         /**
-         *  If specified, gives the call ID for the transferee's client to use when placing the replacement call.
-         *  Mutually exclusive with await_call
+         * If specified, gives the call ID for the transferee's client to use when placing the replacement call.
+         * Mutually exclusive with await_call
          */
         @Json(name = "create_call") val createCall: String? = null,
         /**
-         *  If specified, gives the call ID that the transferee's client should wait for.
-         *  Mutually exclusive with create_call.
+         * If specified, gives the call ID that the transferee's client should wait for.
+         * Mutually exclusive with create_call.
          */
         @Json(name = "await_call") val awaitCall: String? = null,
         /**
          * Required. The version of the VoIP specification this messages adheres to.
          */
         @Json(name = "version") override val version: String?
-): CallSignallingContent  {
+): CallSignalingContent  {
 
     @JsonClass(generateAdapter = true)
     data class TargetUser(
@@ -77,6 +77,5 @@ data class CallReplacesContent(
              * Optional. The avatar URL of the transfer target.
              */
             @Json(name = "avatar_url") val avatarUrl: String?
-
     )
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSelectAnswerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSelectAnswerContent.kt
index 6ea70ac990589b46bd456e3d37ed0a24a67df773..795f332711179121fdf8d40b24e7b2978ae09b90 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSelectAnswerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSelectAnswerContent.kt
@@ -41,4 +41,4 @@ data class CallSelectAnswerContent(
          * Required. The version of the VoIP specification this message adheres to.
          */
         @Json(name = "version") override val version: String?
-): CallSignallingContent
+): CallSignalingContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSignallingContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSignalingContent.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSignallingContent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSignalingContent.kt
index f8d8c2a5e82d777f55cb33ca982c44eb4a2a0cab..92b43dd22ca01fade7c6cc949dc43500719b90e6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSignallingContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallSignalingContent.kt
@@ -16,7 +16,7 @@
 
 package org.matrix.android.sdk.api.session.room.model.call
 
-interface CallSignallingContent {
+interface CallSignalingContent {
     /**
      * Required. A unique identifier for the call.
      */
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 c96a800ee586b32895bbd6bd9ebf3fcab8bb6890..1e8959afc3d6c0bcbe82f4ab2c2e3ccbcabdc806 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
@@ -35,5 +35,5 @@ object MessageType {
     const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
 
     const val MSGTYPE_CONFETTI = "nic.custom.confetti"
-    const val MSGTYPE_SNOW = "io.element.effect.snowfall"
+    const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/RoomDirectoryData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/RoomDirectoryData.kt
deleted file mode 100644
index 91f429d773cd0cf99ff8f9df331e0a006f3c6275..0000000000000000000000000000000000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/RoomDirectoryData.kt
+++ /dev/null
@@ -1,54 +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.api.session.room.model.thirdparty
-
-/**
- * This class describes a rooms directory server.
- */
-data class RoomDirectoryData(
-
-        /**
-         * The server name (might be null)
-         * Set null when the server is the current user's home server.
-         */
-        val homeServer: String? = null,
-
-        /**
-         * The display name (the server description)
-         */
-        val displayName: String = DEFAULT_HOME_SERVER_NAME,
-
-        /**
-         * The third party server identifier
-         */
-        val thirdPartyInstanceId: String? = null,
-
-        /**
-         * Tell if all the federated servers must be included
-         */
-        val includeAllNetworks: Boolean = false,
-
-        /**
-         * the avatar url
-         */
-        val avatarUrl: String? = null
-) {
-
-    companion object {
-        const val DEFAULT_HOME_SERVER_NAME = "Matrix"
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
index 888950dc126b118e64debae5c7f6e889a2ba2927..b78cd5e032225fe030fc20d71751451b060272b8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
@@ -28,7 +28,8 @@ sealed class PeekResult {
             val numJoinedMembers: Int?,
             val roomType: String?,
             val viaServers: List<String>,
-            val someMembers: List<MatrixItem.UserItem>?
+            val someMembers: List<MatrixItem.UserItem>?,
+            val isPublic: Boolean
     ) : PeekResult()
 
     data class PeekingNotAllowed(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/thirdparty/model/ThirdPartyUser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/thirdparty/model/ThirdPartyUser.kt
index d77dfcfe358e5ea79c1115ade71b0f42203750cf..246813a524a0f391037b71a655ba41623cee7a83 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/thirdparty/model/ThirdPartyUser.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/thirdparty/model/ThirdPartyUser.kt
@@ -22,16 +22,16 @@ import org.matrix.android.sdk.api.util.JsonDict
 
 @JsonClass(generateAdapter = true)
 data class ThirdPartyUser(
-        /*
-            Required. A Matrix User ID represting a third party user.
+        /**
+         * Required. A Matrix User ID representing a third party user.
          */
         @Json(name = "userid") val userId: String,
-        /*
-            Required. The protocol ID that the third party location is a part of.
+        /**
+         * Required. The protocol ID that the third party location is a part of.
          */
         @Json(name = "protocol") val protocol: String,
-        /*
-            Required. Information used to identify this third party location.
+        /**
+         *  Required. Information used to identify this third party location.
          */
         @Json(name = "fields") val fields: JsonDict
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
index deb279eb95f063acf97575441ea68a2b122b10ae..3d2773fb4bef8294324e23998a56b785666d15b1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
@@ -20,6 +20,7 @@ import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.session.group.model.GroupSummary
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.RoomType
 import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
 import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
 import org.matrix.android.sdk.api.session.room.sender.SenderInfo
@@ -38,6 +39,8 @@ sealed class MatrixItem(
         init {
             if (BuildConfig.DEBUG) checkId()
         }
+
+        override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
     }
 
     data class EventItem(override val id: String,
@@ -47,6 +50,8 @@ sealed class MatrixItem(
         init {
             if (BuildConfig.DEBUG) checkId()
         }
+
+        override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
     }
 
     data class RoomItem(override val id: String,
@@ -56,6 +61,19 @@ sealed class MatrixItem(
         init {
             if (BuildConfig.DEBUG) checkId()
         }
+
+        override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
+    }
+
+    data class SpaceItem(override val id: String,
+                         override val displayName: String? = null,
+                         override val avatarUrl: String? = null)
+        : MatrixItem(id, displayName, avatarUrl) {
+        init {
+            if (BuildConfig.DEBUG) checkId()
+        }
+
+        override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
     }
 
     data class RoomAliasItem(override val id: String,
@@ -68,6 +86,8 @@ sealed class MatrixItem(
 
         // Best name is the id, and we keep the displayName of the room for the case we need the first letter
         override fun getBestName() = id
+
+        override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
     }
 
     data class GroupItem(override val id: String,
@@ -80,6 +100,8 @@ sealed class MatrixItem(
 
         // Best name is the id, and we keep the displayName of the room for the case we need the first letter
         override fun getBestName() = id
+
+        override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
     }
 
     open fun getBestName(): String {
@@ -92,12 +114,15 @@ sealed class MatrixItem(
         }
     }
 
+    abstract fun updateAvatar(newAvatar: String?): MatrixItem
+
     /**
      * Return the prefix as defined in the matrix spec (and not extracted from the id)
      */
     fun getIdPrefix() = when (this) {
         is UserItem      -> '@'
         is EventItem     -> '$'
+        is SpaceItem,
         is RoomItem      -> '!'
         is RoomAliasItem -> '#'
         is GroupItem     -> '+'
@@ -148,7 +173,11 @@ fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
 
 fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
 
-fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
+fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
+    MatrixItem.SpaceItem(roomId, displayName, avatarUrl)
+} else {
+    MatrixItem.RoomItem(roomId, displayName, avatarUrl)
+}
 
 fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
 
@@ -159,4 +188,8 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName,
 
 fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
 
-fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias ?: "", avatarUrl)
+fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) {
+    MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl)
+} else {
+    MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias, avatarUrl)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt
index e0c52cf9ca95a451b35c9703837846135e9fb435..3742a429d2de65422fa494a1fda16a127a1628cf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt
@@ -33,7 +33,6 @@ internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
  * Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
  */
 internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
-internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
 
 internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index 46256f4b81fc05cb4f5735c2341cf0be24702a93..20ce438d8e8a44fb4fbb1a1201f95b0d43d1cad7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -88,11 +88,9 @@ internal class DefaultAuthenticationService @Inject constructor(
 
         return buildString {
             append(homeServerUrlBase)
+            append(SSO_REDIRECT_PATH)
             if (providerId != null) {
-                append(MSC2858_SSO_REDIRECT_PATH)
                 append("/$providerId")
-            } else {
-                append(SSO_REDIRECT_PATH)
             }
             // Set the redirect url
             appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
index d0d17e2cd5378874e93335cfb0cf1392bbd59214..c718fae3906e6226a43efeb8e1fcf21c00ff6bb4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
@@ -42,7 +42,7 @@ internal data class LoginFlow(
          * the client can show a button for each of the supported providers
          * See MSC #2858
          */
-        @Json(name = "org.matrix.msc2858.identity_providers")
+        @Json(name = "identity_providers")
         val ssoIdentityProvider: List<SsoIdentityProvider>? = null
 
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index d810c8b1a8bb85d47a0e9e38b0952bb38fab1026..2b3c3b28eebfca2f6da87efd999ca7cfadc51398 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database
 import io.realm.DynamicRealm
 import io.realm.FieldAttribute
 import io.realm.RealmMigration
+import org.matrix.android.sdk.api.session.room.model.VersioningState
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
@@ -30,6 +31,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntityFields
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
 import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@@ -44,7 +46,7 @@ import javax.inject.Inject
 class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
 
     companion object {
-        const val SESSION_STORE_SCHEMA_VERSION = 13L
+        const val SESSION_STORE_SCHEMA_VERSION = 14L
     }
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
@@ -63,6 +65,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
         if (oldVersion <= 10) migrateTo11(realm)
         if (oldVersion <= 11) migrateTo12(realm)
         if (oldVersion <= 12) migrateTo13(realm)
+        if (oldVersion <= 13) migrateTo14(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -278,11 +281,29 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
 
     private fun migrateTo13(realm: DynamicRealm) {
         Timber.d("Step 12 -> 13")
-
         // Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12()
         realm.schema.get("SpaceChildSummaryEntity")
                 ?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) }
                 ?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
                 ?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
     }
+
+    private fun migrateTo14(realm: DynamicRealm) {
+        Timber.d("Step 13 -> 14")
+        val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
+                .addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
+                .addField(RoomAccountDataEntityFields.TYPE, String::class.java,  FieldAttribute.INDEXED)
+
+        realm.schema.get("RoomEntity")
+                ?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
+
+        realm.schema.get("RoomSummaryEntity")
+                ?.addField(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, Boolean::class.java, FieldAttribute.INDEXED)
+                ?.transform {
+                    val isHiddenFromUser = it.getString(RoomSummaryEntityFields.VERSIONING_STATE_STR) == VersioningState.UPGRADED_ROOM_JOINED.name
+                    it.setBoolean(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, isHiddenFromUser)
+                }
+
+        roomAccountDataSchema.isEmbedded = true
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt
index 54315a183587d22eaf6bc0da61e82297d42e2a20..4edfdad8970e9a855eacf8aee26e5cca123f1c64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt
@@ -17,17 +17,25 @@
 package org.matrix.android.sdk.internal.database.mapper
 
 import com.squareup.moshi.Moshi
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
+import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntity
 import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import javax.inject.Inject
 
 internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
 
     private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
 
-    fun map(entity: UserAccountDataEntity): UserAccountDataEvent {
-        return UserAccountDataEvent(
+    fun map(entity: UserAccountDataEntity): AccountDataEvent {
+        return AccountDataEvent(
+                type = entity.type ?: "",
+                content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
+        )
+    }
+
+    fun map(entity: RoomAccountDataEntity): AccountDataEvent {
+        return AccountDataEvent(
                 type = entity.type ?: "",
                 content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
index 3fea15bd3dd7facf9edb659156506b9a37b9b2b3..c32c019625e0be622aed19e296871ffb6ca3038d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.database.mapper
 
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
 import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
@@ -92,7 +93,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
                             parentRoomId = roomSummaryEntity.roomId,
                             suggested = it.suggested,
                             canonicalAlias = it.childSummaryEntity?.canonicalAlias,
-                            aliases = it.childSummaryEntity?.aliases?.toList()
+                            aliases = it.childSummaryEntity?.aliases?.toList(),
+                            worldReadable = it.childSummaryEntity?.joinRules == RoomJoinRules.PUBLIC
                     )
                 },
                 flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomAccountDataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomAccountDataEntity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..40040b573826b8bbfa0bc7e4c91468e6a3dc595b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomAccountDataEntity.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.database.model
+
+import io.realm.RealmObject
+import io.realm.annotations.Index
+import io.realm.annotations.RealmClass
+
+@RealmClass(embedded = true)
+internal open class RoomAccountDataEntity(
+        @Index var type: String? = null,
+        var contentStr: String? = null
+) : RealmObject()
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 58297776f06b1f47189aee4cf22ad3ffa718abc1..65483e05bf65b622088e19923bdaaf0fced2a4e1 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
@@ -23,7 +23,8 @@ import io.realm.annotations.PrimaryKey
 
 internal open class RoomEntity(@PrimaryKey var roomId: String = "",
                                var chunks: RealmList<ChunkEntity> = RealmList(),
-                               var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList()
+                               var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
+                               var accountData: RealmList<RoomAccountDataEntity> = RealmList()
 ) : RealmObject() {
 
     private var membershipStr: String = Membership.NONE.name
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
index 1001f9cd66ea11d0ae57d6cdd265478d6406c467..64dc08e8274d1e98bd201a363a8a3eda6d14f38d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
@@ -232,6 +232,12 @@ internal open class RoomSummaryEntity(
             }
         }
 
+    @Index
+    var isHiddenFromUser: Boolean = false
+        set(value) {
+            if (value != field) field = value
+        }
+
     @Index
     private var versioningStateStr: String = VersioningState.NONE.name
     var versioningState: VersioningState
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 72ae512fa5a444529809da11b63028355ba4251e..19472e21d96838372b3fa283d3dee5ca23547b9d 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
@@ -62,6 +62,7 @@ import io.realm.annotations.RealmModule
             UserAccountDataEntity::class,
             ScalarTokenEntity::class,
             WellknownIntegrationManagerConfigEntity::class,
+            RoomAccountDataEntity::class,
             SpaceChildSummaryEntity::class,
             SpaceParentSummaryEntity::class
         ])
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomEntityQueries.kt
index 27e8d9d8d1a0539bbb11bfd7b8e24db7e24e4261..a551f973793c8b9d91c88d1d296f676c68f59b0b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomEntityQueries.kt
@@ -29,6 +29,10 @@ internal fun RoomEntity.Companion.where(realm: Realm, roomId: String): RealmQuer
             .equalTo(RoomEntityFields.ROOM_ID, roomId)
 }
 
+internal fun RoomEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomEntity {
+    return where(realm, roomId).findFirst() ?: realm.createObject(RoomEntity::class.java, roomId)
+}
+
 internal fun RoomEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<RoomEntity> {
     val query = realm.where<RoomEntity>()
     if (membership != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt
index 9afdb40ed1559cd1aad9d46c2f7afc37ce23f1de..8be11e80f37a40d898f3c98dbe74e6d1e2cdedd0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt
@@ -16,13 +16,13 @@
 
 package org.matrix.android.sdk.internal.network
 
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.internal.auth.SessionParamsStore
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.task.TaskExecutor
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -44,7 +44,6 @@ internal class GlobalErrorHandler @Inject constructor(
                 sessionParamsStore.setTokenInvalid(sessionId)
             }
         }
-
         listener?.onGlobalError(globalError)
     }
 
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 b100a336a79b2c3cc909312bd1941391d47706db..1f4797819892a8455d862832d00b416b25323cf9 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
@@ -74,6 +74,7 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.session.sync.job.SyncThread
 import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
+import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService
 import org.matrix.android.sdk.internal.util.createUIHandler
 import timber.log.Timber
 import javax.inject.Inject
@@ -117,7 +118,7 @@ internal class DefaultSession @Inject constructor(
         private val contentDownloadStateTracker: ContentDownloadStateTracker,
         private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
         private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
-        private val accountDataService: Lazy<AccountDataService>,
+        private val accountDataService: Lazy<UserAccountDataService>,
         private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
         private val accountService: Lazy<AccountService>,
         private val eventService: Lazy<EventService>,
@@ -130,6 +131,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(),
@@ -144,9 +146,7 @@ internal class DefaultSession @Inject constructor(
         SecureStorageService by secureStorageService.get(),
         HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
         ProfileService by profileService.get(),
-        AccountDataService by accountDataService.get(),
-        AccountService by accountService.get(),
-        GlobalErrorHandler.Listener {
+        AccountService by accountService.get() {
 
     override val sharedSecretStorageService: SharedSecretStorageService
         get() = _sharedSecretStorageService.get()
@@ -164,16 +164,16 @@ internal class DefaultSession @Inject constructor(
     override fun open() {
         assert(!isOpen)
         isOpen = true
+        globalErrorHandler.listener = this
         cryptoService.get().ensureDevice()
         uiHandler.post {
             lifecycleObservers.forEach {
                 it.onSessionStarted(this)
             }
-            sessionListeners.dispatch {
-                it.onSessionStarted(this)
+            sessionListeners.dispatch { _, listener ->
+                listener.onSessionStarted(this)
             }
         }
-        globalErrorHandler.listener = this
     }
 
     override fun requireBackgroundSync() {
@@ -213,13 +213,13 @@ internal class DefaultSession @Inject constructor(
         // timelineEventDecryptor.destroy()
         uiHandler.post {
             lifecycleObservers.forEach { it.onSessionStopped(this) }
-            sessionListeners.dispatch {
-                it.onSessionStopped(this)
+            sessionListeners.dispatch { _, listener ->
+                listener.onSessionStopped(this)
             }
         }
         cryptoService.get().close()
-        isOpen = false
         globalErrorHandler.listener = null
+        isOpen = false
     }
 
     override fun getSyncStateLive() = getSyncThread().liveState()
@@ -243,8 +243,8 @@ internal class DefaultSession @Inject constructor(
             lifecycleObservers.forEach {
                 it.onClearCache(this)
             }
-            sessionListeners.dispatch {
-                it.onClearCache(this)
+            sessionListeners.dispatch { _, listener ->
+                listener.onClearCache(this)
             }
         }
         withContext(NonCancellable) {
@@ -254,8 +254,8 @@ internal class DefaultSession @Inject constructor(
     }
 
     override fun onGlobalError(globalError: GlobalError) {
-        sessionListeners.dispatch {
-            it.onGlobalError(this, globalError)
+        sessionListeners.dispatch { _, listener ->
+            listener.onGlobalError(this, globalError)
         }
     }
 
@@ -293,6 +293,8 @@ internal class DefaultSession @Inject constructor(
 
     override fun openIdService(): OpenIdService = openIdService.get()
 
+    override fun userAccountDataService(): AccountDataService = accountDataService.get()
+
     override fun getOkHttpClient(): OkHttpClient {
         return unauthenticatedWithCertificateOkHttpClient.get()
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
index 563ff4ada322cb827702b3ef7612e1e25a99caad..d5c661b1e474d4f979df60103f7b147d2c75dfc6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
@@ -16,10 +16,17 @@
 
 package org.matrix.android.sdk.internal.session
 
+import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.internal.SessionManager
+import org.matrix.android.sdk.internal.di.SessionId
+import timber.log.Timber
 import javax.inject.Inject
 
-internal class SessionListeners @Inject constructor() {
+@SessionScope
+internal class SessionListeners @Inject constructor(
+        @SessionId private val sessionId: String,
+        private val sessionManager: SessionManager) {
 
     private val listeners = mutableSetOf<Session.Listener>()
 
@@ -35,11 +42,18 @@ internal class SessionListeners @Inject constructor() {
         }
     }
 
-    fun dispatch(block: (Session.Listener) -> Unit) {
+    fun dispatch(block: (Session, Session.Listener) -> Unit) {
         synchronized(listeners) {
+            val session = getSession() ?: return Unit.also {
+                Timber.w("You don't have any attached session")
+            }
             listeners.forEach {
-                block(it)
+                tryOrNull { block(session, it) }
             }
         }
     }
+
+    private fun getSession(): Session? {
+        return sessionManager.getSessionComponent(sessionId)?.session()
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
index de74b3481822893085a9c3ed49e11ac318ba7f43..49ce92372e2381a984ab4516fc3efea1d811021f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
@@ -93,7 +93,7 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces
 import org.matrix.android.sdk.internal.session.room.tombstone.RoomTombstoneEventProcessor
 import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorageService
 import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
-import org.matrix.android.sdk.internal.session.user.accountdata.DefaultAccountDataService
+import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService
 import org.matrix.android.sdk.internal.session.widgets.DefaultWidgetURLFormatter
 import org.matrix.android.sdk.internal.util.md5
 import retrofit2.Retrofit
@@ -364,7 +364,7 @@ internal abstract class SessionModule {
     abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
 
     @Binds
-    abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService
+    abstract fun bindAccountDataService(service: UserAccountDataService): AccountDataService
 
     @Binds
     abstract fun bindEventService(service: DefaultEventService): EventService
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 8d7e9e819a7bc7a95590f85622108c8797e3fc84..61ea660b6045313708ca783a80078f9e74bb68ee 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
@@ -24,18 +24,15 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
 import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
-import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
 import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
 import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
 import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
 import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
 import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
-import org.matrix.android.sdk.api.session.room.model.call.CallSignallingContent
-import org.matrix.android.sdk.api.util.Optional
+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 timber.log.Timber
-import java.math.BigDecimal
 import javax.inject.Inject
 
 @SessionScope
@@ -192,6 +189,9 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
             // Ignore remote echo
             return
         }
+        if (event.roomId == null || event.senderId == null) {
+            return
+        }
         if (event.senderId == userId) {
             // discard current call, it's answered by another of my session
             activeCallHandler.removeCall(call.callId)
@@ -201,20 +201,16 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
                 Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
                 return
             }
-            call.apply {
-                opponentPartyId = Optional.from(content.partyId)
-                opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
-                capabilities = content.capabilities ?: CallCapabilities()
-            }
+            mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities)
             callListenersDispatcher.onCallAnswerReceived(content)
         }
     }
 
-    private fun MxCall.partyIdsMatches(contentSignallingContent: CallSignallingContent): Boolean {
-        return opponentPartyId?.getOrNull() == contentSignallingContent.partyId
+    private fun MxCall.partyIdsMatches(contentSignalingContent: CallSignalingContent): Boolean {
+        return opponentPartyId?.getOrNull() == contentSignalingContent.partyId
     }
 
-    private fun CallSignallingContent.getCall(): MxCall? {
+    private fun CallSignalingContent.getCall(): MxCall? {
         val currentCall = callId?.let {
             activeCallHandler.getCallWithId(it)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
index 7d046cb6422e15e0028653e16fa395ed27770919..da1f84cc899f297a337de1c408af899f8c1ebf08 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.call
 import org.matrix.android.sdk.api.session.call.CallListener
 import org.matrix.android.sdk.api.session.call.CallSignalingService
 import org.matrix.android.sdk.api.session.call.MxCall
-import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
 import org.matrix.android.sdk.api.session.call.TurnServerResponse
 import org.matrix.android.sdk.internal.session.SessionScope
 import timber.log.Timber
@@ -30,18 +29,13 @@ internal class DefaultCallSignalingService @Inject constructor(
         private val callSignalingHandler: CallSignalingHandler,
         private val mxCallFactory: MxCallFactory,
         private val activeCallHandler: ActiveCallHandler,
-        private val turnServerDataSource: TurnServerDataSource,
-        private val pstnProtocolChecker: PSTNProtocolChecker
+        private val turnServerDataSource: TurnServerDataSource
 ) : CallSignalingService {
 
     override suspend fun getTurnServer(): TurnServerResponse {
         return turnServerDataSource.getTurnServer()
     }
 
-    override fun getPSTNProtocolChecker(): PSTNProtocolChecker {
-        return pstnProtocolChecker
-    }
-
     override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
         return mxCallFactory.createOutgoingCall(roomId, otherUserId, isVideoCall).also {
             activeCallHandler.addCall(it)
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 b14cdca63c5c6e059600b8e635019afdc6248a82..547be2253f80d60c28d613c41be91872a4565f68 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
@@ -17,18 +17,17 @@
 package org.matrix.android.sdk.internal.session.call
 
 import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.session.call.CallIdGenerator
 import org.matrix.android.sdk.api.session.call.MxCall
 import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
 import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
-import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
 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 java.math.BigDecimal
-import java.util.UUID
 import javax.inject.Inject
 
 internal class MxCallFactory @Inject constructor(
@@ -48,32 +47,38 @@ internal class MxCallFactory @Inject constructor(
                 roomId = roomId,
                 userId = userId,
                 ourPartyId = deviceId ?: "",
-                opponentUserId = opponentUserId,
                 isVideoCall = content.isVideo(),
                 localEchoEventFactory = localEchoEventFactory,
                 eventSenderProcessor = eventSenderProcessor,
                 matrixConfiguration = matrixConfiguration,
                 getProfileInfoTask = getProfileInfoTask
         ).apply {
-            opponentPartyId = Optional.from(content.partyId)
-            opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
-            capabilities = content.capabilities ?: CallCapabilities()
+            updateOpponentData(opponentUserId, content, content.capabilities)
         }
     }
 
     fun createOutgoingCall(roomId: String, opponentUserId: String, isVideoCall: Boolean): MxCall {
         return MxCallImpl(
-                callId = UUID.randomUUID().toString(),
+                callId = CallIdGenerator.generate(),
                 isOutgoing = true,
                 roomId = roomId,
                 userId = userId,
                 ourPartyId = deviceId ?: "",
-                opponentUserId = opponentUserId,
                 isVideoCall = isVideoCall,
                 localEchoEventFactory = localEchoEventFactory,
                 eventSenderProcessor = eventSenderProcessor,
                 matrixConfiguration = matrixConfiguration,
                 getProfileInfoTask = getProfileInfoTask
-        )
+        ).apply {
+            // Setup with this userId, might be updated when processing the Answer event
+            this.opponentUserId = opponentUserId
+        }
+    }
+
+    fun updateOutgoingCallWithOpponentData(call: MxCall,
+                                           userId: String,
+                                           content: CallSignalingContent,
+                                           callCapabilities: CallCapabilities?) {
+        (call as? MxCallImpl)?.updateOpponentData(userId, content, callCapabilities)
     }
 }
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 88fba0ea859a3ed43c5fa91d73fbf159912a6303..f101685a4b46b6f6248d79e635956bf139d8dda6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.call.model
 
 import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.session.call.CallIdGenerator
 import org.matrix.android.sdk.api.session.call.CallState
 import org.matrix.android.sdk.api.session.call.MxCall
 import org.matrix.android.sdk.api.session.events.model.Content
@@ -36,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
 import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
 import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent
 import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
+import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
 import org.matrix.android.sdk.api.session.room.model.call.SdpType
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
@@ -43,14 +45,13 @@ 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 timber.log.Timber
-import java.util.UUID
+import java.math.BigDecimal
 
 internal class MxCallImpl(
         override val callId: String,
         override val isOutgoing: Boolean,
         override val roomId: String,
         private val userId: String,
-        override val opponentUserId: String,
         override val isVideoCall: Boolean,
         override val ourPartyId: String,
         private val localEchoEventFactory: LocalEchoEventFactory,
@@ -61,8 +62,16 @@ internal class MxCallImpl(
 
     override var opponentPartyId: Optional<String>? = null
     override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
+    override lateinit var opponentUserId: String
     override var capabilities: CallCapabilities? = null
 
+    fun updateOpponentData(userId: String, content: CallSignalingContent, callCapabilities: CallCapabilities?) {
+        opponentPartyId = Optional.from(content.partyId)
+        opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
+        opponentUserId = userId
+        capabilities = callCapabilities ?: CallCapabilities()
+    }
+
     override var state: CallState = CallState.Idle
         set(value) {
             field = value
@@ -202,7 +211,10 @@ internal class MxCallImpl(
                 .also { eventSenderProcessor.postEvent(it) }
     }
 
-    override suspend fun transfer(targetUserId: String, targetRoomId: String?) {
+    override suspend fun transfer(targetUserId: String,
+                                  targetRoomId: String?,
+                                  createCallId: String?,
+                                  awaitCallId: String?) {
         val profileInfoParams = GetProfileInfoTask.Params(targetUserId)
         val profileInfo = try {
             getProfileInfoTask.execute(profileInfoParams)
@@ -213,15 +225,16 @@ internal class MxCallImpl(
         CallReplacesContent(
                 callId = callId,
                 partyId = ourPartyId,
-                replacementId = UUID.randomUUID().toString(),
+                replacementId = CallIdGenerator.generate(),
                 version = MxCall.VOIP_PROTO_VERSION.toString(),
                 targetUser = CallReplacesContent.TargetUser(
                         id = targetUserId,
                         displayName = profileInfo?.get(ProfileService.DISPLAY_NAME_KEY) as? String,
                         avatarUrl = profileInfo?.get(ProfileService.AVATAR_URL_KEY) as? String
                 ),
-                targerRoomId = targetRoomId,
-                createCall = UUID.randomUUID().toString()
+                targetRoomId = targetRoomId,
+                awaitCall = awaitCallId,
+                createCall = createCallId
         )
                 .let { createEventAndLocalEcho(type = EventType.CALL_REPLACES, roomId = roomId, content = it.toContent()) }
                 .also { eventSenderProcessor.postEvent(it) }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
index c28668a53eb77074f077c62a87d8e826fd4a8ac7..82cd682eae0ecc50bd69db958e16f8685f1d9d03 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
@@ -23,8 +23,11 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
 import org.matrix.android.sdk.api.util.MimeTypes
 import timber.log.Timber
 import java.io.ByteArrayOutputStream
+import javax.inject.Inject
 
-internal object ThumbnailExtractor {
+internal class ThumbnailExtractor @Inject constructor(
+        private val context: Context
+) {
 
     class ThumbnailData(
             val width: Int,
@@ -34,22 +37,22 @@ internal object ThumbnailExtractor {
             val mimeType: String
     )
 
-    fun extractThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? {
+    fun extractThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
         return if (attachment.type == ContentAttachmentData.Type.VIDEO) {
-            extractVideoThumbnail(context, attachment)
+            extractVideoThumbnail(attachment)
         } else {
             null
         }
     }
 
-    private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? {
+    private fun extractVideoThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
         var thumbnailData: ThumbnailData? = null
         val mediaMetadataRetriever = MediaMetadataRetriever()
         try {
             mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
             mediaMetadataRetriever.frameAtTime?.let { thumbnail ->
                 val outputStream = ByteArrayOutputStream()
-                thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+                thumbnail.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
                 val thumbnailWidth = thumbnail.width
                 val thumbnailHeight = thumbnail.height
                 val thumbnailSize = outputStream.size()
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 06cbf1ba90241f7dd101e071ae92e3c0fbf0639b..237411db53d72f614b887eb30af833a5a000706e 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
@@ -82,6 +82,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
     @Inject lateinit var cancelSendTracker: CancelSendTracker
     @Inject lateinit var imageCompressor: ImageCompressor
     @Inject lateinit var videoCompressor: VideoCompressor
+    @Inject lateinit var thumbnailExtractor: ThumbnailExtractor
     @Inject lateinit var localEchoRepository: LocalEchoRepository
     @Inject lateinit var temporaryFileCreator: TemporaryFileCreator
 
@@ -302,7 +303,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
      * If appropriate, it will create and upload a thumbnail
      */
     private suspend fun dealWithThumbnail(params: Params): UploadThumbnailResult? {
-        return ThumbnailExtractor.extractThumbnail(context, params.attachment)
+        return thumbnailExtractor.extractThumbnail(params.attachment)
                 ?.let { thumbnailData ->
                     val thumbnailProgressListener = object : ProgressRequestBody.Listener {
                         override fun onProgress(current: Long, total: Long) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
index 475781ef017a4eda68f64f12ad6ec96620f074e7..4f88d8eb9507b1ca1eccd9cea549a90d74f8efe6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
@@ -44,7 +44,7 @@ import org.matrix.android.sdk.internal.session.profile.BindThreePidsTask
 import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
-import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
+import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.internal.util.ensureProtocol
@@ -77,7 +77,7 @@ internal class DefaultIdentityService @Inject constructor(
         private val submitTokenForBindingTask: IdentitySubmitTokenForBindingTask,
         private val unbindThreePidsTask: UnbindThreePidsTask,
         private val identityApiProvider: IdentityApiProvider,
-        private val accountDataDataSource: AccountDataDataSource,
+        private val accountDataDataSource: UserAccountDataDataSource,
         private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
         private val sessionParams: SessionParams
 ) : IdentityService, SessionLifecycleObserver {
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 3df9a00cc15fa494a95945c5ccd8b85889a98740..f79f8084a8e501d0f4a8bb1908d481ef18a9896e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
@@ -33,8 +33,8 @@ import org.matrix.android.sdk.internal.extensions.observeNotNull
 import org.matrix.android.sdk.api.session.SessionLifecycleObserver
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
-import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
 import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
 import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
@@ -57,7 +57,7 @@ import javax.inject.Inject
 internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration,
                                                       @SessionDatabase private val monarchy: Monarchy,
                                                       private val updateUserAccountDataTask: UpdateUserAccountDataTask,
-                                                      private val accountDataDataSource: AccountDataDataSource,
+                                                      private val accountDataDataSource: UserAccountDataDataSource,
                                                       private val widgetFactory: WidgetFactory)
     : SessionLifecycleObserver {
 
@@ -240,7 +240,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
         )
     }
 
-    private fun UserAccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
+    private fun AccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
         return extractWidgetSequence(widgetFactory)
                 .filter {
                     WidgetType.IntegrationManager == it.type
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt
index 7db9d8f68ae239e445fed97eb5e8d5483c460a75..134da4ce51b975135071a762acc06fd9864aac0d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt
@@ -33,8 +33,8 @@ internal class DefaultPermalinkService @Inject constructor(
         return permalinkFactory.createPermalink(id)
     }
 
-    override fun createRoomPermalink(roomId: String): String? {
-        return permalinkFactory.createRoomPermalink(roomId)
+    override fun createRoomPermalink(roomId: String, viaServers: List<String>?): String? {
+        return permalinkFactory.createRoomPermalink(roomId, viaServers)
     }
 
     override fun createPermalink(roomId: String, eventId: String): String {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt
index 970752449aa04e00960327939be32ba09d4f2225..639e45582a07eaab79fa7cdafbebb556be3e4df2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt
@@ -40,11 +40,18 @@ internal class PermalinkFactory @Inject constructor(
         } else MATRIX_TO_URL_BASE + escape(id)
     }
 
-    fun createRoomPermalink(roomId: String): String? {
+    fun createRoomPermalink(roomId: String, via: List<String>? = null): String? {
         return if (roomId.isEmpty()) {
             null
         } else {
-            MATRIX_TO_URL_BASE + escape(roomId) + viaParameterFinder.computeViaParams(userId, roomId)
+            buildString {
+                append(MATRIX_TO_URL_BASE)
+                append(escape(roomId))
+                append(
+                        via?.takeIf { it.isNotEmpty() }?.let { viaParameterFinder.asUrlViaParameters(it) }
+                                ?: viaParameterFinder.computeViaParams(userId, roomId)
+                )
+            }
         }
     }
 
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 0da60e9ba2affa030270720565911884ab7ab36d..72fbfcced5ee6f6dbf608d623bebb8f238380a32 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
@@ -39,8 +39,11 @@ internal class ViaParameterFinder @Inject constructor(
      * current user one.
      */
     fun computeViaParams(userId: String, roomId: String): String {
-        return computeViaParams(userId, roomId, 3)
-                .joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
+        return asUrlViaParameters(computeViaParams(userId, roomId, 3))
+    }
+
+    fun asUrlViaParameters(viaList: List<String>): String {
+        return viaList.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
     }
 
     fun computeViaParams(userId: String, roomId: String, max: Int): List<String> {
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 a5e066dae80331f7c8601fcde66af626d69bedab..5a2eef7e8a3dbe5fa23a93cfb9bea6a29026f95d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.room
 
 import androidx.lifecycle.LiveData
+import org.matrix.android.sdk.api.session.accountdata.AccountDataService
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.Room
@@ -41,34 +42,35 @@ import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
+import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService
 import org.matrix.android.sdk.internal.session.room.state.SendStateTask
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 import org.matrix.android.sdk.internal.session.search.SearchTask
 import org.matrix.android.sdk.internal.session.space.DefaultSpace
 import org.matrix.android.sdk.internal.util.awaitCallback
 import java.security.InvalidParameterException
-import javax.inject.Inject
 
-internal class DefaultRoom @Inject constructor(override val roomId: String,
-                                               private val roomSummaryDataSource: RoomSummaryDataSource,
-                                               private val timelineService: TimelineService,
-                                               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 sendStateTask: SendStateTask,
-                                               private val viaParameterFinder: ViaParameterFinder,
-                                               private val searchTask: SearchTask) :
+internal class DefaultRoom(override val roomId: String,
+                           private val roomSummaryDataSource: RoomSummaryDataSource,
+                           private val timelineService: TimelineService,
+                           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 sendStateTask: SendStateTask,
+                           private val viaParameterFinder: ViaParameterFinder,
+                           private val searchTask: SearchTask) :
         Room,
         TimelineService by timelineService,
         SendService by sendService,
@@ -83,7 +85,8 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
         TagsService by tagsService,
         RelationService by relationService,
         MembershipService by roomMembersService,
-        RoomPushRuleService by roomPushRuleService {
+        RoomPushRuleService by roomPushRuleService,
+        AccountDataService by roomAccountDataService {
 
     override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
         return roomSummaryDataSource.getRoomSummaryLive(roomId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index 6fee630510b01624b9904d9ec7d576af895438a6..4f1260403938071011519252c458d94d53a2323e 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
@@ -360,4 +360,13 @@ internal interface RoomAPI {
     suspend fun deleteTag(@Path("userId") userId: String,
                           @Path("roomId") roomId: String,
                           @Path("tag") tag: String)
+
+    /**
+     * Set an AccountData event to the room.
+     */
+    @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/account_data/{type}")
+    suspend fun setRoomAccountData(@Path("userId") userId: String,
+                                   @Path("roomId") roomId: String,
+                                   @Path("type") type: String,
+                                   @Body content: JsonDict)
 }
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 3f743c29229d4977adc881ca4ed0f80ba068443b..8efbf2360a27ad1bfed07660816ccffcb1d8c60a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService
 import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
 import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
 import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
@@ -60,6 +61,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                                                       private val relationServiceFactory: DefaultRelationService.Factory,
                                                       private val membershipServiceFactory: DefaultMembershipService.Factory,
                                                       private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
+                                                      private val roomAccountDataServiceFactory: RoomAccountDataService.Factory,
                                                       private val sendStateTask: SendStateTask,
                                                       private val viaParameterFinder: ViaParameterFinder,
                                                       private val searchTask: SearchTask) :
@@ -84,6 +86,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                 relationService = relationServiceFactory.create(roomId),
                 roomMembersService = membershipServiceFactory.create(roomId),
                 roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
+                roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
                 sendStateTask = sendStateTask,
                 searchTask = searchTask,
                 viaParameterFinder = viaParameterFinder
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
index 8f3445bec384ea67ad43366f10dd1e968ec2c8dc..d88c1950560d3928a682b70f8694d165fcd4eefc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
@@ -28,6 +28,8 @@ import org.matrix.android.sdk.api.session.space.SpaceService
 import org.matrix.android.sdk.internal.session.DefaultFileService
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
+import org.matrix.android.sdk.internal.session.room.accountdata.DefaultUpdateRoomAccountDataTask
+import org.matrix.android.sdk.internal.session.room.accountdata.UpdateRoomAccountDataTask
 import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
 import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask
 import org.matrix.android.sdk.internal.session.room.alias.DefaultDeleteRoomAliasTask
@@ -236,6 +238,9 @@ internal abstract class RoomModule {
     @Binds
     abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask
 
+    @Binds
+    abstract fun bindUpdateRoomAccountDataTask(task: DefaultUpdateRoomAccountDataTask): UpdateRoomAccountDataTask
+
     @Binds
     abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0bcf9d7f386088e3c0624cfc3d712e4b7cbdc3a0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.room.accountdata
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.Transformations
+import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.api.util.toOptional
+import org.matrix.android.sdk.internal.database.RealmSessionProvider
+import org.matrix.android.sdk.internal.database.mapper.AccountDataMapper
+import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomEntity
+import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import javax.inject.Inject
+
+internal class RoomAccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
+                                                             private val realmSessionProvider: RealmSessionProvider,
+                                                             private val accountDataMapper: AccountDataMapper) {
+
+    fun getAccountDataEvent(roomId: String, type: String): AccountDataEvent? {
+        return getAccountDataEvents(roomId, setOf(type)).firstOrNull()
+    }
+
+    fun getLiveAccountDataEvent(roomId: String, type: String): LiveData<Optional<AccountDataEvent>> {
+        return Transformations.map(getLiveAccountDataEvents(roomId, setOf(type))) {
+            it.firstOrNull()?.toOptional()
+        }
+    }
+
+    fun getAccountDataEvents(roomId: String, types: Set<String>): List<AccountDataEvent> {
+        return realmSessionProvider.withRealm { realm ->
+            val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return@withRealm emptyList()
+            roomEntity.accountDataEvents(types)
+        }
+    }
+
+    fun getLiveAccountDataEvents(roomId: String, types: Set<String>): LiveData<List<AccountDataEvent>> {
+        val liveRoomEntity = monarchy.findAllManagedWithChanges { RoomEntity.where(it, roomId) }
+        val resultLiveData = MediatorLiveData<List<AccountDataEvent>>()
+        resultLiveData.addSource(liveRoomEntity) {
+            val roomEntity = it.realmResults.firstOrNull()
+            if (roomEntity == null) {
+                resultLiveData.postValue(emptyList())
+            } else {
+                val mappedResult = roomEntity.accountDataEvents(types)
+                resultLiveData.postValue(mappedResult)
+            }
+        }
+        return resultLiveData
+    }
+
+    private fun RoomEntity.accountDataEvents(types: Set<String>): List<AccountDataEvent> {
+        val query = accountData.where()
+        if (types.isNotEmpty()) {
+            query.`in`(RoomAccountDataEntityFields.TYPE, types.toTypedArray())
+        }
+        return query.findAll().map { accountDataMapper.map(it) }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9e9e9dc322a4534c6d2b651f1f36a6f303a84a54
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room.accountdata
+
+import androidx.lifecycle.LiveData
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataService
+import org.matrix.android.sdk.api.session.events.model.Content
+import org.matrix.android.sdk.api.util.Optional
+
+internal class RoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String,
+                                                                  private val dataSource: RoomAccountDataDataSource,
+                                                                  private val updateRoomAccountDataTask: UpdateRoomAccountDataTask
+) : AccountDataService {
+
+    @AssistedFactory
+    interface Factory {
+        fun create(roomId: String): RoomAccountDataService
+    }
+
+    override fun getAccountDataEvent(type: String): AccountDataEvent? {
+        return dataSource.getAccountDataEvent(roomId, type)
+    }
+
+    override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
+        return dataSource.getLiveAccountDataEvent(roomId, type)
+    }
+
+    override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
+        return dataSource.getAccountDataEvents(roomId, types)
+    }
+
+    override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
+        return dataSource.getLiveAccountDataEvents(roomId, types)
+    }
+
+    override suspend fun updateAccountData(type: String, content: Content) {
+        val params = UpdateRoomAccountDataTask.Params(roomId, type, content)
+        return updateRoomAccountDataTask.execute(params)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/UpdateRoomAccountDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/UpdateRoomAccountDataTask.kt
new file mode 100644
index 0000000000000000000000000000000000000000..db18c189089958c65abaa77d6fc52eea337f5f85
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/UpdateRoomAccountDataTask.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.session.room.accountdata
+
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.room.RoomAPI
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface UpdateRoomAccountDataTask : Task<UpdateRoomAccountDataTask.Params, Unit> {
+
+    data class Params(
+            val roomId: String,
+            val type: String,
+            val content: JsonDict
+    )
+}
+
+internal class DefaultUpdateRoomAccountDataTask @Inject constructor(
+        private val roomApi: RoomAPI,
+        @UserId private val userId: String,
+        private val globalErrorReceiver: GlobalErrorReceiver
+) : UpdateRoomAccountDataTask {
+
+    override suspend fun execute(params: UpdateRoomAccountDataTask.Params) {
+        return executeRequest(globalErrorReceiver) {
+            roomApi.setRoomAccountData(userId, params.roomId, params.type, params.content)
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/RoomCreateEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/RoomCreateEventProcessor.kt
index 95572c203cc19680a29098374cba1732b5fc33f6..cc66a0a2d2a7e148a030c5f4321556e95dd71d42 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/RoomCreateEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/RoomCreateEventProcessor.kt
@@ -37,6 +37,7 @@ internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveP
         val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst()
                 ?: RoomSummaryEntity(predecessorRoomId)
         predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
+        predecessorRoomSummary.isHiddenFromUser = true
         realm.insertOrUpdate(predecessorRoomSummary)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
index c6f4bbb4e1a9fdd7cadb464acfd87ea4a42d5a31..219e9c903faecd05733e902cde31951637966141 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
@@ -23,6 +23,8 @@ import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
 import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
 import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.RoomNameContent
 import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
@@ -105,7 +107,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
                     numJoinedMembers = publicRepoResult.numJoinedMembers,
                     viaServers = serverList,
                     roomType = null, // would be nice to get that from directory...
-                    someMembers = null
+                    someMembers = null,
+                    isPublic = true
             )
         }
 
@@ -143,6 +146,11 @@ internal class DefaultPeekRoomTask @Inject constructor(
                 }
             }
 
+            val historyVisibility =
+                    stateEvents
+                            .lastOrNull { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY && it.stateKey?.isNotEmpty() == true }
+                            ?.let { it.content?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility }
+
             val roomType = stateEvents
                     .lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
                     ?.content
@@ -158,7 +166,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
                     numJoinedMembers = memberCount,
                     roomType = roomType,
                     viaServers = serverList,
-                    someMembers = someMembers
+                    someMembers = someMembers,
+                    isPublic = historyVisibility == RoomHistoryVisibility.WORLD_READABLE
             )
         } catch (failure: Throwable) {
             // Would be M_FORBIDDEN if cannot peek :/
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 c1ad6205c38c383b7d4d76e3c498605e524211fa..f505b13b337ee1325f4e3eacd544d4e6ccf87292 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
@@ -73,6 +73,7 @@ internal class LocalEchoEventFactory @Inject constructor(
         @UserId private val userId: String,
         private val markdownParser: MarkdownParser,
         private val textPillsUtils: TextPillsUtils,
+        private val thumbnailExtractor: ThumbnailExtractor,
         private val localEchoRepository: LocalEchoRepository,
         private val permalinkFactory: PermalinkFactory
 ) {
@@ -261,7 +262,7 @@ internal class LocalEchoEventFactory @Inject constructor(
         val width = firstFrame?.width ?: 0
         mediaDataRetriever.release()
 
-        val thumbnailInfo = ThumbnailExtractor.extractThumbnail(context, attachment)?.let {
+        val thumbnailInfo = thumbnailExtractor.extractThumbnail(attachment)?.let {
             ThumbnailInfo(
                     width = it.width,
                     height = it.height,
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 126458b0820e175e874cedf429847bd332bc1228..bff1af60ca9af4c1e8f3df7f489592ba76c4c908 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
@@ -36,7 +36,6 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomType
-import org.matrix.android.sdk.api.session.room.model.VersioningState
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
@@ -244,7 +243,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
         query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
         query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
         query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
-        query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
+        query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false)
 
         queryParams.roomCategoryFilter?.let {
             when (it) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index d488fdfc2a91fa07e400afd7a170715a355a5b1d..7cbcfee713565ecdd840f3c08d26201ef896ce4b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -21,6 +21,7 @@ import io.realm.kotlin.createObject
 import org.matrix.android.sdk.api.extensions.tryOrNull
 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.accountdata.RoomAccountDataTypes
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
 import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
@@ -28,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
 import org.matrix.android.sdk.api.session.room.model.RoomNameContent
 import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
 import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.room.model.VersioningState
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.crypto.EventDecryptor
@@ -55,10 +57,10 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.extensions.clearWith
 import org.matrix.android.sdk.internal.query.process
 import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
+import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource
 import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
 import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
-import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
 import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
 import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
 import timber.log.Timber
@@ -71,7 +73,7 @@ internal class RoomSummaryUpdater @Inject constructor(
         private val roomAvatarResolver: RoomAvatarResolver,
         private val eventDecryptor: EventDecryptor,
         private val crossSigningService: DefaultCrossSigningService,
-        private val stateEventDataSource: StateEventDataSource) {
+        private val roomAccountDataDataSource: RoomAccountDataDataSource) {
 
     fun update(realm: Realm,
                roomId: String,
@@ -100,6 +102,10 @@ internal class RoomSummaryUpdater @Inject constructor(
             roomSummaryEntity.membership = membership
         }
 
+        // Hard to filter from the app now we use PagedList...
+        roomSummaryEntity.isHiddenFromUser = roomSummaryEntity.versioningState == VersioningState.UPGRADED_ROOM_JOINED
+                || roomAccountDataDataSource.getAccountDataEvent(roomId, RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM) != null
+
         val lastNameEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
         val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
         val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
@@ -297,7 +303,7 @@ internal class RoomSummaryUpdater @Inject constructor(
 //            Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}")
 
             lookupMap.entries
-                    .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN  }
+                    .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
                     .forEach { entry ->
                         val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst()
                         if (parent != null) {
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 d0ad19245f9860a2982b96a1eac15d865bd12637..9c6153b34969ce07357c80c73173c462b6ecc326 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
@@ -147,7 +147,8 @@ internal class DefaultSpaceService @Inject constructor(
                                                         parentRoomId = childStateEv.roomId,
                                                         suggested = childStateEvContent.suggested,
                                                         canonicalAlias = childSummary.canonicalAlias,
-                                                        aliases = childSummary.aliases
+                                                        aliases = childSummary.aliases,
+                                                        worldReadable = childSummary.worldReadable
                                                 )
                                             }
                                         }.orEmpty()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
index 7cebbb0192279127bb463ece2a1da75604edd24e..c3586bcea7a545fd2e97d6065dc57d090ad71305 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.initsync.InitSyncStep
 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.tag.RoomTagContent
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
 import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
@@ -55,7 +54,6 @@ import org.matrix.android.sdk.internal.session.initsync.mapWithProgress
 import org.matrix.android.sdk.internal.session.initsync.reportSubtask
 import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler
-import org.matrix.android.sdk.internal.session.room.read.FullyReadContent
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput
@@ -63,16 +61,15 @@ import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent
 import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync
 import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
 import org.matrix.android.sdk.internal.session.sync.model.RoomSync
-import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData
 import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse
+import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler
 import org.matrix.android.sdk.internal.util.computeBestChunkSize
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler,
                                                    private val roomSummaryUpdater: RoomSummaryUpdater,
-                                                   private val roomTagHandler: RoomTagHandler,
-                                                   private val roomFullyReadHandler: RoomFullyReadHandler,
+                                                   private val roomAccountDataHandler: RoomSyncAccountDataHandler,
                                                    private val cryptoService: DefaultCryptoService,
                                                    private val roomMemberEventHandler: RoomMemberEventHandler,
                                                    private val roomTypingUsersHandler: RoomTypingUsersHandler,
@@ -198,11 +195,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                 ?.takeIf { it.isNotEmpty() }
                 ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC, aggregator) }
 
-        if (roomSync.accountData?.events?.isNotEmpty() == true) {
-            handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
+        if (roomSync.accountData != null) {
+            roomAccountDataHandler.handle(realm, roomId, roomSync.accountData)
         }
 
-        val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
+        val roomEntity = RoomEntity.getOrCreate(realm, roomId)
 
         if (roomEntity.membership == Membership.INVITE) {
             roomEntity.chunks.deleteAllFromRealm()
@@ -265,7 +262,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                   insertType: EventInsertType,
                                   syncLocalTimestampMillis: Long): RoomEntity {
         Timber.v("Handle invited sync for room $roomId")
-        val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
+        val roomEntity = RoomEntity.getOrCreate(realm, roomId)
         roomEntity.membership = Membership.INVITE
         if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
             roomSync.inviteState.events.forEach { event ->
@@ -294,7 +291,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                roomSync: RoomSync,
                                insertType: EventInsertType,
                                syncLocalTimestampMillis: Long): RoomEntity {
-        val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
+        val roomEntity = RoomEntity.getOrCreate(realm, roomId)
         for (event in roomSync.state?.events.orEmpty()) {
             if (event.eventId == null || event.stateKey == null || event.type == null) {
                 continue
@@ -460,17 +457,4 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
 
         return result
     }
-
-    private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
-        accountData.events?.forEach { event ->
-            val eventType = event.getClearType()
-            if (eventType == EventType.TAG) {
-                val content = event.getClearContent().toModel<RoomTagContent>()
-                roomTagHandler.handle(realm, roomId, content)
-            } else if (eventType == EventType.FULLY_READ) {
-                val content = event.getClearContent().toModel<FullyReadContent>()
-                roomFullyReadHandler.handle(realm, roomId, content)
-            }
-        }
-    }
 }
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 157787c8cf1d4d989eb1f66edc8bd189e599989d..a4468a96c977eb491a247118f57773079f841cd5 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
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
+import org.matrix.android.sdk.internal.session.SessionListeners
 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
@@ -44,6 +45,7 @@ private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
 internal class SyncResponseHandler @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         @SessionId private val sessionId: String,
+        private val sessionListeners: SessionListeners,
         private val workManagerProvider: WorkManagerProvider,
         private val roomSyncHandler: RoomSyncHandler,
         private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
@@ -125,6 +127,7 @@ internal class SyncResponseHandler @Inject constructor(
         syncResponse.rooms?.let {
             checkPushRules(it, isInitialSync)
             userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite)
+            dispatchInvitedRoom(it)
         }
         syncResponse.groups?.let {
             scheduleGroupDataFetchingIfNeeded(it)
@@ -139,6 +142,13 @@ internal class SyncResponseHandler @Inject constructor(
         }
     }
 
+    private fun dispatchInvitedRoom(roomsSyncResponse: RoomsSyncResponse) {
+        roomsSyncResponse.invite.keys.forEach { roomId ->
+            sessionListeners.dispatch { session, listener ->
+                listener.onNewInvitedRoom(session, roomId) }
+        }
+    }
+
     /**
      * At the moment we don't get any group data through the sync, so we poll where every hour.
      * You can also force to refetch group data using [Group] API.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt
index b8d987d5009471f7a08b1ce7553dccdc01ea976a..3aebd90ed26d045c6a6b1d006959629d4c8766be 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt
@@ -23,7 +23,7 @@ import io.realm.kotlin.where
 import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.pushrules.RuleSetKey
 import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.toModel
@@ -113,7 +113,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         }
     }
 
-    private fun handlePushRules(realm: Realm, event: UserAccountDataEvent) {
+    private fun handlePushRules(realm: Realm, event: AccountDataEvent) {
         val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return
         realm.where(PushRulesEntity::class.java)
                 .findAll()
@@ -155,7 +155,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         realm.insertOrUpdate(underrides)
     }
 
-    private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) {
+    private fun handleDirectChatRooms(realm: Realm, event: AccountDataEvent) {
         val content = event.content.toModel<DirectMessagesContent>() ?: return
         content.forEach { (userId, roomIds) ->
             roomIds.forEach { roomId ->
@@ -181,7 +181,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
                 }
     }
 
-    private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) {
+    private fun handleIgnoredUsers(realm: Realm, event: AccountDataEvent) {
         val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return
         realm.where(IgnoredUserEntity::class.java)
                 .findAll()
@@ -191,7 +191,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         // TODO If not initial sync, we should execute a init sync
     }
 
-    private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) {
+    private fun handleBreadcrumbs(realm: Realm, event: AccountDataEvent) {
         val recentRoomIds = event.content.toModel<BreadcrumbsContent>()?.recentRoomIds ?: return
         val entity = BreadcrumbsEntity.getOrCreate(realm)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt
index 05b50ab2c5ee00be2f069b7c008721ea67c7d4d2..ddb71cd19f4344733f23b1a53ef800d9034fdb25 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 
 @JsonClass(generateAdapter = true)
 internal data class UserAccountDataSync(
-        @Json(name = "events") val list: List<UserAccountDataEvent> = emptyList()
+        @Json(name = "events") val list: List<AccountDataEvent> = emptyList()
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c8aab586a01048cfb19a5507552bd1f7ff2b5293
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.sync.parsing
+
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
+import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.database.mapper.ContentMapper
+import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntity
+import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomEntity
+import org.matrix.android.sdk.internal.database.query.getOrCreate
+import org.matrix.android.sdk.internal.session.room.read.FullyReadContent
+import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler
+import org.matrix.android.sdk.internal.session.sync.RoomTagHandler
+import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData
+import javax.inject.Inject
+
+internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTagHandler: RoomTagHandler,
+                                                              private val roomFullyReadHandler: RoomFullyReadHandler) {
+
+    fun handle(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
+        if (accountData.events.isNullOrEmpty()) {
+            return
+        }
+        val roomEntity = RoomEntity.getOrCreate(realm, roomId)
+        for (event in accountData.events) {
+            val eventType = event.getClearType()
+            handleGeneric(roomEntity, event.getClearContent(), eventType)
+            if (eventType == RoomAccountDataTypes.EVENT_TYPE_TAG) {
+                val content = event.getClearContent().toModel<RoomTagContent>()
+                roomTagHandler.handle(realm, roomId, content)
+            } else if (eventType == RoomAccountDataTypes.EVENT_TYPE_FULLY_READ) {
+                val content = event.getClearContent().toModel<FullyReadContent>()
+                roomFullyReadHandler.handle(realm, roomId, content)
+            }
+        }
+    }
+
+    private fun handleGeneric(roomEntity: RoomEntity, content: JsonDict?, eventType: String) {
+        val existing = roomEntity.accountData.where().equalTo(RoomAccountDataEntityFields.TYPE, eventType).findFirst()
+        if (existing != null) {
+            // Update current value
+            existing.contentStr = ContentMapper.map(content)
+        } else {
+            val roomAccountData = RoomAccountDataEntity(
+                    type = eventType,
+                    contentStr = ContentMapper.map(content)
+            )
+            roomEntity.accountData.add(roomAccountData)
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
index bac725fad28f25cb3a495b62656ee3fea94f4815..2c7dc92dddcd58f4c3dbfe6765892a19c40ba9cc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
@@ -30,7 +30,7 @@ import org.matrix.android.sdk.internal.session.identity.IdentityAuthAPI
 import org.matrix.android.sdk.internal.session.identity.IdentityRegisterTask
 import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTermsContent
-import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
+import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
 import org.matrix.android.sdk.internal.util.ensureTrailingSlash
 import javax.inject.Inject
@@ -38,7 +38,7 @@ import javax.inject.Inject
 internal class DefaultTermsService @Inject constructor(
         @UnauthenticatedWithCertificate
         private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
-        private val accountDataDataSource: AccountDataDataSource,
+        private val accountDataDataSource: UserAccountDataDataSource,
         private val termsAPI: TermsAPI,
         private val retrofitFactory: RetrofitFactory,
         private val getOpenIdTokenTask: GetOpenIdTokenTask,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt
index 2e03bc7a869e76a8284cafe0ff016b3d20233c9d..3ecc39ac9484d0f371896b3f069e75292b7f64a7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt
@@ -38,7 +38,7 @@ internal interface ThirdPartyAPI {
      *
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol
      */
-    @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}")
+    @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/user/{protocol}")
     suspend fun getThirdPartyUser(@Path("protocol") protocol: String,
                                   @QueryMap params: Map<String, String>?): List<ThirdPartyUser>
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt
similarity index 79%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataDataSource.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt
index d145c008ba85d71b5a33457e89302efe7f407b89..f64b1bdd2ecc7db38fb446b96104f52a52b6f978 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt
@@ -21,7 +21,7 @@ import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmQuery
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.RealmSessionProvider
@@ -31,27 +31,27 @@ import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityField
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import javax.inject.Inject
 
-internal class AccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
-                                                         private val realmSessionProvider: RealmSessionProvider,
-                                                         private val accountDataMapper: AccountDataMapper) {
+internal class UserAccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
+                                                             private val realmSessionProvider: RealmSessionProvider,
+                                                             private val accountDataMapper: AccountDataMapper) {
 
-    fun getAccountDataEvent(type: String): UserAccountDataEvent? {
+    fun getAccountDataEvent(type: String): AccountDataEvent? {
         return getAccountDataEvents(setOf(type)).firstOrNull()
     }
 
-    fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
+    fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
         return Transformations.map(getLiveAccountDataEvents(setOf(type))) {
             it.firstOrNull()?.toOptional()
         }
     }
 
-    fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
+    fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
         return realmSessionProvider.withRealm {
             accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map)
         }
     }
 
-    fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
+    fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
         return monarchy.findAllMappedWithChanges(
                 { accountDataEventsQuery(it, types) },
                 accountDataMapper::map
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt
similarity index 87%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt
index 27db30f3b3dc6f9671d06ef4178d97c77318e192..b15d1d0f8bbe8f2cd737b36c57ecea11e616e134 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt
@@ -23,33 +23,33 @@ import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.util.awaitCallback
 import javax.inject.Inject
 
-internal class DefaultAccountDataService @Inject constructor(
+internal class UserAccountDataService @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val updateUserAccountDataTask: UpdateUserAccountDataTask,
         private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
-        private val accountDataDataSource: AccountDataDataSource,
+        private val accountDataDataSource: UserAccountDataDataSource,
         private val taskExecutor: TaskExecutor
 ) : AccountDataService {
 
-    override fun getAccountDataEvent(type: String): UserAccountDataEvent? {
+    override fun getAccountDataEvent(type: String): AccountDataEvent? {
         return accountDataDataSource.getAccountDataEvent(type)
     }
 
-    override fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
+    override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
         return accountDataDataSource.getLiveAccountDataEvent(type)
     }
 
-    override fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
+    override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
         return accountDataDataSource.getAccountDataEvents(types)
     }
 
-    override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
+    override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
         return accountDataDataSource.getLiveAccountDataEvents(types)
     }
 
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 d741dbc966446bc957b39b0c2ad7875763d249d8..ca1a129da79e262486fe5926df4bb9c847fb0f0f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
@@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -39,7 +39,7 @@ import org.matrix.android.sdk.api.session.SessionLifecycleObserver
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
 import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
-import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
+import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
 import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
 import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
 import java.util.HashMap
@@ -47,7 +47,7 @@ import javax.inject.Inject
 
 @SessionScope
 internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager,
-                                                 private val accountDataDataSource: AccountDataDataSource,
+                                                 private val accountDataDataSource: UserAccountDataDataSource,
                                                  private val stateEventDataSource: StateEventDataSource,
                                                  private val createWidgetTask: CreateWidgetTask,
                                                  private val widgetFactory: WidgetFactory,
@@ -150,8 +150,8 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
         return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes)
     }
 
-    private fun UserAccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null,
-                                                  excludedTypes: Set<String>? = null): List<Widget> {
+    private fun AccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null,
+                                              excludedTypes: Set<String>? = null): List<Widget> {
         return extractWidgetSequence(widgetFactory)
                 .filter {
                     val widgetType = it.widgetContent.type ?: return@filter false
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt
index 6f423b38a09ed42f3f4ed74f69761bf6ad869d29..5aa32d5a31751f5f928e6be47cab0d1da907340d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt
@@ -19,10 +19,10 @@ package org.matrix.android.sdk.internal.session.widgets.helper
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.util.JsonDict
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import org.matrix.android.sdk.api.session.widgets.model.Widget
 
-internal fun UserAccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
+internal fun AccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
     return content.asSequence()
             .mapNotNull {
                 @Suppress("UNCHECKED_CAST")