diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
index 67a35cac2e..252359193d 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
@@ -24,7 +24,7 @@ import kotlinx.coroutines.rx2.rxSingle
 import org.matrix.android.sdk.api.extensions.orFalse
 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.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
@@ -177,10 +177,10 @@ class RxSession(private val session: Session) {
                 }
     }
 
-    fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
-        return session.getLiveAccountDataEvents(types).asObservable()
+    fun liveUserAccountData(types: Set<String>): Observable<List<AccountDataEvent>> {
+        return session.userAccountDataService().getLiveAccountDataEvents(types).asObservable()
                 .startWithCallable {
-                    session.getAccountDataEvents(types)
+                    session.userAccountDataService().getAccountDataEvents(types)
                 }
     }
 
@@ -201,8 +201,8 @@ class RxSession(private val session: Session) {
     }
 
     fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> {
-        return Observable.combineLatest<List<UserAccountDataEvent>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
-                liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
+        return Observable.combineLatest<List<AccountDataEvent>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
+                liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
                 liveCrossSigningInfo(session.myUserId),
                 liveCrossSigningPrivateKeys(),
                 Function3 { _, crossSigningInfo, pInfo ->
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 eb4773f3c8..25c22bca57 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/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
index b5f90e87ea..e888e5d2de 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 744e3e5379..e5cbd07aaf 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 5ebeaad3de..77f3eb0cd9 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/CallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt
index dc67aa536a..c34744e75f 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/PSTNProtocolChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/PSTNProtocolChecker.kt
deleted file mode 100644
index 6627f62e24..0000000000
--- 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 d2befca1ee..229a53fa9d 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/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index 8c434fc440..41bcbf8ff6 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 0000000000..0e80c307b4
--- /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/call/CallAnswerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
index 45a54bb379..180b32db05 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 7bfe7a97ac..dc0a1e3b2e 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 0acc409053..4752d777e1 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")
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 42489bc0ce..e4332f0ea7 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 040993bb51..68dd5ef043 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 1da229b179..ea412fbe3e 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 97a3b8c7a7..2b368a83a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
@@ -61,7 +61,7 @@ data class CallReplacesContent(
          * Required. The version of the VoIP specification this messages adheres to.
          */
         @Json(name = "version") override val version: String?
-): CallSignallingContent  {
+): CallSignalingContent  {
 
     @JsonClass(generateAdapter = true)
     data class TargetUser(
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 6ea70ac990..795f332711 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 f8d8c2a5e8..92b43dd22c 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/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index d810c8b1a8..2b3c3b28ee 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 54315a1835..4edfdad897 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/model/RoomAccountDataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomAccountDataEntity.kt
new file mode 100644
index 0000000000..40040b5738
--- /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 58297776f0..65483e05bf 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 1001f9cd66..64dc08e827 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 72ae512fa5..19472e21d9 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 27e8d9d8d1..a551f97379 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 9afdb40ed1..8be11e80f3 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 b100a336a7..1f47978198 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 563ff4ada3..5f529b3e66 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,16 @@
 
 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 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 +41,17 @@ internal class SessionListeners @Inject constructor() {
         }
     }
 
-    fun dispatch(block: (Session.Listener) -> Unit) {
+    fun dispatch(block: (Session, Session.Listener) -> Unit) {
         synchronized(listeners) {
+            val session = getSession()
             listeners.forEach {
-                block(it)
+                tryOrNull { block(session, it) }
             }
         }
     }
+
+    private fun getSession(): Session {
+        return sessionManager.getSessionComponent(sessionId)?.session()
+                ?: throw IllegalStateException("No session found with this id.")
+    }
 }
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 de74b34818..49ce92372e 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 8d7e9e819a..6bf11ab78f 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
@@ -30,7 +30,7 @@ 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.session.room.model.call.CallSignalingContent
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionScope
@@ -210,11 +210,11 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
         }
     }
 
-    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 7d046cb642..da1f84cc89 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/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
index 475781ef01..4f88d8eb95 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 3df9a00cc1..f79f8084a8 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/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index a5e066dae8..5a2eef7e8a 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 6fee630510..4f12604039 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 3f743c2922..8efbf2360a 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 8f3445bec3..d88c195056 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 0000000000..0bcf9d7f38
--- /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 0000000000..9e9e9dc322
--- /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 0000000000..db18c18908
--- /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 95572c203c..cc66a0a2d2 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/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index 126458b082..bff1af60ca 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 d488fdfc2a..7cbcfee713 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/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
index 7cebbb0192..c3586bcea7 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 157787c8cf..a4468a96c9 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 b8d987d500..3aebd90ed2 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 05b50ab2c5..ddb71cd19f 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 0000000000..c8aab586a0
--- /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 bac725fad2..2c7dc92ddd 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 2e03bc7a86..3ecc39ac94 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 d145c008ba..f64b1bdd2e 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 27db30f3b3..b15d1d0f8b 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 d741dbc966..ca1a129da7 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 6f423b38a0..5aa32d5a31 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")
diff --git a/newsfragment/3355.feature b/newsfragment/3355.feature
new file mode 100644
index 0000000000..117478a648
--- /dev/null
+++ b/newsfragment/3355.feature
@@ -0,0 +1 @@
+VoIP: support for virtual rooms
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
index 0a724b62c6..b5f45e6586 100644
--- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
+++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
@@ -104,7 +104,7 @@ class DefaultErrorFormatter @Inject constructor(
                     }
                 }
             }
-            is Failure.OtherServerError  -> {
+            is Failure.OtherServerError -> {
                 when (throwable.httpCode) {
                     HttpURLConnection.HTTP_NOT_FOUND ->
                         // homeserver not found
@@ -116,9 +116,9 @@ class DefaultErrorFormatter @Inject constructor(
                         throwable.localizedMessage
                 }
             }
-            is DialPadLookup.Failure                                                        ->
+            is DialPadLookup.Failure    ->
                 stringProvider.getString(R.string.call_dial_pad_lookup_error)
-            else                                                                            -> throwable.localizedMessage
+            else                        -> throwable.localizedMessage
         }
                 ?: stringProvider.getString(R.string.unknown_error)
     }
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Session.kt b/vector/src/main/java/im/vector/app/core/extensions/Session.kt
index a21d4ecab3..699247ab6d 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Session.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Session.kt
@@ -32,6 +32,7 @@ fun Session.configureAndStart(context: Context) {
     setFilter(FilterService.FilterPreset.ElementFilter)
     startSyncing(context)
     refreshPushers()
+    context.vectorComponent().webRtcCallManager().checkForProtocolsSupportIfNeeded()
 }
 
 fun Session.startSyncing(context: Context) {
diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt
index e9e855e760..59eee14d37 100644
--- a/vector/src/main/java/im/vector/app/core/services/CallService.kt
+++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt
@@ -32,12 +32,12 @@ import im.vector.app.features.call.VectorCallActivity
 import im.vector.app.features.call.telecom.CallConnection
 import im.vector.app.features.call.webrtc.WebRtcCall
 import im.vector.app.features.call.webrtc.WebRtcCallManager
+import im.vector.app.features.call.webrtc.getOpponentAsMatrixItem
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.notifications.NotificationUtils
 import im.vector.app.features.popup.IncomingCallAlert
 import im.vector.app.features.popup.PopupAlertManager
 import org.matrix.android.sdk.api.util.MatrixItem
-import org.matrix.android.sdk.api.util.toMatrixItem
 import timber.log.Timber
 
 /**
@@ -176,7 +176,7 @@ class CallService : VectorService() {
         }
         alertManager.postVectorAlert(incomingCallAlert)
         val notification = notificationUtils.buildIncomingCallNotification(
-                mxCall = call.mxCall,
+                call = call,
                 title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
                 fromBg = fromBg
         )
@@ -207,7 +207,7 @@ class CallService : VectorService() {
     private fun showCallScreen(call: WebRtcCall, mode: String) {
         val intent = VectorCallActivity.newIntent(
                 context = this,
-                mxCall = call.mxCall,
+                call = call,
                 mode = mode
         )
         startActivity(intent)
@@ -221,7 +221,7 @@ class CallService : VectorService() {
         val opponentMatrixItem = getOpponentMatrixItem(call)
         Timber.v("displayOutgoingCallNotification : display the dedicated notification")
         val notification = notificationUtils.buildOutgoingRingingCallNotification(
-                mxCall = call.mxCall,
+                call = call,
                 title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
         )
         if (knownCalls.isEmpty()) {
@@ -244,7 +244,7 @@ class CallService : VectorService() {
         val opponentMatrixItem = getOpponentMatrixItem(call)
         alertManager.cancelAlert(callId)
         val notification = notificationUtils.buildPendingCallNotification(
-                mxCall = call.mxCall,
+                call = call,
                 title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
         )
         if (knownCalls.isEmpty()) {
@@ -275,7 +275,9 @@ class CallService : VectorService() {
     }
 
     private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? {
-        return vectorComponent().currentSession().getUser(call.mxCall.opponentUserId)?.toMatrixItem()
+        return vectorComponent().activeSessionHolder().getSafeActiveSession()?.let {
+            call.getOpponentAsMatrixItem(it)
+        }
     }
 
     companion object {
diff --git a/vector/src/main/java/im/vector/app/features/call/CallSessionDependencies.kt b/vector/src/main/java/im/vector/app/features/call/CallSessionDependencies.kt
new file mode 100644
index 0000000000..d1b3f77604
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/CallSessionDependencies.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.call
+
+import im.vector.app.features.call.lookup.CallProtocolsChecker
+import im.vector.app.features.call.lookup.CallUserMapper
+import im.vector.app.features.session.SessionScopedProperty
+import org.matrix.android.sdk.api.session.Session
+
+interface VectorCallService {
+    val protocolChecker: CallProtocolsChecker
+    val userMapper: CallUserMapper
+}
+
+val Session.vectorCallService: VectorCallService by SessionScopedProperty {
+    object : VectorCallService {
+        override val protocolChecker = CallProtocolsChecker(it)
+        override val userMapper = CallUserMapper(it, protocolChecker)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
index a9e2982714..a4974283dc 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
@@ -46,6 +46,7 @@ import im.vector.app.databinding.ActivityCallBinding
 import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
 import im.vector.app.features.call.dialpad.DialPadFragment
 import im.vector.app.features.call.utils.EglUtils
+import im.vector.app.features.call.webrtc.WebRtcCall
 import im.vector.app.features.call.webrtc.WebRtcCallManager
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.home.room.detail.RoomDetailActivity
@@ -54,7 +55,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
 import kotlinx.parcelize.Parcelize
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.call.CallState
-import org.matrix.android.sdk.api.session.call.MxCallDetail
 import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
 import org.matrix.android.sdk.api.session.call.TurnServerResponse
 import org.webrtc.EglBase
@@ -64,7 +64,7 @@ import javax.inject.Inject
 
 @Parcelize
 data class CallArgs(
-        val roomId: String,
+        val signalingRoomId: String,
         val callId: String,
         val participantUserId: String,
         val isIncomingCall: Boolean,
@@ -276,7 +276,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
         views.otherKnownCallAvatarView.setOnClickListener {
             withState(callViewModel) {
                 val otherCall = callManager.getCallById(it.otherKnownCallInfo?.callId ?: "") ?: return@withState
-                startActivity(newIntent(this, otherCall.mxCall, null))
+                startActivity(newIntent(this, otherCall, null))
                 finish()
             }
         }
@@ -364,18 +364,18 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
         const val INCOMING_RINGING = "INCOMING_RINGING"
         const val INCOMING_ACCEPT = "INCOMING_ACCEPT"
 
-        fun newIntent(context: Context, mxCall: MxCallDetail, mode: String?): Intent {
+        fun newIntent(context: Context, call: WebRtcCall, mode: String?): Intent {
             return Intent(context, VectorCallActivity::class.java).apply {
                 // what could be the best flags?
                 flags = Intent.FLAG_ACTIVITY_NEW_TASK
-                putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.opponentUserId, !mxCall.isOutgoing, mxCall.isVideoCall))
+                putExtra(MvRx.KEY_ARG, CallArgs(call.nativeRoomId, call.callId, call.mxCall.opponentUserId, !call.mxCall.isOutgoing, call.mxCall.isVideoCall))
                 putExtra(EXTRA_MODE, mode)
             }
         }
 
         fun newIntent(context: Context,
                       callId: String,
-                      roomId: String,
+                      signalingRoomId: String,
                       otherUserId: String,
                       isIncomingCall: Boolean,
                       isVideoCall: Boolean,
@@ -383,7 +383,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
             return Intent(context, VectorCallActivity::class.java).apply {
                 // what could be the best flags?
                 flags = FLAG_ACTIVITY_CLEAR_TOP
-                putExtra(MvRx.KEY_ARG, CallArgs(roomId, callId, otherUserId, isIncomingCall, isVideoCall))
+                putExtra(MvRx.KEY_ARG, CallArgs(signalingRoomId, callId, otherUserId, isIncomingCall, isVideoCall))
                 putExtra(EXTRA_MODE, mode)
             }
         }
@@ -410,7 +410,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
     }
 
     override fun returnToChat() {
-        val args = RoomDetailArgs(callArgs.roomId)
+        val args = RoomDetailArgs(callArgs.signalingRoomId)
         val intent = RoomDetailActivity.newIntent(this, args).apply {
             flags = FLAG_ACTIVITY_CLEAR_TOP
         }
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
index 8a2d56a5a2..17163019ac 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
@@ -30,6 +30,7 @@ import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.features.call.audio.CallAudioManager
 import im.vector.app.features.call.webrtc.WebRtcCall
 import im.vector.app.features.call.webrtc.WebRtcCallManager
+import im.vector.app.features.call.webrtc.getOpponentAsMatrixItem
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
@@ -38,8 +39,6 @@ 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.call.MxPeerConnectionState
 import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
-import org.matrix.android.sdk.api.util.MatrixItem
-import org.matrix.android.sdk.api.util.toMatrixItem
 
 class VectorCallViewModel @AssistedInject constructor(
         @Assisted initialState: VectorCallViewState,
@@ -152,7 +151,7 @@ class VectorCallViewModel @AssistedInject constructor(
             if (otherCall == null) {
                 copy(otherKnownCallInfo = null)
             } else {
-                val otherUserItem: MatrixItem? = session.getUser(otherCall.mxCall.opponentUserId)?.toMatrixItem()
+                val otherUserItem = otherCall.getOpponentAsMatrixItem(session)
                 copy(otherKnownCallInfo = VectorCallViewState.CallInfo(otherCall.callId, otherUserItem))
             }
         }
@@ -167,7 +166,7 @@ class VectorCallViewModel @AssistedInject constructor(
         } else {
             call = webRtcCall
             callManager.addCurrentCallListener(currentCallListener)
-            val item: MatrixItem? = session.getUser(webRtcCall.mxCall.opponentUserId)?.toMatrixItem()
+            val item  = webRtcCall.getOpponentAsMatrixItem(session)
             webRtcCall.addListener(callListener)
             val currentSoundDevice = callManager.audioManager.selectedDevice
             if (currentSoundDevice == CallAudioManager.Device.PHONE) {
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
index cdd002114a..17f536e6cc 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
@@ -51,7 +51,7 @@ data class VectorCallViewState(
 
     constructor(callArgs: CallArgs): this(
             callId = callArgs.callId,
-            roomId = callArgs.roomId,
+            roomId = callArgs.signalingRoomId,
             isVideoCall = callArgs.isVideoCall
     )
 }
diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
index 6fccea6c8c..4ed1e4a0db 100644
--- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
+++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
@@ -16,30 +16,24 @@
 
 package im.vector.app.features.call.dialpad
 
+import im.vector.app.features.call.lookup.pstnLookup
 import im.vector.app.features.call.webrtc.WebRtcCallManager
 import im.vector.app.features.createdirect.DirectRoomHelper
-import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.Session
+import java.lang.IllegalStateException
 import javax.inject.Inject
 
 class DialPadLookup @Inject constructor(
         private val session: Session,
-        private val directRoomHelper: DirectRoomHelper,
-        private val callManager: WebRtcCallManager
+        private val webRtcCallManager: WebRtcCallManager,
+        private val directRoomHelper: DirectRoomHelper
 ) {
     class Failure : Throwable()
 
     data class Result(val userId: String, val roomId: String)
 
     suspend fun lookupPhoneNumber(phoneNumber: String): Result {
-        val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure()
-        val thirdPartyUser = tryOrNull {
-            session.thirdPartyService().getThirdPartyUser(
-                    protocol = supportedProtocolKey,
-                    fields = mapOf("m.id.phone" to phoneNumber)
-            ).firstOrNull()
-        } ?: throw Failure()
-
+        val thirdPartyUser = session.pstnLookup(phoneNumber, webRtcCallManager.supportedPSTNProtocol).firstOrNull() ?: throw IllegalStateException()
         val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId)
         return Result(userId = thirdPartyUser.userId, roomId = roomId)
     }
diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/CallProtocolsChecker.kt b/vector/src/main/java/im/vector/app/features/call/lookup/CallProtocolsChecker.kt
new file mode 100644
index 0000000000..9f6a24fd25
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/lookup/CallProtocolsChecker.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.call.lookup
+
+import im.vector.app.features.session.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
+import timber.log.Timber
+import java.util.concurrent.atomic.AtomicBoolean
+
+const val PROTOCOL_PSTN_PREFIXED = "im.vector.protocol.pstn"
+const val PROTOCOL_PSTN = "m.protocol.pstn"
+const val PROTOCOL_SIP_NATIVE = "im.vector.protocol.sip_native"
+const val PROTOCOL_SIP_VIRTUAL = "im.vector.protocol.sip_virtual"
+
+class CallProtocolsChecker(private val session: Session) {
+
+    interface Listener {
+        fun onPSTNSupportUpdated() = Unit
+        fun onVirtualRoomSupportUpdated() = Unit
+    }
+
+    private val alreadyChecked = AtomicBoolean(false)
+    private val checking = AtomicBoolean(false)
+
+    private val listeners = mutableListOf<Listener>()
+
+    fun addListener(listener: Listener) {
+        listeners.add(listener)
+    }
+
+    fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    var supportedPSTNProtocol: String? = null
+        private set
+
+    var supportVirtualRooms: Boolean = false
+        private set
+
+    fun checkProtocols() {
+        session.coroutineScope.launch {
+            checkThirdPartyProtocols()
+        }
+    }
+
+    suspend fun awaitCheckProtocols() {
+        checkThirdPartyProtocols()
+    }
+
+    private suspend fun checkThirdPartyProtocols() {
+        if (alreadyChecked.get()) return
+        if (!checking.compareAndSet(false, true)) return
+        try {
+            val protocols = getThirdPartyProtocols(3)
+            alreadyChecked.set(true)
+            checking.set(false)
+            supportedPSTNProtocol = protocols.extractPSTN()
+            if (supportedPSTNProtocol != null) {
+                listeners.forEach {
+                    tryOrNull { it.onPSTNSupportUpdated() }
+                }
+            }
+            supportVirtualRooms = protocols.supportsVirtualRooms()
+            if (supportVirtualRooms) {
+                listeners.forEach {
+                    tryOrNull { it.onVirtualRoomSupportUpdated() }
+                }
+            }
+        } catch (failure: Throwable) {
+            Timber.v("Fail to get third party protocols, will check again next time.")
+        }
+    }
+
+    private fun Map<String, ThirdPartyProtocol>.extractPSTN(): String? {
+        return when {
+            containsKey(PROTOCOL_PSTN_PREFIXED) -> PROTOCOL_PSTN_PREFIXED
+            containsKey(PROTOCOL_PSTN)          -> PROTOCOL_PSTN
+            else                                -> null
+        }
+    }
+
+    private fun Map<String, ThirdPartyProtocol>.supportsVirtualRooms(): Boolean {
+        return containsKey(PROTOCOL_SIP_VIRTUAL) && containsKey(PROTOCOL_SIP_NATIVE)
+    }
+
+    private suspend fun getThirdPartyProtocols(maxTries: Int): Map<String, ThirdPartyProtocol> {
+        return try {
+            session.thirdPartyService().getThirdPartyProtocols()
+        } catch (failure: Throwable) {
+            if (maxTries == 1) {
+                throw failure
+            } else {
+                // Wait for 10s before trying again
+                delay(10_000L)
+                return getThirdPartyProtocols(maxTries - 1)
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt
new file mode 100644
index 0000000000..04177bd2b0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.call.lookup
+
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+
+class CallUserMapper(private val session: Session, private val protocolsChecker: CallProtocolsChecker) {
+
+    fun nativeRoomForVirtualRoom(roomId: String): String? {
+        val virtualRoom = session.getRoom(roomId) ?: return null
+        val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM)
+        return virtualRoomEvent?.content?.toModel<RoomVirtualContent>()?.nativeRoomId
+    }
+
+    suspend fun getOrCreateVirtualRoomForRoom(roomId: String, opponentUserId: String): String? {
+        protocolsChecker.awaitCheckProtocols()
+        if (!protocolsChecker.supportVirtualRooms) return null
+        val virtualUser = userToVirtualUser(opponentUserId) ?: return null
+        val virtualRoomId = tryOrNull {
+            ensureVirtualRoomExists(virtualUser, roomId)
+        } ?: return null
+        session.getRoom(virtualRoomId)?.markVirtual(roomId)
+        return virtualRoomId
+    }
+
+    suspend fun onNewInvitedRoom(invitedRoomId: String) {
+        protocolsChecker.awaitCheckProtocols()
+        if (!protocolsChecker.supportVirtualRooms) return
+        val invitedRoom = session.getRoom(invitedRoomId) ?: return
+        val inviterId = invitedRoom.roomSummary()?.inviterId ?: return
+        val nativeLookup = session.sipNativeLookup(inviterId).firstOrNull() ?: return
+        if (nativeLookup.fields.containsKey("is_virtual")) {
+            val nativeUser = nativeLookup.userId
+            val nativeRoomId = session.getExistingDirectRoomWithUser(nativeUser)
+            if (nativeRoomId != null) {
+                // It's a virtual room with a matching native room, so set the room account data. This
+                // will make sure we know where how to map calls and also allow us know not to display
+                // it in the future.
+                invitedRoom.markVirtual(nativeRoomId)
+                // also auto-join the virtual room if we have a matching native room
+                // (possibly we should only join if we've also joined the native room, then we'd also have
+                // to make sure we joined virtual rooms on joining a native one)
+                session.joinRoom(invitedRoomId)
+            }
+        }
+    }
+
+    private suspend fun userToVirtualUser(userId: String): String? {
+        val results = session.sipVirtualLookup(userId)
+        return results.firstOrNull()?.userId
+    }
+
+    private suspend fun Room.markVirtual(nativeRoomId: String) {
+        val virtualRoomContent = RoomVirtualContent(nativeRoomId = nativeRoomId)
+        updateAccountData(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM, virtualRoomContent.toContent())
+    }
+
+    private suspend fun ensureVirtualRoomExists(userId: String, nativeRoomId: String): String {
+        val existingDMRoom = tryOrNull { session.getExistingDirectRoomWithUser(userId) }
+        val roomId: String
+        if (existingDMRoom != null) {
+            roomId = existingDMRoom
+        } else {
+            val roomParams = CreateRoomParams().apply {
+                invitedUserIds.add(userId)
+                setDirectMessage()
+                creationContent[RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM] = nativeRoomId
+            }
+            roomId = session.createRoom(roomParams)
+        }
+        return roomId
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/RoomVirtualContent.kt b/vector/src/main/java/im/vector/app/features/call/lookup/RoomVirtualContent.kt
new file mode 100644
index 0000000000..4f76f940e3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/lookup/RoomVirtualContent.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.call.lookup
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class RoomVirtualContent(
+        @Json(name = "native_room") val nativeRoomId: String
+)
diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/ThirdPartyLookup.kt b/vector/src/main/java/im/vector/app/features/call/lookup/ThirdPartyLookup.kt
new file mode 100644
index 0000000000..1e9834059f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/lookup/ThirdPartyLookup.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.call.lookup
+
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser
+
+suspend fun Session.pstnLookup(phoneNumber: String, protocol: String?): List<ThirdPartyUser> {
+    if (protocol == null) return emptyList()
+    return tryOrNull {
+        thirdPartyService().getThirdPartyUser(
+                protocol = protocol,
+                fields = mapOf("m.id.phone" to phoneNumber)
+        )
+    }.orEmpty()
+}
+
+suspend fun Session.sipVirtualLookup(nativeMxid: String): List<ThirdPartyUser> {
+    return tryOrNull {
+        thirdPartyService().getThirdPartyUser(
+                protocol = PROTOCOL_SIP_VIRTUAL,
+                fields = mapOf("native_mxid" to nativeMxid)
+        )
+    }.orEmpty()
+}
+
+suspend fun Session.sipNativeLookup(virtualMxid: String): List<ThirdPartyUser> {
+    return tryOrNull {
+        thirdPartyService().getThirdPartyUser(
+                protocol = PROTOCOL_SIP_NATIVE,
+                fields = mapOf("virtual_mxid" to virtualMxid)
+        )
+    }.orEmpty()
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
index a3a1a29c4b..82d9d2e983 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
@@ -86,6 +86,8 @@ private const val VIDEO_TRACK_ID = "ARDAMSv0"
 private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
 
 class WebRtcCall(val mxCall: MxCall,
+                 // This is where the call is placed from an ui perspective. In case of virtual room, it can differs from the signalingRoomId.
+                 val nativeRoomId: String,
                  private val rootEglBase: EglBase?,
                  private val context: Context,
                  private val dispatcher: CoroutineContext,
@@ -116,7 +118,8 @@ class WebRtcCall(val mxCall: MxCall,
     }
 
     val callId = mxCall.callId
-    val roomId = mxCall.roomId
+    // room where call signaling is placed. In case of virtual room it can differs from the nativeRoomId.
+    val signalingRoomId = mxCall.roomId
 
     private var peerConnection: PeerConnection? = null
     private var localAudioSource: AudioSource? = null
@@ -385,6 +388,7 @@ class WebRtcCall(val mxCall: MxCall,
             peerConnection?.awaitSetRemoteDescription(offerSdp)
         } catch (failure: Throwable) {
             Timber.v("Failure putting remote description")
+            endCall(true, CallHangupContent.Reason.UNKWOWN_ERROR)
             return@withContext
         }
         // 2) Access camera + microphone, create local stream
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt
new file mode 100644
index 0000000000..c99d097707
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.call.webrtc
+
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.util.MatrixItem
+import org.matrix.android.sdk.api.util.toMatrixItem
+
+fun WebRtcCall.getOpponentAsMatrixItem(session: Session): MatrixItem? {
+    return session.getRoomSummary(nativeRoomId)?.otherMemberIds?.firstOrNull()?.let {
+        session.getUser(it)?.toMatrixItem()
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
index 2f8f84051e..253b1ac33d 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
@@ -24,15 +24,18 @@ import im.vector.app.ActiveSessionDataSource
 import im.vector.app.core.services.CallService
 import im.vector.app.features.call.VectorCallActivity
 import im.vector.app.features.call.audio.CallAudioManager
+import im.vector.app.features.call.lookup.CallProtocolsChecker
+import im.vector.app.features.call.lookup.CallUserMapper
 import im.vector.app.features.call.utils.EglUtils
+import im.vector.app.features.call.vectorCallService
 import im.vector.app.push.fcm.FcmHelper
 import kotlinx.coroutines.asCoroutineDispatcher
+import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.call.CallListener
 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.call.PSTNProtocolChecker
 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.CallHangupContent
@@ -64,8 +67,11 @@ class WebRtcCallManager @Inject constructor(
     private val currentSession: Session?
         get() = activeSessionDataSource.currentValue?.orNull()
 
-    private val pstnProtocolChecker: PSTNProtocolChecker?
-        get() = currentSession?.callSignalingService()?.getPSTNProtocolChecker()
+    private val protocolsChecker: CallProtocolsChecker?
+        get() = currentSession?.vectorCallService?.protocolChecker
+
+    private val callUserMapper: CallUserMapper?
+        get() = currentSession?.vectorCallService?.userMapper
 
     interface CurrentCallListener {
         fun onCurrentCallChange(call: WebRtcCall?) {}
@@ -73,17 +79,20 @@ class WebRtcCallManager @Inject constructor(
     }
 
     val supportedPSTNProtocol: String?
-        get() = pstnProtocolChecker?.supportedPSTNProtocol
+        get() = protocolsChecker?.supportedPSTNProtocol
 
     val supportsPSTNProtocol: Boolean
         get() = supportedPSTNProtocol != null
 
-    fun addPstnSupportListener(listener: PSTNProtocolChecker.Listener) {
-        pstnProtocolChecker?.addListener(listener)
+    val supportsVirtualRooms: Boolean
+        get() = protocolsChecker?.supportVirtualRooms.orFalse()
+
+    fun addProtocolsCheckerListener(listener: CallProtocolsChecker.Listener) {
+        protocolsChecker?.addListener(listener)
     }
 
-    fun removePstnSupportListener(listener: PSTNProtocolChecker.Listener) {
-        pstnProtocolChecker?.removeListener(listener)
+    fun removeProtocolsCheckerListener(listener: CallProtocolsChecker.Listener) {
+        protocolsChecker?.removeListener(listener)
     }
 
     private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>()
@@ -154,8 +163,8 @@ class WebRtcCallManager @Inject constructor(
         return callsByCallId.values.toList()
     }
 
-    fun checkForPSTNSupportIfNeeded() {
-        pstnProtocolChecker?.checkForPSTNSupportIfNeeded()
+    fun checkForProtocolsSupportIfNeeded() {
+        protocolsChecker?.checkProtocols()
     }
 
     /**
@@ -218,7 +227,8 @@ class WebRtcCallManager @Inject constructor(
             Timber.v("On call ended for unknown call $callId")
         }
         CallService.onCallTerminated(context, callId)
-        callsByRoomId[webRtcCall.roomId]?.remove(webRtcCall)
+        callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall)
+        callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall)
         if (getCurrentCall()?.callId == callId) {
             val otherCall = getCalls().lastOrNull()
             currentCall.setAndNotify(otherCall)
@@ -245,9 +255,10 @@ class WebRtcCallManager @Inject constructor(
         }
     }
 
-    fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) {
+    suspend fun startOutgoingCall(nativeRoomId: String, otherUserId: String, isVideoCall: Boolean) {
+        val signalingRoomId =  callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId
         Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
-        if (getCallsByRoomId(signalingRoomId).isNotEmpty()) {
+        if (getCallsByRoomId(nativeRoomId).isNotEmpty()) {
             Timber.w("## VOIP you already have a call in this room")
             return
         }
@@ -261,7 +272,7 @@ class WebRtcCallManager @Inject constructor(
         }
         getCurrentCall()?.updateRemoteOnHold(onHold = true)
         val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
-        val webRtcCall = createWebRtcCall(mxCall)
+        val webRtcCall = createWebRtcCall(mxCall, nativeRoomId)
         currentCall.setAndNotify(webRtcCall)
 
         CallService.onOutgoingCallRinging(
@@ -269,7 +280,7 @@ class WebRtcCallManager @Inject constructor(
                 callId = mxCall.callId)
 
         // start the activity now
-        context.startActivity(VectorCallActivity.newIntent(context, mxCall, VectorCallActivity.OUTGOING_CREATED))
+        context.startActivity(VectorCallActivity.newIntent(context, webRtcCall, VectorCallActivity.OUTGOING_CREATED))
     }
 
     override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) {
@@ -281,9 +292,10 @@ class WebRtcCallManager @Inject constructor(
         call.onCallIceCandidateReceived(iceCandidatesContent)
     }
 
-    private fun createWebRtcCall(mxCall: MxCall): WebRtcCall {
+    private fun createWebRtcCall(mxCall: MxCall, nativeRoomId: String): WebRtcCall {
         val webRtcCall = WebRtcCall(
                 mxCall = mxCall,
+                nativeRoomId = nativeRoomId,
                 rootEglBase = rootEglBase,
                 context = context,
                 dispatcher = dispatcher,
@@ -297,6 +309,8 @@ class WebRtcCallManager @Inject constructor(
         )
         advertisedCalls.add(mxCall.callId)
         callsByCallId[mxCall.callId] = webRtcCall
+        callsByRoomId.getOrPut(nativeRoomId) { ArrayList(1) }
+                .add(webRtcCall)
         callsByRoomId.getOrPut(mxCall.roomId) { ArrayList(1) }
                 .add(webRtcCall)
         if (getCurrentCall() == null) {
@@ -306,12 +320,13 @@ class WebRtcCallManager @Inject constructor(
     }
 
     fun endCallForRoom(roomId: String, originatedByMe: Boolean = true) {
-        callsByRoomId[roomId]?.forEach { it.endCall(originatedByMe) }
+        callsByRoomId[roomId]?.firstOrNull()?.endCall(originatedByMe)
     }
 
     override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) {
         Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}")
-        if (getCallsByRoomId(mxCall.roomId).isNotEmpty()) {
+        val nativeRoomId = callUserMapper?.nativeRoomForVirtualRoom(mxCall.roomId) ?: mxCall.roomId
+        if (getCallsByRoomId(nativeRoomId).isNotEmpty()) {
             Timber.w("## VOIP you already have a call in this room")
             return
         }
@@ -320,7 +335,7 @@ class WebRtcCallManager @Inject constructor(
             // Just ignore, maybe we could answer from other session?
             return
         }
-        createWebRtcCall(mxCall).apply {
+        createWebRtcCall(mxCall, nativeRoomId).apply {
             offerSdp = callInviteContent.offer
         }
         // Start background service with notification
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
index d2cf871701..f8290c0321 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
@@ -252,7 +252,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
     }
 
     private fun isBackupKeyInQuadS(): Boolean {
-        val sssBackupSecret = session.getAccountDataEvent(KEYBACKUP_SECRET_SSSS_NAME)
+        val sssBackupSecret = session.userAccountDataService().getAccountDataEvent(KEYBACKUP_SECRET_SSSS_NAME)
                 ?: return false
 
         // Some sanity ?
diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
index 11a30b304e..f55b482124 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
@@ -218,7 +218,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
 
                 withContext(Dispatchers.IO) {
                     args.requestedSecrets.forEach {
-                        if (session.getAccountDataEvent(it) != null) {
+                        if (session.userAccountDataService().getAccountDataEvent(it) != null) {
                             val res = session.sharedSecretStorageService.getSecret(
                                     name = it,
                                     keyId = keyInfo.id,
@@ -287,7 +287,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
 
                 withContext(Dispatchers.IO) {
                     args.requestedSecrets.forEach {
-                        if (session.getAccountDataEvent(it) != null) {
+                        if (session.userAccountDataService().getAccountDataEvent(it) != null) {
                             val res = session.sharedSecretStorageService.getSecret(
                                     name = it,
                                     keyId = keyInfo.id,
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
index 69395b2386..291a4218b9 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
@@ -409,7 +409,7 @@ class HomeDetailFragment @Inject constructor(
             VectorCallActivity.newIntent(
                     context = requireContext(),
                     callId = call.callId,
-                    roomId = call.mxCall.roomId,
+                    signalingRoomId = call.signalingRoomId,
                     otherUserId = call.mxCall.opponentUserId,
                     isIncomingCall = !call.mxCall.isOutgoing,
                     isVideoCall = call.mxCall.isVideoCall,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
index 1f3f527be8..f13f9dba60 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
@@ -417,7 +417,7 @@ class RoomDetailFragment @Inject constructor(
     private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) {
         val intent = VectorCallActivity.newIntent(
                 context = vectorBaseActivity,
-                mxCall = event.call.mxCall,
+                call = event.call,
                 mode = VectorCallActivity.INCOMING_ACCEPT
         )
         startActivity(intent)
@@ -2042,7 +2042,7 @@ class RoomDetailFragment @Inject constructor(
             VectorCallActivity.newIntent(
                     context = requireContext(),
                     callId = call.callId,
-                    roomId = call.roomId,
+                    signalingRoomId = call.signalingRoomId,
                     otherUserId = call.mxCall.opponentUserId,
                     isIncomingCall = !call.mxCall.isOutgoing,
                     isVideoCall = call.mxCall.isVideoCall,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 44392309e2..205ccf7fca 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -40,6 +40,7 @@ import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.core.resources.StringProvider
 import im.vector.app.features.call.conference.JitsiService
 import im.vector.app.features.call.dialpad.DialPadLookup
+import im.vector.app.features.call.lookup.CallProtocolsChecker
 import im.vector.app.features.call.webrtc.WebRtcCallManager
 import im.vector.app.features.command.CommandParser
 import im.vector.app.features.command.ParsedCommand
@@ -68,7 +69,6 @@ import org.matrix.android.sdk.api.MatrixPatterns
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.LocalEcho
@@ -121,7 +121,7 @@ class RoomDetailViewModel @AssistedInject constructor(
         private val jitsiService: JitsiService,
         timelineSettingsFactory: TimelineSettingsFactory
 ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
-        Timeline.Listener, ChatEffectManager.Delegate, PSTNProtocolChecker.Listener {
+        Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
 
     private val room = session.getRoom(initialState.roomId)!!
     private val eventId = initialState.eventId
@@ -185,8 +185,8 @@ class RoomDetailViewModel @AssistedInject constructor(
         viewModelScope.launch(Dispatchers.IO) {
             tryOrNull { session.onRoomDisplayed(initialState.roomId) }
         }
-        callManager.addPstnSupportListener(this)
-        callManager.checkForPSTNSupportIfNeeded()
+        callManager.addProtocolsCheckerListener(this)
+        callManager.checkForProtocolsSupportIfNeeded()
         chatEffectManager.delegate = this
 
         // Ensure to share the outbound session keys with all members
@@ -330,7 +330,7 @@ class RoomDetailViewModel @AssistedInject constructor(
     private fun handleStartCallWithPhoneNumber(action: RoomDetailAction.StartCallWithPhoneNumber) {
         viewModelScope.launch {
             try {
-                val result = DialPadLookup(session, directRoomHelper, callManager).lookupPhoneNumber(action.phoneNumber)
+                val result = DialPadLookup(session, callManager, directRoomHelper).lookupPhoneNumber(action.phoneNumber)
                 callManager.startOutgoingCall(result.roomId, result.userId, action.videoCall)
             } catch (failure: Throwable) {
                 _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
@@ -391,8 +391,10 @@ class RoomDetailViewModel @AssistedInject constructor(
     }
 
     private fun handleStartCall(action: RoomDetailAction.StartCall) {
-        room.roomSummary()?.otherMemberIds?.firstOrNull()?.let {
-            callManager.startOutgoingCall(room.roomId, it, action.isVideo)
+        viewModelScope.launch {
+            room.roomSummary()?.otherMemberIds?.firstOrNull()?.let {
+                callManager.startOutgoingCall(room.roomId, it, action.isVideo)
+            }
         }
     }
 
@@ -1508,7 +1510,7 @@ class RoomDetailViewModel @AssistedInject constructor(
         }
         chatEffectManager.delegate = null
         chatEffectManager.dispose()
-        callManager.removePstnSupportListener(this)
+        callManager.removeProtocolsCheckerListener(this)
         super.onCleared()
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt
index 30f1ecdc6d..cf508a2dab 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt
@@ -103,7 +103,7 @@ class StartCallActionsHandler(
                 val currentCall = callManager.getCurrentCall()
                 if (currentCall != null) {
                     // resume existing if same room, if not prompt to kill and then restart new call?
-                    if (currentCall.roomId == roomId) {
+                    if (currentCall.signalingRoomId == roomId) {
                         onTapToReturnToCall()
                     }
                     //                        else {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
index 3df9898078..9dcc3e8182 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
@@ -32,7 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
 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.CallRejectContent
-import org.matrix.android.sdk.api.session.room.model.call.CallSignallingContent
+import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.util.toMatrixItem
 import javax.inject.Inject
@@ -51,7 +51,7 @@ class CallItemFactory @Inject constructor(
         if (event.root.eventId == null) return null
         val roomId = event.roomId
         val informationData = messageInformationDataFactory.create(params)
-        val callSignalingContent = event.getCallSignallingContent() ?: return null
+        val callSignalingContent = event.getCallSignalingContent() ?: return null
         val callId = callSignalingContent.callId ?: return null
         val call = callManager.getCallById(callId)
         val callKind = when {
@@ -112,7 +112,7 @@ class CallItemFactory @Inject constructor(
         }
     }
 
-    private fun TimelineEvent.getCallSignallingContent(): CallSignallingContent? {
+    private fun TimelineEvent.getCallSignalingContent(): CallSignalingContent? {
         return when (root.getClearType()) {
             EventType.CALL_INVITE -> root.getClearContent().toModel<CallInviteContent>()
             EventType.CALL_HANGUP -> root.getClearContent().toModel<CallHangupContent>()
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
index 35cc95f3dc..aad868008b 100755
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
@@ -51,12 +51,12 @@ import im.vector.app.core.resources.StringProvider
 import im.vector.app.core.utils.startNotificationChannelSettingsIntent
 import im.vector.app.features.call.VectorCallActivity
 import im.vector.app.features.call.service.CallHeadsUpActionReceiver
+import im.vector.app.features.call.webrtc.WebRtcCall
 import im.vector.app.features.home.HomeActivity
 import im.vector.app.features.home.room.detail.RoomDetailActivity
 import im.vector.app.features.home.room.detail.RoomDetailArgs
 import im.vector.app.features.settings.VectorPreferences
 import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver
-import org.matrix.android.sdk.api.session.call.MxCall
 import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -287,7 +287,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
      * @return the call notification.
      */
     @SuppressLint("NewApi")
-    fun buildIncomingCallNotification(mxCall: MxCall,
+    fun buildIncomingCallNotification(call: WebRtcCall,
                                       title: String,
                                       fromBg: Boolean): Notification {
         val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
@@ -295,7 +295,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
         val builder = NotificationCompat.Builder(context, notificationChannel)
                 .setContentTitle(ensureTitleNotEmpty(title))
                 .apply {
-                    if (mxCall.isVideoCall) {
+                    if (call.mxCall.isVideoCall) {
                         setContentText(stringProvider.getString(R.string.incoming_video_call))
                     } else {
                         setContentText(stringProvider.getString(R.string.incoming_voice_call))
@@ -308,11 +308,11 @@ class NotificationUtils @Inject constructor(private val context: Context,
 
         val contentIntent = VectorCallActivity.newIntent(
                 context = context,
-                mxCall = mxCall,
+                call = call,
                 mode = VectorCallActivity.INCOMING_RINGING
         ).apply {
             flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
-            data = Uri.parse("foobar://${mxCall.callId}")
+            data = Uri.parse("foobar://${call.callId}")
         }
         val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
 
@@ -320,12 +320,12 @@ class NotificationUtils @Inject constructor(private val context: Context,
                 .addNextIntentWithParentStack(HomeActivity.newIntent(context))
                 .addNextIntent(VectorCallActivity.newIntent(
                         context = context,
-                        mxCall = mxCall,
+                        call = call,
                         mode = VectorCallActivity.INCOMING_ACCEPT)
                 )
                 .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
 
-        val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
+        val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId)
 
         builder.addAction(
                 NotificationCompat.Action(
@@ -351,7 +351,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
         return builder.build()
     }
 
-    fun buildOutgoingRingingCallNotification(mxCall: MxCall,
+    fun buildOutgoingRingingCallNotification(call: WebRtcCall,
                                              title: String): Notification {
         val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
         val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
@@ -366,14 +366,14 @@ class NotificationUtils @Inject constructor(private val context: Context,
 
         val contentIntent = VectorCallActivity.newIntent(
                 context = context,
-                mxCall = mxCall,
+                call = call,
                 mode = null).apply {
             flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
-            data = Uri.parse("foobar://$mxCall.callId")
+            data = Uri.parse("foobar://$call.callId")
         }
         val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
 
-        val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
+        val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId)
 
         builder.addAction(
                 NotificationCompat.Action(
@@ -397,12 +397,12 @@ class NotificationUtils @Inject constructor(private val context: Context,
      * @return the call notification.
      */
     @SuppressLint("NewApi")
-    fun buildPendingCallNotification(mxCall: MxCall,
+    fun buildPendingCallNotification(call: WebRtcCall,
                                      title: String): Notification {
         val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
                 .setContentTitle(ensureTitleNotEmpty(title))
                 .apply {
-                    if (mxCall.isVideoCall) {
+                    if (call.mxCall.isVideoCall) {
                         setContentText(stringProvider.getString(R.string.video_call_in_progress))
                     } else {
                         setContentText(stringProvider.getString(R.string.call_in_progress))
@@ -411,7 +411,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
                 .setSmallIcon(R.drawable.incoming_call_notification_transparent)
                 .setCategory(NotificationCompat.CATEGORY_CALL)
 
-        val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
+        val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId)
 
         builder.addAction(
                 NotificationCompat.Action(
@@ -422,7 +422,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
 
         val contentPendingIntent = TaskStackBuilder.create(context)
                 .addNextIntentWithParentStack(HomeActivity.newIntent(context))
-                .addNextIntent(VectorCallActivity.newIntent(context, mxCall, null))
+                .addNextIntent(VectorCallActivity.newIntent(context, call, null))
                 .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
 
         builder.setContentIntent(contentPendingIntent)
diff --git a/vector/src/main/java/im/vector/app/features/session/SessionListener.kt b/vector/src/main/java/im/vector/app/features/session/SessionListener.kt
index 7b7be550cb..d07e26d82d 100644
--- a/vector/src/main/java/im/vector/app/features/session/SessionListener.kt
+++ b/vector/src/main/java/im/vector/app/features/session/SessionListener.kt
@@ -21,6 +21,8 @@ import androidx.lifecycle.MutableLiveData
 import im.vector.app.core.extensions.postLiveEvent
 import im.vector.app.core.utils.LiveEvent
 import kotlinx.coroutines.cancelChildren
+import im.vector.app.features.call.vectorCallService
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.api.session.Session
 import javax.inject.Inject
@@ -37,6 +39,12 @@ class SessionListener @Inject constructor() : Session.Listener {
         _globalErrorLiveData.postLiveEvent(globalError)
     }
 
+    override fun onNewInvitedRoom(session: Session, roomId: String) {
+        session.coroutineScope.launch {
+            session.vectorCallService.userMapper.onNewInvitedRoom(roomId)
+        }
+    }
+
     override fun onSessionStopped(session: Session) {
         session.coroutineScope.coroutineContext.cancelChildren()
     }
diff --git a/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt b/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt
new file mode 100644
index 0000000000..e6a84a41d8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.session
+
+import org.matrix.android.sdk.api.session.Session
+import kotlin.reflect.KProperty
+
+/**
+ * This is a simple hack for having some Session scope dependencies.
+ * Probably a temporary solution waiting for refactoring the Dagger management of Session.
+ * You should use it with an extension property :
+    val Session.myProperty: MyProperty by SessionScopedProperty {
+        init code
+    }
+ *
+ */
+class SessionScopedProperty<T : Any>(val initializer: (Session) -> T) {
+
+    private val propertyBySessionId = HashMap<String, T>()
+
+    private val sessionListener = object : Session.Listener {
+
+        override fun onSessionStopped(session: Session) {
+            synchronized(propertyBySessionId) {
+                session.removeListener(this)
+                propertyBySessionId.remove(session.sessionId)
+            }
+        }
+    }
+
+    operator fun getValue(thisRef: Session, property: KProperty<*>): T = synchronized(propertyBySessionId) {
+        propertyBySessionId.getOrPut(thisRef.sessionId) {
+            thisRef.addListener(sessionListener)
+            initializer(thisRef)
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt
index 8f4e36b9a1..7636ecac6a 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt
@@ -27,7 +27,7 @@ import im.vector.app.core.resources.StringProvider
 import im.vector.app.core.ui.list.genericFooterItem
 import im.vector.app.core.ui.list.genericItemWithValue
 import im.vector.app.core.utils.DebouncedClickListener
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import javax.inject.Inject
 
 class AccountDataEpoxyController @Inject constructor(
@@ -35,8 +35,8 @@ class AccountDataEpoxyController @Inject constructor(
 ) : TypedEpoxyController<AccountDataViewState>() {
 
     interface InteractionListener {
-        fun didTap(data: UserAccountDataEvent)
-        fun didLongTap(data: UserAccountDataEvent)
+        fun didTap(data: AccountDataEvent)
+        fun didLongTap(data: AccountDataEvent)
     }
 
     var interactionListener: InteractionListener? = null
diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt
index c50dd3c187..df6c8bd5fa 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt
@@ -35,7 +35,7 @@ import im.vector.app.core.utils.createJSonViewerStyleProvider
 import im.vector.app.databinding.FragmentGenericRecyclerBinding
 
 import org.billcarsonfr.jsonviewer.JSonViewerDialog
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import javax.inject.Inject
 
@@ -73,9 +73,9 @@ class AccountDataFragment @Inject constructor(
         super.onDestroyView()
     }
 
-    override fun didTap(data: UserAccountDataEvent) {
+    override fun didTap(data: AccountDataEvent) {
         val jsonString = MoshiProvider.providesMoshi()
-                .adapter(UserAccountDataEvent::class.java)
+                .adapter(AccountDataEvent::class.java)
                 .toJson(data)
         JSonViewerDialog.newInstance(
                 jsonString,
@@ -84,7 +84,7 @@ class AccountDataFragment @Inject constructor(
         ).show(childFragmentManager, "JSON_VIEWER")
     }
 
-    override fun didLongTap(data: UserAccountDataEvent) {
+    override fun didLongTap(data: AccountDataEvent) {
         AlertDialog.Builder(requireActivity())
                 .setTitle(R.string.delete)
                 .setMessage(getString(R.string.delete_account_data_warning, data.type))
diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt
index 7880e734a5..421bc53396 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt
@@ -31,11 +31,11 @@ import im.vector.app.core.platform.EmptyViewEvents
 import im.vector.app.core.platform.VectorViewModel
 import kotlinx.coroutines.launch
 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.rx.rx
 
 data class AccountDataViewState(
-        val accountData: Async<List<UserAccountDataEvent>> = Uninitialized
+        val accountData: Async<List<AccountDataEvent>> = Uninitialized
 ) : MvRxState
 
 class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState,
@@ -43,7 +43,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A
     : VectorViewModel<AccountDataViewState, AccountDataAction, EmptyViewEvents>(initialState) {
 
     init {
-        session.rx().liveAccountData(emptySet())
+        session.rx().liveUserAccountData(emptySet())
                 .execute {
                     copy(accountData = it)
                 }
@@ -57,7 +57,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A
 
     private fun handleDeleteAccountData(action: AccountDataAction.DeleteAccountData) {
         viewModelScope.launch {
-            session.updateAccountData(action.type, emptyMap())
+            session.userAccountDataService().updateAccountData(action.type, emptyMap())
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
index f9acfb3ce6..de88baa692 100644
--- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
@@ -284,7 +284,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
                     )
             )
             launchWidgetAPIAction(widgetPostAPIMediator, eventData) {
-                session.updateAccountData(
+                session.userAccountDataService().updateAccountData(
                         type = UserAccountDataTypes.TYPE_WIDGETS,
                         content = addUserWidgetBody
                 )
diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt
index 1c3ad7563c..00b388bfb8 100644
--- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt
@@ -35,7 +35,7 @@ import io.reactivex.functions.Function4
 import io.reactivex.subjects.PublishSubject
 import org.matrix.android.sdk.api.extensions.orFalse
 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.crypto.crosssigning.MASTER_KEY_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
 import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
@@ -98,8 +98,8 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
 
         keysBackupState.value = session.cryptoService().keysBackupService().state
 
-        Observable.combineLatest<List<UserAccountDataEvent>, Optional<MXCrossSigningInfo>, KeysBackupState, Optional<PrivateKeysInfo>, BannerState>(
-                session.rx().liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)),
+        Observable.combineLatest<List<AccountDataEvent>, Optional<MXCrossSigningInfo>, KeysBackupState, Optional<PrivateKeysInfo>, BannerState>(
+                session.rx().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)),
                 session.rx().liveCrossSigningInfo(session.myUserId),
                 keyBackupPublishSubject,
                 session.rx().liveCrossSigningPrivateKeys(),
diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt
index 2f8d45043b..21c0c7481a 100644
--- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt
@@ -97,7 +97,7 @@ class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState:
             )
         }
 
-        session.rx().liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME))
+        session.rx().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME))
                 .map {
                     session.sharedSecretStorageService.isRecoverySetup()
                 }