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): Observable> { - return session.getLiveAccountDataEvents(types).asObservable() + fun liveUserAccountData(types: Set): Observable> { + 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 { - return Observable.combineLatest, Optional, Optional, SecretsSynchronisationInfo>( - liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)), + return Observable.combineLatest, Optional, Optional, 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?> { t -> + val accountDataObserver = Observer?> { 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?> { t -> + val accountDefDataObserver = Observer?> { 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?> { t -> + val accountDataObserver = Observer?> { 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> + fun getLiveAccountDataEvent(type: String): LiveData> /** * 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): List + fun getAccountDataEvents(types: Set): List /** * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed */ - fun getLiveAccountDataEvents(types: Set): LiveData> + fun getLiveAccountDataEvents(types: Set): LiveData> /** * 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() - - 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 = 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>(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 = RealmList(), - var sendingTimelineEvents: RealmList = RealmList() + var sendingTimelineEvents: RealmList = RealmList(), + var accountData: RealmList = 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 { val query = realm.where() 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, private val homeServerCapabilitiesService: Lazy, - private val accountDataService: Lazy, + private val accountDataService: Lazy, private val _sharedSecretStorageService: Lazy, private val accountService: Lazy, private val eventService: Lazy, @@ -130,6 +131,7 @@ internal class DefaultSession @Inject constructor( @UnauthenticatedWithCertificate private val unauthenticatedWithCertificateOkHttpClient: Lazy ) : 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() @@ -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> { 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> { + return Transformations.map(getLiveAccountDataEvents(roomId, setOf(type))) { + it.firstOrNull()?.toOptional() + } + } + + fun getAccountDataEvents(roomId: String, types: Set): List { + return realmSessionProvider.withRealm { realm -> + val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return@withRealm emptyList() + roomEntity.accountDataEvents(types) + } + } + + fun getLiveAccountDataEvents(roomId: String, types: Set): LiveData> { + val liveRoomEntity = monarchy.findAllManagedWithChanges { RoomEntity.where(it, roomId) } + val resultLiveData = MediatorLiveData>() + 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): List { + 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> { + return dataSource.getLiveAccountDataEvent(roomId, type) + } + + override fun getAccountDataEvents(types: Set): List { + return dataSource.getAccountDataEvents(roomId, types) + } + + override fun getLiveAccountDataEvents(types: Set): LiveData> { + 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 { + + 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() - roomTagHandler.handle(realm, roomId, content) - } else if (eventType == EventType.FULLY_READ) { - val content = event.getClearContent().toModel() - 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() ?: 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() ?: 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()?.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()?.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 = emptyList() + @Json(name = "events") val list: List = 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() + roomTagHandler.handle(realm, roomId, content) + } else if (eventType == RoomAccountDataTypes.EVENT_TYPE_FULLY_READ) { + val content = event.getClearContent().toModel() + 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, - 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?): List } 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> { + fun getLiveAccountDataEvent(type: String): LiveData> { return Transformations.map(getLiveAccountDataEvents(setOf(type))) { it.firstOrNull()?.toOptional() } } - fun getAccountDataEvents(types: Set): List { + fun getAccountDataEvents(types: Set): List { return realmSessionProvider.withRealm { accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map) } } - fun getLiveAccountDataEvents(types: Set): LiveData> { + fun getLiveAccountDataEvents(types: Set): LiveData> { 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> { + override fun getLiveAccountDataEvent(type: String): LiveData> { return accountDataDataSource.getLiveAccountDataEvent(type) } - override fun getAccountDataEvents(types: Set): List { + override fun getAccountDataEvents(types: Set): List { return accountDataDataSource.getAccountDataEvents(types) } - override fun getLiveAccountDataEvents(types: Set): LiveData> { + override fun getLiveAccountDataEvents(types: Set): LiveData> { 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? = null, - excludedTypes: Set? = null): List { + private fun AccountDataEvent.mapToWidgets(widgetTypes: Set? = null, + excludedTypes: Set? = null): List { 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 { +internal fun AccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence { 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(), 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(), 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(), 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(), 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() + + 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.extractPSTN(): String? { + return when { + containsKey(PROTOCOL_PSTN_PREFIXED) -> PROTOCOL_PSTN_PREFIXED + containsKey(PROTOCOL_PSTN) -> PROTOCOL_PSTN + else -> null + } + } + + private fun Map.supportsVirtualRooms(): Boolean { + return containsKey(PROTOCOL_SIP_VIRTUAL) && containsKey(PROTOCOL_SIP_NATIVE) + } + + private suspend fun getThirdPartyProtocols(maxTries: Int): Map { + 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()?.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 { + 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 { + return tryOrNull { + thirdPartyService().getThirdPartyUser( + protocol = PROTOCOL_SIP_VIRTUAL, + fields = mapOf("native_mxid" to nativeMxid) + ) + }.orEmpty() +} + +suspend fun Session.sipNativeLookup(virtualMxid: String): List { + 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() @@ -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(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() EventType.CALL_HANGUP -> root.getClearContent().toModel() 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(val initializer: (Session) -> T) { + + private val propertyBySessionId = HashMap() + + 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() { 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> = Uninitialized + val accountData: Async> = Uninitialized ) : MvRxState class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState, @@ -43,7 +43,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A : VectorViewModel(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, Optional, KeysBackupState, Optional, BannerState>( - session.rx().liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)), + Observable.combineLatest, Optional, KeysBackupState, Optional, 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() }