Merge develop into feature/fga/call_transfer

This commit is contained in:
ganfra 2021-05-27 14:40:06 +02:00
commit 90ccc3006d
92 changed files with 1086 additions and 380 deletions

View File

@ -53,7 +53,7 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation "androidx.recyclerview:recyclerview:1.2.0" implementation "androidx.recyclerview:recyclerview:1.2.0"

View File

@ -24,7 +24,7 @@ import kotlinx.coroutines.rx2.rxSingle
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session 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.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.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
@ -177,10 +177,10 @@ class RxSession(private val session: Session) {
} }
} }
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> { fun liveUserAccountData(types: Set<String>): Observable<List<AccountDataEvent>> {
return session.getLiveAccountDataEvents(types).asObservable() return session.userAccountDataService().getLiveAccountDataEvents(types).asObservable()
.startWithCallable { .startWithCallable {
session.getAccountDataEvents(types) session.userAccountDataService().getAccountDataEvents(types)
} }
} }
@ -201,8 +201,8 @@ class RxSession(private val session: Session) {
} }
fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> { fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> {
return Observable.combineLatest<List<UserAccountDataEvent>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>( return Observable.combineLatest<List<AccountDataEvent>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)), liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
liveCrossSigningInfo(session.myUserId), liveCrossSigningInfo(session.myUserId),
liveCrossSigningPrivateKeys(), liveCrossSigningPrivateKeys(),
Function3 { _, crossSigningInfo, pInfo -> Function3 { _, crossSigningInfo, pInfo ->

View File

@ -112,7 +112,7 @@ dependencies {
def lifecycle_version = '2.2.0' def lifecycle_version = '2.2.0'
def arch_version = '2.1.0' def arch_version = '2.1.0'
def markwon_version = '3.1.0' def markwon_version = '3.1.0'
def daggerVersion = '2.35.1' def daggerVersion = '2.36'
def work_version = '2.5.0' def work_version = '2.5.0'
def retrofit_version = '2.9.0' def retrofit_version = '2.9.0'
@ -121,7 +121,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.3.0" implementation "androidx.appcompat:appcompat:1.3.0"
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.5.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

View File

@ -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.SSSS_ALGORITHM_AES_HMAC_SHA2
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService 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.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -73,12 +73,12 @@ class QuadSTests : InstrumentedTest {
// Assert Account data is updated // Assert Account data is updated
val accountDataLock = CountDownLatch(1) val accountDataLock = CountDownLatch(1)
var accountData: UserAccountDataEvent? = null var accountData: AccountDataEvent? = null
val liveAccountData = runBlocking(Dispatchers.Main) { val liveAccountData = runBlocking(Dispatchers.Main) {
aliceSession.getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") aliceSession.userAccountDataService().getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
} }
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") { if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
accountData = t.getOrNull() accountData = t.getOrNull()
accountDataLock.countDown() accountDataLock.countDown()
@ -100,13 +100,13 @@ class QuadSTests : InstrumentedTest {
quadS.setDefaultKey(TEST_KEY_ID) quadS.setDefaultKey(TEST_KEY_ID)
} }
var defaultKeyAccountData: UserAccountDataEvent? = null var defaultKeyAccountData: AccountDataEvent? = null
val defaultDataLock = CountDownLatch(1) val defaultDataLock = CountDownLatch(1)
val liveDefAccountData = runBlocking(Dispatchers.Main) { val liveDefAccountData = runBlocking(Dispatchers.Main) {
aliceSession.getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) aliceSession.userAccountDataService().getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
} }
val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> val accountDefDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) { if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
defaultKeyAccountData = t.getOrNull()!! defaultKeyAccountData = t.getOrNull()!!
defaultDataLock.countDown() 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<*, *> val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0) assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
@ -275,14 +275,14 @@ class QuadSTests : InstrumentedTest {
mTestHelper.signOutAndClose(aliceSession) mTestHelper.signOutAndClose(aliceSession)
} }
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { private fun assertAccountData(session: Session, type: String): AccountDataEvent {
val accountDataLock = CountDownLatch(1) val accountDataLock = CountDownLatch(1)
var accountData: UserAccountDataEvent? = null var accountData: AccountDataEvent? = null
val liveAccountData = runBlocking(Dispatchers.Main) { val liveAccountData = runBlocking(Dispatchers.Main) {
session.getLiveAccountDataEvent(type) session.userAccountDataService().getLiveAccountDataEvent(type)
} }
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == type) { if (t?.getOrNull()?.type == type) {
accountData = t.getOrNull() accountData = t.getOrNull()
accountDataLock.countDown() accountDataLock.countDown()

View File

@ -78,7 +78,6 @@ interface Session :
InitialSyncProgressService, InitialSyncProgressService,
HomeServerCapabilitiesService, HomeServerCapabilitiesService,
SecureStorageService, SecureStorageService,
AccountDataService,
AccountService { AccountService {
/** /**
@ -239,6 +238,11 @@ interface Session :
*/ */
fun openIdService(): OpenIdService fun openIdService(): OpenIdService
/**
* Returns the user account data service associated with the session
*/
fun userAccountDataService(): AccountDataService
/** /**
* Add a listener to the session. * Add a listener to the session.
* @param listener the listener to add. * @param listener the listener to add.
@ -262,12 +266,17 @@ interface Session :
* A global session listener to get notified for some events. * A global session listener to get notified for some events.
*/ */
interface Listener : SessionLifecycleObserver { 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: * Possible cases:
* - The access token is not valid anymore, * - The access token is not valid anymore,
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver * - 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 val sharedSecretStorageService: SharedSecretStorageService

View File

@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.events.model.Content
* Currently used types are defined in [UserAccountDataTypes]. * Currently used types are defined in [UserAccountDataTypes].
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class UserAccountDataEvent( data class AccountDataEvent(
@Json(name = "type") val type: String, @Json(name = "type") val type: String,
@Json(name = "content") val content: Content @Json(name = "content") val content: Content
) )

View File

@ -20,28 +20,31 @@ import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.util.Optional 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 { interface AccountDataService {
/** /**
* Retrieve the account data with the provided type or null if not found * 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 * Observe the account data with the provided type
*/ */
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>>
/** /**
* Retrieve the account data with the provided types. The return list can have a different size that * 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. * the size of the types set, because some AccountData may not exist.
* If an empty set is provided, all the AccountData are retrieved * If an empty set is provided, all the AccountData are retrieved
*/ */
fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent>
/** /**
* Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
*/ */
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>>
/** /**
* Update the account data with the provided type and the provided account data content * Update the account data with the provided type and the provided account data content

View File

@ -20,8 +20,6 @@ interface CallSignalingService {
suspend fun getTurnServer(): TurnServerResponse suspend fun getTurnServer(): TurnServerResponse
fun getPSTNProtocolChecker(): PSTNProtocolChecker
/** /**
* Create an outgoing call * Create an outgoing call
*/ */

View File

@ -1,98 +0,0 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.call
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.thirdparty.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn"
private const val PSTN_MATRIX_KEY = "m.protocol.pstn"
/**
* This class is responsible for checking if the HS support the PSTN protocol.
* As long as the request succeed, it'll check only once by session.
*/
@SessionScope
class PSTNProtocolChecker @Inject internal constructor(private val taskExecutor: TaskExecutor,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask) {
interface Listener {
fun onPSTNSupportUpdated()
}
private var alreadyChecked = AtomicBoolean(false)
private val pstnSupportListeners = mutableListOf<Listener>()
fun addListener(listener: Listener) {
pstnSupportListeners.add(listener)
}
fun removeListener(listener: Listener) {
pstnSupportListeners.remove(listener)
}
var supportedPSTNProtocol: String? = null
private set
fun checkForPSTNSupportIfNeeded() {
if (alreadyChecked.get()) return
taskExecutor.executorScope.checkForPSTNSupport()
}
private fun CoroutineScope.checkForPSTNSupport() = launch {
try {
supportedPSTNProtocol = getSupportedPSTN(3)
alreadyChecked.set(true)
if (supportedPSTNProtocol != null) {
pstnSupportListeners.forEach {
tryOrNull { it.onPSTNSupportUpdated() }
}
}
} catch (failure: Throwable) {
Timber.v("Fail to get supported PSTN, will check again next time.")
}
}
private suspend fun getSupportedPSTN(maxTries: Int): String? {
val thirdPartyProtocols: Map<String, ThirdPartyProtocol> = try {
getThirdPartyProtocolsTask.execute(Unit)
} catch (failure: Throwable) {
if (maxTries == 1) {
throw failure
} else {
// Wait for 10s before trying again
delay(10_000L)
return getSupportedPSTN(maxTries - 1)
}
}
return when {
thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY
thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY
else -> null
}
}
}

View File

@ -31,9 +31,7 @@ object EventType {
const val TYPING = "m.typing" const val TYPING = "m.typing"
const val REDACTION = "m.room.redaction" const val REDACTION = "m.room.redaction"
const val RECEIPT = "m.receipt" const val RECEIPT = "m.receipt"
const val TAG = "m.tag"
const val ROOM_KEY = "m.room_key" const val ROOM_KEY = "m.room_key"
const val FULLY_READ = "m.fully_read"
const val PLUMBING = "m.room.plumbing" const val PLUMBING = "m.room.plumbing"
const val BOT_OPTIONS = "m.room.bot.options" const val BOT_OPTIONS = "m.room.bot.options"
const val PREVIEW_URLS = "org.matrix.room.preview_urls" const val PREVIEW_URLS = "org.matrix.room.preview_urls"

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.room package org.matrix.android.sdk.api.session.room
import androidx.lifecycle.LiveData 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.alias.AliasService
import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.call.RoomCallService
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
@ -55,7 +56,8 @@ interface Room :
RoomCallService, RoomCallService,
RelationService, RelationService,
RoomCryptoService, RoomCryptoService,
RoomPushRuleService { RoomPushRuleService,
AccountDataService {
/** /**
* The roomId of this room * The roomId of this room
@ -86,12 +88,12 @@ interface Room :
* @return The search result * @return The search result
*/ */
suspend fun search(searchTerm: String, suspend fun search(searchTerm: String,
nextBatch: String?, nextBatch: String?,
orderByRecent: Boolean, orderByRecent: Boolean,
limit: Int, limit: Int,
beforeLimit: Int, beforeLimit: Int,
afterLimit: Int, afterLimit: Int,
includeProfile: Boolean): SearchResult includeProfile: Boolean): SearchResult
/** /**
* Use this room as a Space, if the type is correct. * Use this room as a Space, if the type is correct.

View File

@ -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"
}

View File

@ -44,7 +44,7 @@ data class CallAnswerContent(
* Capability advertisement. * Capability advertisement.
*/ */
@Json(name = "capabilities") val capabilities: CallCapabilities? = null @Json(name = "capabilities") val capabilities: CallCapabilities? = null
): CallSignallingContent { ): CallSignalingContent {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Answer( data class Answer(

View File

@ -41,4 +41,4 @@ data class CallCandidatesContent(
* Required. The version of the VoIP specification this messages adheres to. * Required. The version of the VoIP specification this messages adheres to.
*/ */
@Json(name = "version") override val version: String? @Json(name = "version") override val version: String?
): CallSignallingContent ): CallSignalingContent

View File

@ -44,7 +44,7 @@ data class CallHangupContent(
* One of: ["ice_failed", "invite_timeout"] * One of: ["ice_failed", "invite_timeout"]
*/ */
@Json(name = "reason") val reason: Reason? = null @Json(name = "reason") val reason: Reason? = null
) : CallSignallingContent { ) : CallSignalingContent {
@JsonClass(generateAdapter = false) @JsonClass(generateAdapter = false)
enum class Reason { enum class Reason {
@Json(name = "ice_failed") @Json(name = "ice_failed")

View File

@ -55,7 +55,7 @@ data class CallInviteContent(
*/ */
@Json(name = "capabilities") val capabilities: CallCapabilities? = null @Json(name = "capabilities") val capabilities: CallCapabilities? = null
): CallSignallingContent { ): CallSignalingContent {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Offer( data class Offer(
/** /**

View File

@ -47,7 +47,7 @@ data class CallNegotiateContent(
*/ */
@Json(name = "version") override val version: String? @Json(name = "version") override val version: String?
): CallSignallingContent { ): CallSignalingContent {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Description( data class Description(
/** /**

View File

@ -37,4 +37,4 @@ data class CallRejectContent(
* Required. The version of the VoIP specification this message adheres to. * Required. The version of the VoIP specification this message adheres to.
*/ */
@Json(name = "version") override val version: String? @Json(name = "version") override val version: String?
) : CallSignallingContent ) : CallSignalingContent

View File

@ -61,7 +61,7 @@ data class CallReplacesContent(
* Required. The version of the VoIP specification this messages adheres to. * Required. The version of the VoIP specification this messages adheres to.
*/ */
@Json(name = "version") override val version: String? @Json(name = "version") override val version: String?
): CallSignallingContent { ): CallSignalingContent {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class TargetUser( data class TargetUser(

View File

@ -41,4 +41,4 @@ data class CallSelectAnswerContent(
* Required. The version of the VoIP specification this message adheres to. * Required. The version of the VoIP specification this message adheres to.
*/ */
@Json(name = "version") override val version: String? @Json(name = "version") override val version: String?
): CallSignallingContent ): CallSignalingContent

View File

@ -16,7 +16,7 @@
package org.matrix.android.sdk.api.session.room.model.call package org.matrix.android.sdk.api.session.room.model.call
interface CallSignallingContent { interface CallSignalingContent {
/** /**
* Required. A unique identifier for the call. * Required. A unique identifier for the call.
*/ */

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.FieldAttribute import io.realm.FieldAttribute
import io.realm.RealmMigration 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.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent 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.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields 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.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.RoomEntityFields
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@ -44,7 +46,7 @@ import javax.inject.Inject
class RealmSessionStoreMigration @Inject constructor() : RealmMigration { class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
companion object { 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) { 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 <= 10) migrateTo11(realm)
if (oldVersion <= 11) migrateTo12(realm) if (oldVersion <= 11) migrateTo12(realm)
if (oldVersion <= 12) migrateTo13(realm) if (oldVersion <= 12) migrateTo13(realm)
if (oldVersion <= 13) migrateTo14(realm)
} }
private fun migrateTo1(realm: DynamicRealm) { private fun migrateTo1(realm: DynamicRealm) {
@ -278,11 +281,29 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
private fun migrateTo13(realm: DynamicRealm) { private fun migrateTo13(realm: DynamicRealm) {
Timber.d("Step 12 -> 13") Timber.d("Step 12 -> 13")
// Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12() // Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12()
realm.schema.get("SpaceChildSummaryEntity") realm.schema.get("SpaceChildSummaryEntity")
?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) } ?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) }
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java) ?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true) ?.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
}
} }

View File

@ -17,17 +17,25 @@
package org.matrix.android.sdk.internal.database.mapper package org.matrix.android.sdk.internal.database.mapper
import com.squareup.moshi.Moshi 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.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.internal.database.model.UserAccountDataEntity
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
import javax.inject.Inject import javax.inject.Inject
internal class AccountDataMapper @Inject constructor(moshi: Moshi) { internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE) private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
fun map(entity: UserAccountDataEntity): UserAccountDataEvent { fun map(entity: UserAccountDataEntity): AccountDataEvent {
return UserAccountDataEvent( return AccountDataEvent(
type = entity.type ?: "",
content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
)
}
fun map(entity: RoomAccountDataEntity): AccountDataEvent {
return AccountDataEvent(
type = entity.type ?: "", type = entity.type ?: "",
content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty() content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
) )

View File

@ -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()

View File

@ -23,7 +23,8 @@ import io.realm.annotations.PrimaryKey
internal open class RoomEntity(@PrimaryKey var roomId: String = "", internal open class RoomEntity(@PrimaryKey var roomId: String = "",
var chunks: RealmList<ChunkEntity> = RealmList(), var chunks: RealmList<ChunkEntity> = RealmList(),
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList() var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
var accountData: RealmList<RoomAccountDataEntity> = RealmList()
) : RealmObject() { ) : RealmObject() {
private var membershipStr: String = Membership.NONE.name private var membershipStr: String = Membership.NONE.name

View File

@ -232,6 +232,12 @@ internal open class RoomSummaryEntity(
} }
} }
@Index
var isHiddenFromUser: Boolean = false
set(value) {
if (value != field) field = value
}
@Index @Index
private var versioningStateStr: String = VersioningState.NONE.name private var versioningStateStr: String = VersioningState.NONE.name
var versioningState: VersioningState var versioningState: VersioningState

View File

@ -62,6 +62,7 @@ import io.realm.annotations.RealmModule
UserAccountDataEntity::class, UserAccountDataEntity::class,
ScalarTokenEntity::class, ScalarTokenEntity::class,
WellknownIntegrationManagerConfigEntity::class, WellknownIntegrationManagerConfigEntity::class,
RoomAccountDataEntity::class,
SpaceChildSummaryEntity::class, SpaceChildSummaryEntity::class,
SpaceParentSummaryEntity::class SpaceParentSummaryEntity::class
]) ])

View File

@ -29,6 +29,10 @@ internal fun RoomEntity.Companion.where(realm: Realm, roomId: String): RealmQuer
.equalTo(RoomEntityFields.ROOM_ID, roomId) .equalTo(RoomEntityFields.ROOM_ID, roomId)
} }
internal fun RoomEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomEntity {
return where(realm, roomId).findFirst() ?: realm.createObject(RoomEntity::class.java, roomId)
}
internal fun RoomEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<RoomEntity> { internal fun RoomEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<RoomEntity> {
val query = realm.where<RoomEntity>() val query = realm.where<RoomEntity>()
if (membership != null) { if (membership != null) {

View File

@ -16,13 +16,13 @@
package org.matrix.android.sdk.internal.network 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.api.failure.GlobalError
import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.auth.SessionParamsStore
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -44,7 +44,6 @@ internal class GlobalErrorHandler @Inject constructor(
sessionParamsStore.setTokenInvalid(sessionId) sessionParamsStore.setTokenInvalid(sessionId)
} }
} }
listener?.onGlobalError(globalError) listener?.onGlobalError(globalError)
} }

View File

@ -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.SyncTokenStore
import org.matrix.android.sdk.internal.session.sync.job.SyncThread 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.sync.job.SyncWorker
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService
import org.matrix.android.sdk.internal.util.createUIHandler import org.matrix.android.sdk.internal.util.createUIHandler
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -117,7 +118,7 @@ internal class DefaultSession @Inject constructor(
private val contentDownloadStateTracker: ContentDownloadStateTracker, private val contentDownloadStateTracker: ContentDownloadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>, private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>, private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
private val accountDataService: Lazy<AccountDataService>, private val accountDataService: Lazy<UserAccountDataService>,
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
private val accountService: Lazy<AccountService>, private val accountService: Lazy<AccountService>,
private val eventService: Lazy<EventService>, private val eventService: Lazy<EventService>,
@ -130,6 +131,7 @@ internal class DefaultSession @Inject constructor(
@UnauthenticatedWithCertificate @UnauthenticatedWithCertificate
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient> private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
) : Session, ) : Session,
GlobalErrorHandler.Listener,
RoomService by roomService.get(), RoomService by roomService.get(),
RoomDirectoryService by roomDirectoryService.get(), RoomDirectoryService by roomDirectoryService.get(),
GroupService by groupService.get(), GroupService by groupService.get(),
@ -144,9 +146,7 @@ internal class DefaultSession @Inject constructor(
SecureStorageService by secureStorageService.get(), SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
ProfileService by profileService.get(), ProfileService by profileService.get(),
AccountDataService by accountDataService.get(), AccountService by accountService.get() {
AccountService by accountService.get(),
GlobalErrorHandler.Listener {
override val sharedSecretStorageService: SharedSecretStorageService override val sharedSecretStorageService: SharedSecretStorageService
get() = _sharedSecretStorageService.get() get() = _sharedSecretStorageService.get()
@ -164,16 +164,16 @@ internal class DefaultSession @Inject constructor(
override fun open() { override fun open() {
assert(!isOpen) assert(!isOpen)
isOpen = true isOpen = true
globalErrorHandler.listener = this
cryptoService.get().ensureDevice() cryptoService.get().ensureDevice()
uiHandler.post { uiHandler.post {
lifecycleObservers.forEach { lifecycleObservers.forEach {
it.onSessionStarted(this) it.onSessionStarted(this)
} }
sessionListeners.dispatch { sessionListeners.dispatch { _, listener ->
it.onSessionStarted(this) listener.onSessionStarted(this)
} }
} }
globalErrorHandler.listener = this
} }
override fun requireBackgroundSync() { override fun requireBackgroundSync() {
@ -213,13 +213,13 @@ internal class DefaultSession @Inject constructor(
// timelineEventDecryptor.destroy() // timelineEventDecryptor.destroy()
uiHandler.post { uiHandler.post {
lifecycleObservers.forEach { it.onSessionStopped(this) } lifecycleObservers.forEach { it.onSessionStopped(this) }
sessionListeners.dispatch { sessionListeners.dispatch { _, listener ->
it.onSessionStopped(this) listener.onSessionStopped(this)
} }
} }
cryptoService.get().close() cryptoService.get().close()
isOpen = false
globalErrorHandler.listener = null globalErrorHandler.listener = null
isOpen = false
} }
override fun getSyncStateLive() = getSyncThread().liveState() override fun getSyncStateLive() = getSyncThread().liveState()
@ -243,8 +243,8 @@ internal class DefaultSession @Inject constructor(
lifecycleObservers.forEach { lifecycleObservers.forEach {
it.onClearCache(this) it.onClearCache(this)
} }
sessionListeners.dispatch { sessionListeners.dispatch { _, listener ->
it.onClearCache(this) listener.onClearCache(this)
} }
} }
withContext(NonCancellable) { withContext(NonCancellable) {
@ -254,8 +254,8 @@ internal class DefaultSession @Inject constructor(
} }
override fun onGlobalError(globalError: GlobalError) { override fun onGlobalError(globalError: GlobalError) {
sessionListeners.dispatch { sessionListeners.dispatch { _, listener ->
it.onGlobalError(this, globalError) listener.onGlobalError(this, globalError)
} }
} }
@ -293,6 +293,8 @@ internal class DefaultSession @Inject constructor(
override fun openIdService(): OpenIdService = openIdService.get() override fun openIdService(): OpenIdService = openIdService.get()
override fun userAccountDataService(): AccountDataService = accountDataService.get()
override fun getOkHttpClient(): OkHttpClient { override fun getOkHttpClient(): OkHttpClient {
return unauthenticatedWithCertificateOkHttpClient.get() return unauthenticatedWithCertificateOkHttpClient.get()
} }

View File

@ -16,10 +16,16 @@
package org.matrix.android.sdk.internal.session 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.api.session.Session
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.di.SessionId
import javax.inject.Inject import javax.inject.Inject
internal class SessionListeners @Inject constructor() { @SessionScope
internal class SessionListeners @Inject constructor(
@SessionId private val sessionId: String,
private val sessionManager: SessionManager) {
private val listeners = mutableSetOf<Session.Listener>() private val listeners = mutableSetOf<Session.Listener>()
@ -35,11 +41,17 @@ internal class SessionListeners @Inject constructor() {
} }
} }
fun dispatch(block: (Session.Listener) -> Unit) { fun dispatch(block: (Session, Session.Listener) -> Unit) {
synchronized(listeners) { synchronized(listeners) {
val session = getSession()
listeners.forEach { 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.")
}
} }

View File

@ -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.room.tombstone.RoomTombstoneEventProcessor
import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorageService 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.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.session.widgets.DefaultWidgetURLFormatter
import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.md5
import retrofit2.Retrofit import retrofit2.Retrofit
@ -364,7 +364,7 @@ internal abstract class SessionModule {
abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
@Binds @Binds
abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService abstract fun bindAccountDataService(service: UserAccountDataService): AccountDataService
@Binds @Binds
abstract fun bindEventService(service: DefaultEventService): EventService abstract fun bindEventService(service: DefaultEventService): EventService

View File

@ -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.CallNegotiateContent
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent 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.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.api.util.Optional
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope 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 { private fun MxCall.partyIdsMatches(contentSignalingContent: CallSignalingContent): Boolean {
return opponentPartyId?.getOrNull() == contentSignallingContent.partyId return opponentPartyId?.getOrNull() == contentSignalingContent.partyId
} }
private fun CallSignallingContent.getCall(): MxCall? { private fun CallSignalingContent.getCall(): MxCall? {
val currentCall = callId?.let { val currentCall = callId?.let {
activeCallHandler.getCallWithId(it) activeCallHandler.getCallWithId(it)
} }

View File

@ -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.CallListener
import org.matrix.android.sdk.api.session.call.CallSignalingService 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.MxCall
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
@ -30,18 +29,13 @@ internal class DefaultCallSignalingService @Inject constructor(
private val callSignalingHandler: CallSignalingHandler, private val callSignalingHandler: CallSignalingHandler,
private val mxCallFactory: MxCallFactory, private val mxCallFactory: MxCallFactory,
private val activeCallHandler: ActiveCallHandler, private val activeCallHandler: ActiveCallHandler,
private val turnServerDataSource: TurnServerDataSource, private val turnServerDataSource: TurnServerDataSource
private val pstnProtocolChecker: PSTNProtocolChecker
) : CallSignalingService { ) : CallSignalingService {
override suspend fun getTurnServer(): TurnServerResponse { override suspend fun getTurnServer(): TurnServerResponse {
return turnServerDataSource.getTurnServer() return turnServerDataSource.getTurnServer()
} }
override fun getPSTNProtocolChecker(): PSTNProtocolChecker {
return pstnProtocolChecker
}
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall { override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
return mxCallFactory.createOutgoingCall(roomId, otherUserId, isVideoCall).also { return mxCallFactory.createOutgoingCall(roomId, otherUserId, isVideoCall).also {
activeCallHandler.addCall(it) activeCallHandler.addCall(it)

View File

@ -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.profile.UnbindThreePidsTask
import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent 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.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.session.user.accountdata.UpdateUserAccountDataTask
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.ensureProtocol import org.matrix.android.sdk.internal.util.ensureProtocol
@ -77,7 +77,7 @@ internal class DefaultIdentityService @Inject constructor(
private val submitTokenForBindingTask: IdentitySubmitTokenForBindingTask, private val submitTokenForBindingTask: IdentitySubmitTokenForBindingTask,
private val unbindThreePidsTask: UnbindThreePidsTask, private val unbindThreePidsTask: UnbindThreePidsTask,
private val identityApiProvider: IdentityApiProvider, private val identityApiProvider: IdentityApiProvider,
private val accountDataDataSource: AccountDataDataSource, private val accountDataDataSource: UserAccountDataDataSource,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService, private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
private val sessionParams: SessionParams private val sessionParams: SessionParams
) : IdentityService, SessionLifecycleObserver { ) : IdentityService, SessionLifecycleObserver {

View File

@ -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.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope 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.UserAccountDataTypes
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
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.session.user.accountdata.UpdateUserAccountDataTask
import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence 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, internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val accountDataDataSource: AccountDataDataSource, private val accountDataDataSource: UserAccountDataDataSource,
private val widgetFactory: WidgetFactory) private val widgetFactory: WidgetFactory)
: SessionLifecycleObserver { : 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) return extractWidgetSequence(widgetFactory)
.filter { .filter {
WidgetType.IntegrationManager == it.type WidgetType.IntegrationManager == it.type

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.room package org.matrix.android.sdk.internal.session.room
import androidx.lifecycle.LiveData 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.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.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.api.util.Optional
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM 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.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.state.SendStateTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource 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.search.SearchTask
import org.matrix.android.sdk.internal.session.space.DefaultSpace import org.matrix.android.sdk.internal.session.space.DefaultSpace
import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.internal.util.awaitCallback
import java.security.InvalidParameterException import java.security.InvalidParameterException
import javax.inject.Inject
internal class DefaultRoom @Inject constructor(override val roomId: String, internal class DefaultRoom(override val roomId: String,
private val roomSummaryDataSource: RoomSummaryDataSource, private val roomSummaryDataSource: RoomSummaryDataSource,
private val timelineService: TimelineService, private val timelineService: TimelineService,
private val sendService: SendService, private val sendService: SendService,
private val draftService: DraftService, private val draftService: DraftService,
private val stateService: StateService, private val stateService: StateService,
private val uploadsService: UploadsService, private val uploadsService: UploadsService,
private val reportingService: ReportingService, private val reportingService: ReportingService,
private val roomCallService: RoomCallService, private val roomCallService: RoomCallService,
private val readService: ReadService, private val readService: ReadService,
private val typingService: TypingService, private val typingService: TypingService,
private val aliasService: AliasService, private val aliasService: AliasService,
private val tagsService: TagsService, private val tagsService: TagsService,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val relationService: RelationService, private val relationService: RelationService,
private val roomMembersService: MembershipService, private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService, private val roomPushRuleService: RoomPushRuleService,
private val sendStateTask: SendStateTask, private val roomAccountDataService: RoomAccountDataService,
private val viaParameterFinder: ViaParameterFinder, private val sendStateTask: SendStateTask,
private val searchTask: SearchTask) : private val viaParameterFinder: ViaParameterFinder,
private val searchTask: SearchTask) :
Room, Room,
TimelineService by timelineService, TimelineService by timelineService,
SendService by sendService, SendService by sendService,
@ -83,7 +85,8 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
TagsService by tagsService, TagsService by tagsService,
RelationService by relationService, RelationService by relationService,
MembershipService by roomMembersService, MembershipService by roomMembersService,
RoomPushRuleService by roomPushRuleService { RoomPushRuleService by roomPushRuleService,
AccountDataService by roomAccountDataService {
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> { override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
return roomSummaryDataSource.getRoomSummaryLive(roomId) return roomSummaryDataSource.getRoomSummaryLive(roomId)

View File

@ -360,4 +360,13 @@ internal interface RoomAPI {
suspend fun deleteTag(@Path("userId") userId: String, suspend fun deleteTag(@Path("userId") userId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("tag") tag: 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)
} }

View File

@ -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.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.internal.session.SessionScope 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.permalinks.ViaParameterFinder
import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
@ -60,6 +61,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
private val relationServiceFactory: DefaultRelationService.Factory, private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
private val roomAccountDataServiceFactory: RoomAccountDataService.Factory,
private val sendStateTask: SendStateTask, private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder, private val viaParameterFinder: ViaParameterFinder,
private val searchTask: SearchTask) : private val searchTask: SearchTask) :
@ -84,6 +86,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
relationService = relationServiceFactory.create(roomId), relationService = relationServiceFactory.create(roomId),
roomMembersService = membershipServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId),
roomPushRuleService = roomPushRuleServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
sendStateTask = sendStateTask, sendStateTask = sendStateTask,
searchTask = searchTask, searchTask = searchTask,
viaParameterFinder = viaParameterFinder viaParameterFinder = viaParameterFinder

View File

@ -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.DefaultFileService
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI 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.AddRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultDeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultDeleteRoomAliasTask
@ -236,6 +238,9 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask
@Binds
abstract fun bindUpdateRoomAccountDataTask(task: DefaultUpdateRoomAccountDataTask): UpdateRoomAccountDataTask
@Binds @Binds
abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
} }

View File

@ -0,0 +1,77 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.room.accountdata
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.mapper.AccountDataMapper
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject
internal class RoomAccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider,
private val accountDataMapper: AccountDataMapper) {
fun getAccountDataEvent(roomId: String, type: String): AccountDataEvent? {
return getAccountDataEvents(roomId, setOf(type)).firstOrNull()
}
fun getLiveAccountDataEvent(roomId: String, type: String): LiveData<Optional<AccountDataEvent>> {
return Transformations.map(getLiveAccountDataEvents(roomId, setOf(type))) {
it.firstOrNull()?.toOptional()
}
}
fun getAccountDataEvents(roomId: String, types: Set<String>): List<AccountDataEvent> {
return realmSessionProvider.withRealm { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return@withRealm emptyList()
roomEntity.accountDataEvents(types)
}
}
fun getLiveAccountDataEvents(roomId: String, types: Set<String>): LiveData<List<AccountDataEvent>> {
val liveRoomEntity = monarchy.findAllManagedWithChanges { RoomEntity.where(it, roomId) }
val resultLiveData = MediatorLiveData<List<AccountDataEvent>>()
resultLiveData.addSource(liveRoomEntity) {
val roomEntity = it.realmResults.firstOrNull()
if (roomEntity == null) {
resultLiveData.postValue(emptyList())
} else {
val mappedResult = roomEntity.accountDataEvents(types)
resultLiveData.postValue(mappedResult)
}
}
return resultLiveData
}
private fun RoomEntity.accountDataEvents(types: Set<String>): List<AccountDataEvent> {
val query = accountData.where()
if (types.isNotEmpty()) {
query.`in`(RoomAccountDataEntityFields.TYPE, types.toTypedArray())
}
return query.findAll().map { accountDataMapper.map(it) }
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.room.accountdata
import androidx.lifecycle.LiveData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.util.Optional
internal class RoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String,
private val dataSource: RoomAccountDataDataSource,
private val updateRoomAccountDataTask: UpdateRoomAccountDataTask
) : AccountDataService {
@AssistedFactory
interface Factory {
fun create(roomId: String): RoomAccountDataService
}
override fun getAccountDataEvent(type: String): AccountDataEvent? {
return dataSource.getAccountDataEvent(roomId, type)
}
override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
return dataSource.getLiveAccountDataEvent(roomId, type)
}
override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
return dataSource.getAccountDataEvents(roomId, types)
}
override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
return dataSource.getLiveAccountDataEvents(roomId, types)
}
override suspend fun updateAccountData(type: String, content: Content) {
val params = UpdateRoomAccountDataTask.Params(roomId, type, content)
return updateRoomAccountDataTask.execute(params)
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.room.accountdata
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface UpdateRoomAccountDataTask : Task<UpdateRoomAccountDataTask.Params, Unit> {
data class Params(
val roomId: String,
val type: String,
val content: JsonDict
)
}
internal class DefaultUpdateRoomAccountDataTask @Inject constructor(
private val roomApi: RoomAPI,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver
) : UpdateRoomAccountDataTask {
override suspend fun execute(params: UpdateRoomAccountDataTask.Params) {
return executeRequest(globalErrorReceiver) {
roomApi.setRoomAccountData(userId, params.roomId, params.type, params.content)
}
}
}

View File

@ -37,6 +37,7 @@ internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveP
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst() val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst()
?: RoomSummaryEntity(predecessorRoomId) ?: RoomSummaryEntity(predecessorRoomId)
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
predecessorRoomSummary.isHiddenFromUser = true
realm.insertOrUpdate(predecessorRoomSummary) realm.insertOrUpdate(predecessorRoomSummary)
} }

View File

@ -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.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary 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.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.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount 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.DISPLAY_NAME, queryParams.displayName)
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) 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 { queryParams.roomCategoryFilter?.let {
when (it) { when (it) {

View File

@ -21,6 +21,7 @@ import io.realm.kotlin.createObject
import org.matrix.android.sdk.api.extensions.tryOrNull 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.EventType
import org.matrix.android.sdk.api.session.events.model.toModel 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.Membership
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent 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.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent 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.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.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.EventDecryptor 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.extensions.clearWith
import org.matrix.android.sdk.internal.query.process 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.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.RoomDisplayNameResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper 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.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.RoomSyncSummary
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
import timber.log.Timber import timber.log.Timber
@ -71,7 +73,7 @@ internal class RoomSummaryUpdater @Inject constructor(
private val roomAvatarResolver: RoomAvatarResolver, private val roomAvatarResolver: RoomAvatarResolver,
private val eventDecryptor: EventDecryptor, private val eventDecryptor: EventDecryptor,
private val crossSigningService: DefaultCrossSigningService, private val crossSigningService: DefaultCrossSigningService,
private val stateEventDataSource: StateEventDataSource) { private val roomAccountDataDataSource: RoomAccountDataDataSource) {
fun update(realm: Realm, fun update(realm: Realm,
roomId: String, roomId: String,
@ -100,6 +102,10 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.membership = membership 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 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 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 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()}") // Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}")
lookupMap.entries 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 -> .forEach { entry ->
val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst() val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst()
if (parent != null) { if (parent != null) {

View File

@ -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.initsync.InitSyncStep
import org.matrix.android.sdk.api.session.room.model.Membership 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.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.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM 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.initsync.reportSubtask
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource 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.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.summary.RoomSummaryUpdater
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput 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.InvitedRoomSync
import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral 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.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.model.RoomsSyncResponse
import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler
import org.matrix.android.sdk.internal.util.computeBestChunkSize import org.matrix.android.sdk.internal.util.computeBestChunkSize
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler, internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler,
private val roomSummaryUpdater: RoomSummaryUpdater, private val roomSummaryUpdater: RoomSummaryUpdater,
private val roomTagHandler: RoomTagHandler, private val roomAccountDataHandler: RoomSyncAccountDataHandler,
private val roomFullyReadHandler: RoomFullyReadHandler,
private val cryptoService: DefaultCryptoService, private val cryptoService: DefaultCryptoService,
private val roomMemberEventHandler: RoomMemberEventHandler, private val roomMemberEventHandler: RoomMemberEventHandler,
private val roomTypingUsersHandler: RoomTypingUsersHandler, private val roomTypingUsersHandler: RoomTypingUsersHandler,
@ -198,11 +195,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
?.takeIf { it.isNotEmpty() } ?.takeIf { it.isNotEmpty() }
?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC, aggregator) } ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC, aggregator) }
if (roomSync.accountData?.events?.isNotEmpty() == true) { if (roomSync.accountData != null) {
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData) 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) { if (roomEntity.membership == Membership.INVITE) {
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
@ -265,7 +262,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
insertType: EventInsertType, insertType: EventInsertType,
syncLocalTimestampMillis: Long): RoomEntity { syncLocalTimestampMillis: Long): RoomEntity {
Timber.v("Handle invited sync for room $roomId") 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 roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
roomSync.inviteState.events.forEach { event -> roomSync.inviteState.events.forEach { event ->
@ -294,7 +291,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
roomSync: RoomSync, roomSync: RoomSync,
insertType: EventInsertType, insertType: EventInsertType,
syncLocalTimestampMillis: Long): RoomEntity { 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()) { for (event in roomSync.state?.events.orEmpty()) {
if (event.eventId == null || event.stateKey == null || event.type == null) { if (event.eventId == null || event.stateKey == null || event.type == null) {
continue continue
@ -460,17 +457,4 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
return result return result
} }
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
accountData.events?.forEach { event ->
val eventType = event.getClearType()
if (eventType == EventType.TAG) {
val content = event.getClearContent().toModel<RoomTagContent>()
roomTagHandler.handle(realm, roomId, content)
} else if (eventType == EventType.FULLY_READ) {
val content = event.getClearContent().toModel<FullyReadContent>()
roomFullyReadHandler.handle(realm, roomId, content)
}
}
}
} }

View File

@ -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.SessionDatabase
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider 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.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.initsync.reportSubtask
@ -44,6 +45,7 @@ private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
internal class SyncResponseHandler @Inject constructor( internal class SyncResponseHandler @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val sessionListeners: SessionListeners,
private val workManagerProvider: WorkManagerProvider, private val workManagerProvider: WorkManagerProvider,
private val roomSyncHandler: RoomSyncHandler, private val roomSyncHandler: RoomSyncHandler,
private val userAccountDataSyncHandler: UserAccountDataSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
@ -125,6 +127,7 @@ internal class SyncResponseHandler @Inject constructor(
syncResponse.rooms?.let { syncResponse.rooms?.let {
checkPushRules(it, isInitialSync) checkPushRules(it, isInitialSync)
userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite)
dispatchInvitedRoom(it)
} }
syncResponse.groups?.let { syncResponse.groups?.let {
scheduleGroupDataFetchingIfNeeded(it) 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. * 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. * You can also force to refetch group data using [Group] API.

View File

@ -23,7 +23,7 @@ import io.realm.kotlin.where
import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.pushrules.RuleScope
import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.RuleSetKey
import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse 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.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
@ -113,7 +113,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
} }
} }
private fun handlePushRules(realm: Realm, event: UserAccountDataEvent) { private fun handlePushRules(realm: Realm, event: AccountDataEvent) {
val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return
realm.where(PushRulesEntity::class.java) realm.where(PushRulesEntity::class.java)
.findAll() .findAll()
@ -155,7 +155,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
realm.insertOrUpdate(underrides) realm.insertOrUpdate(underrides)
} }
private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) { private fun handleDirectChatRooms(realm: Realm, event: AccountDataEvent) {
val content = event.content.toModel<DirectMessagesContent>() ?: return val content = event.content.toModel<DirectMessagesContent>() ?: return
content.forEach { (userId, roomIds) -> content.forEach { (userId, roomIds) ->
roomIds.forEach { roomId -> roomIds.forEach { roomId ->
@ -181,7 +181,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
} }
} }
private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) { private fun handleIgnoredUsers(realm: Realm, event: AccountDataEvent) {
val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return
realm.where(IgnoredUserEntity::class.java) realm.where(IgnoredUserEntity::class.java)
.findAll() .findAll()
@ -191,7 +191,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
// TODO If not initial sync, we should execute a init sync // TODO If not initial sync, we should execute a init sync
} }
private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) { private fun handleBreadcrumbs(realm: Realm, event: AccountDataEvent) {
val recentRoomIds = event.content.toModel<BreadcrumbsContent>()?.recentRoomIds ?: return val recentRoomIds = event.content.toModel<BreadcrumbsContent>()?.recentRoomIds ?: return
val entity = BreadcrumbsEntity.getOrCreate(realm) val entity = BreadcrumbsEntity.getOrCreate(realm)

View File

@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass 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) @JsonClass(generateAdapter = true)
internal data class UserAccountDataSync( internal data class UserAccountDataSync(
@Json(name = "events") val list: List<UserAccountDataEvent> = emptyList() @Json(name = "events") val list: List<AccountDataEvent> = emptyList()
) )

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.sync.parsing
import io.realm.Realm
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntity
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.session.room.read.FullyReadContent
import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler
import org.matrix.android.sdk.internal.session.sync.RoomTagHandler
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData
import javax.inject.Inject
internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTagHandler: RoomTagHandler,
private val roomFullyReadHandler: RoomFullyReadHandler) {
fun handle(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
if (accountData.events.isNullOrEmpty()) {
return
}
val roomEntity = RoomEntity.getOrCreate(realm, roomId)
for (event in accountData.events) {
val eventType = event.getClearType()
handleGeneric(roomEntity, event.getClearContent(), eventType)
if (eventType == RoomAccountDataTypes.EVENT_TYPE_TAG) {
val content = event.getClearContent().toModel<RoomTagContent>()
roomTagHandler.handle(realm, roomId, content)
} else if (eventType == RoomAccountDataTypes.EVENT_TYPE_FULLY_READ) {
val content = event.getClearContent().toModel<FullyReadContent>()
roomFullyReadHandler.handle(realm, roomId, content)
}
}
}
private fun handleGeneric(roomEntity: RoomEntity, content: JsonDict?, eventType: String) {
val existing = roomEntity.accountData.where().equalTo(RoomAccountDataEntityFields.TYPE, eventType).findFirst()
if (existing != null) {
// Update current value
existing.contentStr = ContentMapper.map(content)
} else {
val roomAccountData = RoomAccountDataEntity(
type = eventType,
contentStr = ContentMapper.map(content)
)
roomEntity.accountData.add(roomAccountData)
}
}
}

View File

@ -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.identity.IdentityRegisterTask
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask 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.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.session.user.accountdata.UpdateUserAccountDataTask
import org.matrix.android.sdk.internal.util.ensureTrailingSlash import org.matrix.android.sdk.internal.util.ensureTrailingSlash
import javax.inject.Inject import javax.inject.Inject
@ -38,7 +38,7 @@ import javax.inject.Inject
internal class DefaultTermsService @Inject constructor( internal class DefaultTermsService @Inject constructor(
@UnauthenticatedWithCertificate @UnauthenticatedWithCertificate
private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>, private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
private val accountDataDataSource: AccountDataDataSource, private val accountDataDataSource: UserAccountDataDataSource,
private val termsAPI: TermsAPI, private val termsAPI: TermsAPI,
private val retrofitFactory: RetrofitFactory, private val retrofitFactory: RetrofitFactory,
private val getOpenIdTokenTask: GetOpenIdTokenTask, private val getOpenIdTokenTask: GetOpenIdTokenTask,

View File

@ -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 * 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, suspend fun getThirdPartyUser(@Path("protocol") protocol: String,
@QueryMap params: Map<String, String>?): List<ThirdPartyUser> @QueryMap params: Map<String, String>?): List<ThirdPartyUser>
} }

View File

@ -21,7 +21,7 @@ import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery 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.Optional
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.RealmSessionProvider 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 org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject import javax.inject.Inject
internal class AccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, internal class UserAccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider, private val realmSessionProvider: RealmSessionProvider,
private val accountDataMapper: AccountDataMapper) { private val accountDataMapper: AccountDataMapper) {
fun getAccountDataEvent(type: String): UserAccountDataEvent? { fun getAccountDataEvent(type: String): AccountDataEvent? {
return getAccountDataEvents(setOf(type)).firstOrNull() return getAccountDataEvents(setOf(type)).firstOrNull()
} }
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> { fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
return Transformations.map(getLiveAccountDataEvents(setOf(type))) { return Transformations.map(getLiveAccountDataEvents(setOf(type))) {
it.firstOrNull()?.toOptional() it.firstOrNull()?.toOptional()
} }
} }
fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> { fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
return realmSessionProvider.withRealm { return realmSessionProvider.withRealm {
accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map) accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map)
} }
} }
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> { fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
return monarchy.findAllMappedWithChanges( return monarchy.findAllMappedWithChanges(
{ accountDataEventsQuery(it, types) }, { accountDataEventsQuery(it, types) },
accountDataMapper::map accountDataMapper::map

View File

@ -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.api.util.Optional
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler 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.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.internal.util.awaitCallback
import javax.inject.Inject import javax.inject.Inject
internal class DefaultAccountDataService @Inject constructor( internal class UserAccountDataService @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val userAccountDataSyncHandler: UserAccountDataSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val accountDataDataSource: AccountDataDataSource, private val accountDataDataSource: UserAccountDataDataSource,
private val taskExecutor: TaskExecutor private val taskExecutor: TaskExecutor
) : AccountDataService { ) : AccountDataService {
override fun getAccountDataEvent(type: String): UserAccountDataEvent? { override fun getAccountDataEvent(type: String): AccountDataEvent? {
return accountDataDataSource.getAccountDataEvent(type) return accountDataDataSource.getAccountDataEvent(type)
} }
override fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> { override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
return accountDataDataSource.getLiveAccountDataEvent(type) return accountDataDataSource.getLiveAccountDataEvent(type)
} }
override fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> { override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
return accountDataDataSource.getAccountDataEvents(types) return accountDataDataSource.getAccountDataEvents(types)
} }
override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> { override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
return accountDataDataSource.getLiveAccountDataEvents(types) return accountDataDataSource.getLiveAccountDataEvents(types)
} }

View File

@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session 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.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.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.SessionScope
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager 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.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.WidgetFactory
import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
import java.util.HashMap import java.util.HashMap
@ -47,7 +47,7 @@ import javax.inject.Inject
@SessionScope @SessionScope
internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager, internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager,
private val accountDataDataSource: AccountDataDataSource, private val accountDataDataSource: UserAccountDataDataSource,
private val stateEventDataSource: StateEventDataSource, private val stateEventDataSource: StateEventDataSource,
private val createWidgetTask: CreateWidgetTask, private val createWidgetTask: CreateWidgetTask,
private val widgetFactory: WidgetFactory, private val widgetFactory: WidgetFactory,
@ -150,8 +150,8 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes) return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes)
} }
private fun UserAccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null, private fun AccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null,
excludedTypes: Set<String>? = null): List<Widget> { excludedTypes: Set<String>? = null): List<Widget> {
return extractWidgetSequence(widgetFactory) return extractWidgetSequence(widgetFactory)
.filter { .filter {
val widgetType = it.widgetContent.type ?: return@filter false val widgetType = it.widgetContent.type ?: return@filter false

View File

@ -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.Event
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.util.JsonDict 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 import org.matrix.android.sdk.api.session.widgets.model.Widget
internal fun UserAccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> { internal fun AccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
return content.asSequence() return content.asSequence()
.mapNotNull { .mapNotNull {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -43,7 +43,7 @@ android {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation "androidx.fragment:fragment-ktx:1.3.3" implementation "androidx.fragment:fragment-ktx:1.3.4"
implementation 'androidx.exifinterface:exifinterface:1.3.2' implementation 'androidx.exifinterface:exifinterface:1.3.2'
// Log // Log

View File

@ -0,0 +1 @@
VoIP: support for virtual rooms

View File

@ -296,13 +296,13 @@ android {
dependencies { dependencies {
def epoxy_version = '4.6.1' def epoxy_version = '4.6.1'
def fragment_version = '1.3.3' def fragment_version = '1.3.4'
def arrow_version = "0.8.2" def arrow_version = "0.8.2"
def markwon_version = '4.1.2' def markwon_version = '4.1.2'
def big_image_viewer_version = '1.8.0' def big_image_viewer_version = '1.8.0'
def glide_version = '4.12.0' def glide_version = '4.12.0'
def moshi_version = '1.12.0' def moshi_version = '1.12.0'
def daggerVersion = '2.35.1' def daggerVersion = '2.36'
def autofill_version = "1.1.0" def autofill_version = "1.1.0"
def work_version = '2.5.0' def work_version = '2.5.0'
def arch_version = '2.1.0' def arch_version = '2.1.0'
@ -331,7 +331,7 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.sharetarget:sharetarget:1.1.0" implementation "androidx.sharetarget:sharetarget:1.1.0"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.5.0'
implementation "androidx.media:media:1.3.1" implementation "androidx.media:media:1.3.1"
implementation "org.threeten:threetenbp:1.4.0:no-tzdb" implementation "org.threeten:threetenbp:1.4.0:no-tzdb"

View File

@ -104,7 +104,7 @@ class DefaultErrorFormatter @Inject constructor(
} }
} }
} }
is Failure.OtherServerError -> { is Failure.OtherServerError -> {
when (throwable.httpCode) { when (throwable.httpCode) {
HttpURLConnection.HTTP_NOT_FOUND -> HttpURLConnection.HTTP_NOT_FOUND ->
// homeserver not found // homeserver not found
@ -116,9 +116,9 @@ class DefaultErrorFormatter @Inject constructor(
throwable.localizedMessage throwable.localizedMessage
} }
} }
is DialPadLookup.Failure -> is DialPadLookup.Failure ->
stringProvider.getString(R.string.call_dial_pad_lookup_error) stringProvider.getString(R.string.call_dial_pad_lookup_error)
else -> throwable.localizedMessage else -> throwable.localizedMessage
} }
?: stringProvider.getString(R.string.unknown_error) ?: stringProvider.getString(R.string.unknown_error)
} }

View File

@ -32,6 +32,7 @@ fun Session.configureAndStart(context: Context) {
setFilter(FilterService.FilterPreset.ElementFilter) setFilter(FilterService.FilterPreset.ElementFilter)
startSyncing(context) startSyncing(context)
refreshPushers() refreshPushers()
context.vectorComponent().webRtcCallManager().checkForProtocolsSupportIfNeeded()
} }
fun Session.startSyncing(context: Context) { fun Session.startSyncing(context: Context) {

View File

@ -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.telecom.CallConnection
import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager 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.home.AvatarRenderer
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.popup.IncomingCallAlert import im.vector.app.features.popup.IncomingCallAlert
import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.PopupAlertManager
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber import timber.log.Timber
/** /**
@ -176,7 +176,7 @@ class CallService : VectorService() {
} }
alertManager.postVectorAlert(incomingCallAlert) alertManager.postVectorAlert(incomingCallAlert)
val notification = notificationUtils.buildIncomingCallNotification( val notification = notificationUtils.buildIncomingCallNotification(
mxCall = call.mxCall, call = call,
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId, title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
fromBg = fromBg fromBg = fromBg
) )
@ -207,7 +207,7 @@ class CallService : VectorService() {
private fun showCallScreen(call: WebRtcCall, mode: String) { private fun showCallScreen(call: WebRtcCall, mode: String) {
val intent = VectorCallActivity.newIntent( val intent = VectorCallActivity.newIntent(
context = this, context = this,
mxCall = call.mxCall, call = call,
mode = mode mode = mode
) )
startActivity(intent) startActivity(intent)
@ -221,7 +221,7 @@ class CallService : VectorService() {
val opponentMatrixItem = getOpponentMatrixItem(call) val opponentMatrixItem = getOpponentMatrixItem(call)
Timber.v("displayOutgoingCallNotification : display the dedicated notification") Timber.v("displayOutgoingCallNotification : display the dedicated notification")
val notification = notificationUtils.buildOutgoingRingingCallNotification( val notification = notificationUtils.buildOutgoingRingingCallNotification(
mxCall = call.mxCall, call = call,
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
) )
if (knownCalls.isEmpty()) { if (knownCalls.isEmpty()) {
@ -244,7 +244,7 @@ class CallService : VectorService() {
val opponentMatrixItem = getOpponentMatrixItem(call) val opponentMatrixItem = getOpponentMatrixItem(call)
alertManager.cancelAlert(callId) alertManager.cancelAlert(callId)
val notification = notificationUtils.buildPendingCallNotification( val notification = notificationUtils.buildPendingCallNotification(
mxCall = call.mxCall, call = call,
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
) )
if (knownCalls.isEmpty()) { if (knownCalls.isEmpty()) {
@ -275,7 +275,9 @@ class CallService : VectorService() {
} }
private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? { private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? {
return vectorComponent().currentSession().getUser(call.mxCall.opponentUserId)?.toMatrixItem() return vectorComponent().activeSessionHolder().getSafeActiveSession()?.let {
call.getOpponentAsMatrixItem(it)
}
} }
companion object { companion object {

View File

@ -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)
}
}

View File

@ -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.CallDialPadBottomSheet
import im.vector.app.features.call.dialpad.DialPadFragment import im.vector.app.features.call.dialpad.DialPadFragment
import im.vector.app.features.call.utils.EglUtils 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.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
@ -54,7 +55,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.orFalse 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.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.MxPeerConnectionState
import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.webrtc.EglBase import org.webrtc.EglBase
@ -64,7 +64,7 @@ import javax.inject.Inject
@Parcelize @Parcelize
data class CallArgs( data class CallArgs(
val roomId: String, val signalingRoomId: String,
val callId: String, val callId: String,
val participantUserId: String, val participantUserId: String,
val isIncomingCall: Boolean, val isIncomingCall: Boolean,
@ -287,7 +287,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
views.otherKnownCallAvatarView.setOnClickListener { views.otherKnownCallAvatarView.setOnClickListener {
withState(callViewModel) { withState(callViewModel) {
val otherCall = callManager.getCallById(it.otherKnownCallInfo?.callId ?: "") ?: return@withState val otherCall = callManager.getCallById(it.otherKnownCallInfo?.callId ?: "") ?: return@withState
startActivity(newIntent(this, otherCall.mxCall, null)) startActivity(newIntent(this, otherCall, null))
finish() finish()
} }
} }
@ -375,18 +375,18 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
const val INCOMING_RINGING = "INCOMING_RINGING" const val INCOMING_RINGING = "INCOMING_RINGING"
const val INCOMING_ACCEPT = "INCOMING_ACCEPT" 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 { return Intent(context, VectorCallActivity::class.java).apply {
// what could be the best flags? // what could be the best flags?
flags = Intent.FLAG_ACTIVITY_NEW_TASK 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) putExtra(EXTRA_MODE, mode)
} }
} }
fun newIntent(context: Context, fun newIntent(context: Context,
callId: String, callId: String,
roomId: String, signalingRoomId: String,
otherUserId: String, otherUserId: String,
isIncomingCall: Boolean, isIncomingCall: Boolean,
isVideoCall: Boolean, isVideoCall: Boolean,
@ -394,7 +394,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
return Intent(context, VectorCallActivity::class.java).apply { return Intent(context, VectorCallActivity::class.java).apply {
// what could be the best flags? // what could be the best flags?
flags = FLAG_ACTIVITY_CLEAR_TOP 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) putExtra(EXTRA_MODE, mode)
} }
} }
@ -421,7 +421,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
} }
override fun returnToChat() { override fun returnToChat() {
val args = RoomDetailArgs(callArgs.roomId) val args = RoomDetailArgs(callArgs.signalingRoomId)
val intent = RoomDetailActivity.newIntent(this, args).apply { val intent = RoomDetailActivity.newIntent(this, args).apply {
flags = FLAG_ACTIVITY_CLEAR_TOP flags = FLAG_ACTIVITY_CLEAR_TOP
} }

View File

@ -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.audio.CallAudioManager
import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.call.webrtc.getOpponentAsMatrixItem
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -123,7 +124,7 @@ class VectorCallViewModel @AssistedInject constructor(
private fun computeTransfereeNameIfAny(call: MxCall): Optional<String> { private fun computeTransfereeNameIfAny(call: MxCall): Optional<String> {
val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return Optional.empty() val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return Optional.empty()
val transfereeRoom = session.getRoomSummary(transfereeCall.roomId) val transfereeRoom = session.getRoomSummary(transfereeCall.nativeRoomId)
val transfereeName = transfereeRoom?.displayName ?: "Unknown person" val transfereeName = transfereeRoom?.displayName ?: "Unknown person"
return Optional.from(transfereeName) return Optional.from(transfereeName)
} }
@ -162,7 +163,7 @@ class VectorCallViewModel @AssistedInject constructor(
if (otherCall == null) { if (otherCall == null) {
copy(otherKnownCallInfo = null) copy(otherKnownCallInfo = null)
} else { } else {
val otherUserItem: MatrixItem? = session.getUser(otherCall.mxCall.opponentUserId)?.toMatrixItem() val otherUserItem = otherCall.getOpponentAsMatrixItem(session)
copy(otherKnownCallInfo = VectorCallViewState.CallInfo(otherCall.callId, otherUserItem)) copy(otherKnownCallInfo = VectorCallViewState.CallInfo(otherCall.callId, otherUserItem))
} }
} }
@ -177,7 +178,7 @@ class VectorCallViewModel @AssistedInject constructor(
} else { } else {
call = webRtcCall call = webRtcCall
callManager.addCurrentCallListener(currentCallListener) callManager.addCurrentCallListener(currentCallListener)
val item: MatrixItem? = session.getUser(webRtcCall.mxCall.opponentUserId)?.toMatrixItem() val item = webRtcCall.getOpponentAsMatrixItem(session)
webRtcCall.addListener(callListener) webRtcCall.addListener(callListener)
val currentSoundDevice = callManager.audioManager.selectedDevice val currentSoundDevice = callManager.audioManager.selectedDevice
if (currentSoundDevice == CallAudioManager.Device.PHONE) { if (currentSoundDevice == CallAudioManager.Device.PHONE) {

View File

@ -53,7 +53,7 @@ data class VectorCallViewState(
constructor(callArgs: CallArgs): this( constructor(callArgs: CallArgs): this(
callId = callArgs.callId, callId = callArgs.callId,
roomId = callArgs.roomId, roomId = callArgs.signalingRoomId,
isVideoCall = callArgs.isVideoCall isVideoCall = callArgs.isVideoCall
) )
} }

View File

@ -16,30 +16,24 @@
package im.vector.app.features.call.dialpad 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.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper import im.vector.app.features.createdirect.DirectRoomHelper
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import java.lang.IllegalStateException
import javax.inject.Inject import javax.inject.Inject
class DialPadLookup @Inject constructor( class DialPadLookup @Inject constructor(
private val session: Session, private val session: Session,
private val directRoomHelper: DirectRoomHelper, private val webRtcCallManager: WebRtcCallManager,
private val callManager: WebRtcCallManager private val directRoomHelper: DirectRoomHelper
) { ) {
class Failure : Throwable() class Failure : Throwable()
data class Result(val userId: String, val roomId: String) data class Result(val userId: String, val roomId: String)
suspend fun lookupPhoneNumber(phoneNumber: String): Result { suspend fun lookupPhoneNumber(phoneNumber: String): Result {
val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure() val thirdPartyUser = session.pstnLookup(phoneNumber, webRtcCallManager.supportedPSTNProtocol).firstOrNull() ?: throw IllegalStateException()
val thirdPartyUser = tryOrNull {
session.thirdPartyService().getThirdPartyUser(
protocol = supportedProtocolKey,
fields = mapOf("m.id.phone" to phoneNumber)
).firstOrNull()
} ?: throw Failure()
val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId) val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId)
return Result(userId = thirdPartyUser.userId, roomId = roomId) return Result(userId = thirdPartyUser.userId, roomId = roomId)
} }

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.call.lookup
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
const val PROTOCOL_PSTN_PREFIXED = "im.vector.protocol.pstn"
const val PROTOCOL_PSTN = "m.protocol.pstn"
const val PROTOCOL_SIP_NATIVE = "im.vector.protocol.sip_native"
const val PROTOCOL_SIP_VIRTUAL = "im.vector.protocol.sip_virtual"
class CallProtocolsChecker(private val session: Session) {
interface Listener {
fun onPSTNSupportUpdated() = Unit
fun onVirtualRoomSupportUpdated() = Unit
}
private val alreadyChecked = AtomicBoolean(false)
private val checking = AtomicBoolean(false)
private val listeners = mutableListOf<Listener>()
fun addListener(listener: Listener) {
listeners.add(listener)
}
fun removeListener(listener: Listener) {
listeners.remove(listener)
}
var supportedPSTNProtocol: String? = null
private set
var supportVirtualRooms: Boolean = false
private set
fun checkProtocols() {
session.coroutineScope.launch {
checkThirdPartyProtocols()
}
}
suspend fun awaitCheckProtocols() {
checkThirdPartyProtocols()
}
private suspend fun checkThirdPartyProtocols() {
if (alreadyChecked.get()) return
if (!checking.compareAndSet(false, true)) return
try {
val protocols = getThirdPartyProtocols(3)
alreadyChecked.set(true)
checking.set(false)
supportedPSTNProtocol = protocols.extractPSTN()
if (supportedPSTNProtocol != null) {
listeners.forEach {
tryOrNull { it.onPSTNSupportUpdated() }
}
}
supportVirtualRooms = protocols.supportsVirtualRooms()
if (supportVirtualRooms) {
listeners.forEach {
tryOrNull { it.onVirtualRoomSupportUpdated() }
}
}
} catch (failure: Throwable) {
Timber.v("Fail to get third party protocols, will check again next time.")
}
}
private fun Map<String, ThirdPartyProtocol>.extractPSTN(): String? {
return when {
containsKey(PROTOCOL_PSTN_PREFIXED) -> PROTOCOL_PSTN_PREFIXED
containsKey(PROTOCOL_PSTN) -> PROTOCOL_PSTN
else -> null
}
}
private fun Map<String, ThirdPartyProtocol>.supportsVirtualRooms(): Boolean {
return containsKey(PROTOCOL_SIP_VIRTUAL) && containsKey(PROTOCOL_SIP_NATIVE)
}
private suspend fun getThirdPartyProtocols(maxTries: Int): Map<String, ThirdPartyProtocol> {
return try {
session.thirdPartyService().getThirdPartyProtocols()
} catch (failure: Throwable) {
if (maxTries == 1) {
throw failure
} else {
// Wait for 10s before trying again
delay(10_000L)
return getThirdPartyProtocols(maxTries - 1)
}
}
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.call.lookup
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
class CallUserMapper(private val session: Session, private val protocolsChecker: CallProtocolsChecker) {
fun nativeRoomForVirtualRoom(roomId: String): String? {
val virtualRoom = session.getRoom(roomId) ?: return null
val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM)
return virtualRoomEvent?.content?.toModel<RoomVirtualContent>()?.nativeRoomId
}
suspend fun getOrCreateVirtualRoomForRoom(roomId: String, opponentUserId: String): String? {
protocolsChecker.awaitCheckProtocols()
if (!protocolsChecker.supportVirtualRooms) return null
val virtualUser = userToVirtualUser(opponentUserId) ?: return null
val virtualRoomId = tryOrNull {
ensureVirtualRoomExists(virtualUser, roomId)
} ?: return null
session.getRoom(virtualRoomId)?.markVirtual(roomId)
return virtualRoomId
}
suspend fun onNewInvitedRoom(invitedRoomId: String) {
protocolsChecker.awaitCheckProtocols()
if (!protocolsChecker.supportVirtualRooms) return
val invitedRoom = session.getRoom(invitedRoomId) ?: return
val inviterId = invitedRoom.roomSummary()?.inviterId ?: return
val nativeLookup = session.sipNativeLookup(inviterId).firstOrNull() ?: return
if (nativeLookup.fields.containsKey("is_virtual")) {
val nativeUser = nativeLookup.userId
val nativeRoomId = session.getExistingDirectRoomWithUser(nativeUser)
if (nativeRoomId != null) {
// It's a virtual room with a matching native room, so set the room account data. This
// will make sure we know where how to map calls and also allow us know not to display
// it in the future.
invitedRoom.markVirtual(nativeRoomId)
// also auto-join the virtual room if we have a matching native room
// (possibly we should only join if we've also joined the native room, then we'd also have
// to make sure we joined virtual rooms on joining a native one)
session.joinRoom(invitedRoomId)
}
}
}
private suspend fun userToVirtualUser(userId: String): String? {
val results = session.sipVirtualLookup(userId)
return results.firstOrNull()?.userId
}
private suspend fun Room.markVirtual(nativeRoomId: String) {
val virtualRoomContent = RoomVirtualContent(nativeRoomId = nativeRoomId)
updateAccountData(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM, virtualRoomContent.toContent())
}
private suspend fun ensureVirtualRoomExists(userId: String, nativeRoomId: String): String {
val existingDMRoom = tryOrNull { session.getExistingDirectRoomWithUser(userId) }
val roomId: String
if (existingDMRoom != null) {
roomId = existingDMRoom
} else {
val roomParams = CreateRoomParams().apply {
invitedUserIds.add(userId)
setDirectMessage()
creationContent[RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM] = nativeRoomId
}
roomId = session.createRoom(roomParams)
}
return roomId
}
}

View File

@ -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
)

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.call.lookup
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser
suspend fun Session.pstnLookup(phoneNumber: String, protocol: String?): List<ThirdPartyUser> {
if (protocol == null) return emptyList()
return tryOrNull {
thirdPartyService().getThirdPartyUser(
protocol = protocol,
fields = mapOf("m.id.phone" to phoneNumber)
)
}.orEmpty()
}
suspend fun Session.sipVirtualLookup(nativeMxid: String): List<ThirdPartyUser> {
return tryOrNull {
thirdPartyService().getThirdPartyUser(
protocol = PROTOCOL_SIP_VIRTUAL,
fields = mapOf("native_mxid" to nativeMxid)
)
}.orEmpty()
}
suspend fun Session.sipNativeLookup(virtualMxid: String): List<ThirdPartyUser> {
return tryOrNull {
thirdPartyService().getThirdPartyUser(
protocol = PROTOCOL_SIP_NATIVE,
fields = mapOf("virtual_mxid" to virtualMxid)
)
}.orEmpty()
}

View File

@ -89,7 +89,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
if (action.consultFirst) { if (action.consultFirst) {
val dmRoomId = directRoomHelper.ensureDMExists(action.selectedUserId) val dmRoomId = directRoomHelper.ensureDMExists(action.selectedUserId)
callManager.startOutgoingCall( callManager.startOutgoingCall(
signalingRoomId = dmRoomId, nativeRoomId = dmRoomId,
otherUserId = action.selectedUserId, otherUserId = action.selectedUserId,
isVideoCall = call?.mxCall?.isVideoCall.orFalse(), isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
transferee = call transferee = call
@ -111,7 +111,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber) val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber)
if (action.consultFirst) { if (action.consultFirst) {
callManager.startOutgoingCall( callManager.startOutgoingCall(
signalingRoomId = result.roomId, nativeRoomId = result.roomId,
otherUserId = result.userId, otherUserId = result.userId,
isVideoCall = call?.mxCall?.isVideoCall.orFalse(), isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
transferee = call transferee = call

View File

@ -87,6 +87,8 @@ private const val VIDEO_TRACK_ID = "ARDAMSv0"
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints() private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
class WebRtcCall(val mxCall: MxCall, 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 rootEglBase: EglBase?,
private val context: Context, private val context: Context,
private val dispatcher: CoroutineContext, private val dispatcher: CoroutineContext,
@ -117,7 +119,8 @@ class WebRtcCall(val mxCall: MxCall,
} }
val callId = mxCall.callId 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 peerConnection: PeerConnection? = null
private var localAudioSource: AudioSource? = null private var localAudioSource: AudioSource? = null
@ -414,6 +417,7 @@ class WebRtcCall(val mxCall: MxCall,
peerConnection?.awaitSetRemoteDescription(offerSdp) peerConnection?.awaitSetRemoteDescription(offerSdp)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.v("Failure putting remote description") Timber.v("Failure putting remote description")
endCall(true, CallHangupContent.Reason.UNKWOWN_ERROR)
return@withContext return@withContext
} }
// 2) Access camera + microphone, create local stream // 2) Access camera + microphone, create local stream

View File

@ -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()
}
}

View File

@ -24,15 +24,18 @@ import im.vector.app.ActiveSessionDataSource
import im.vector.app.core.services.CallService import im.vector.app.core.services.CallService
import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.audio.CallAudioManager 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.utils.EglUtils
import im.vector.app.features.call.vectorCallService
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.asCoroutineDispatcher 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.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session 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.CallListener
import org.matrix.android.sdk.api.session.call.CallState 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.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.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
@ -64,8 +67,11 @@ class WebRtcCallManager @Inject constructor(
private val currentSession: Session? private val currentSession: Session?
get() = activeSessionDataSource.currentValue?.orNull() get() = activeSessionDataSource.currentValue?.orNull()
private val pstnProtocolChecker: PSTNProtocolChecker? private val protocolsChecker: CallProtocolsChecker?
get() = currentSession?.callSignalingService()?.getPSTNProtocolChecker() get() = currentSession?.vectorCallService?.protocolChecker
private val callUserMapper: CallUserMapper?
get() = currentSession?.vectorCallService?.userMapper
interface CurrentCallListener { interface CurrentCallListener {
fun onCurrentCallChange(call: WebRtcCall?) {} fun onCurrentCallChange(call: WebRtcCall?) {}
@ -73,17 +79,20 @@ class WebRtcCallManager @Inject constructor(
} }
val supportedPSTNProtocol: String? val supportedPSTNProtocol: String?
get() = pstnProtocolChecker?.supportedPSTNProtocol get() = protocolsChecker?.supportedPSTNProtocol
val supportsPSTNProtocol: Boolean val supportsPSTNProtocol: Boolean
get() = supportedPSTNProtocol != null get() = supportedPSTNProtocol != null
fun addPstnSupportListener(listener: PSTNProtocolChecker.Listener) { val supportsVirtualRooms: Boolean
pstnProtocolChecker?.addListener(listener) get() = protocolsChecker?.supportVirtualRooms.orFalse()
fun addProtocolsCheckerListener(listener: CallProtocolsChecker.Listener) {
protocolsChecker?.addListener(listener)
} }
fun removePstnSupportListener(listener: PSTNProtocolChecker.Listener) { fun removeProtocolsCheckerListener(listener: CallProtocolsChecker.Listener) {
pstnProtocolChecker?.removeListener(listener) protocolsChecker?.removeListener(listener)
} }
private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>() private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>()
@ -162,8 +171,8 @@ class WebRtcCallManager @Inject constructor(
return callsByCallId.values.toList() return callsByCallId.values.toList()
} }
fun checkForPSTNSupportIfNeeded() { fun checkForProtocolsSupportIfNeeded() {
pstnProtocolChecker?.checkForPSTNSupportIfNeeded() protocolsChecker?.checkProtocols()
} }
/** /**
@ -226,7 +235,8 @@ class WebRtcCallManager @Inject constructor(
Timber.v("On call ended for unknown call $callId") Timber.v("On call ended for unknown call $callId")
} }
CallService.onCallTerminated(context, callId) CallService.onCallTerminated(context, callId)
callsByRoomId[webRtcCall.roomId]?.remove(webRtcCall) callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall)
callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall)
transferees.remove(callId) transferees.remove(callId)
if (getCurrentCall()?.callId == callId) { if (getCurrentCall()?.callId == callId) {
val otherCall = getCalls().lastOrNull() val otherCall = getCalls().lastOrNull()
@ -254,9 +264,10 @@ class WebRtcCallManager @Inject constructor(
} }
} }
fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean, transferee: WebRtcCall? = null) { suspend fun startOutgoingCall(nativeRoomId: String, otherUserId: String, isVideoCall: Boolean, transferee: WebRtcCall? = null) {
val signalingRoomId = callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall") 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") Timber.w("## VOIP you already have a call in this room")
return return
} }
@ -270,7 +281,7 @@ class WebRtcCallManager @Inject constructor(
} }
getCurrentCall()?.updateRemoteOnHold(onHold = true) getCurrentCall()?.updateRemoteOnHold(onHold = true)
val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
val webRtcCall = createWebRtcCall(mxCall) val webRtcCall = createWebRtcCall(mxCall, nativeRoomId)
currentCall.setAndNotify(webRtcCall) currentCall.setAndNotify(webRtcCall)
if(transferee != null){ if(transferee != null){
transferees[webRtcCall.callId] = transferee transferees[webRtcCall.callId] = transferee
@ -280,7 +291,7 @@ class WebRtcCallManager @Inject constructor(
callId = mxCall.callId) callId = mxCall.callId)
// start the activity now // 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) { override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) {
@ -292,9 +303,10 @@ class WebRtcCallManager @Inject constructor(
call.onCallIceCandidateReceived(iceCandidatesContent) call.onCallIceCandidateReceived(iceCandidatesContent)
} }
private fun createWebRtcCall(mxCall: MxCall): WebRtcCall { private fun createWebRtcCall(mxCall: MxCall, nativeRoomId: String): WebRtcCall {
val webRtcCall = WebRtcCall( val webRtcCall = WebRtcCall(
mxCall = mxCall, mxCall = mxCall,
nativeRoomId = nativeRoomId,
rootEglBase = rootEglBase, rootEglBase = rootEglBase,
context = context, context = context,
dispatcher = dispatcher, dispatcher = dispatcher,
@ -308,6 +320,8 @@ class WebRtcCallManager @Inject constructor(
) )
advertisedCalls.add(mxCall.callId) advertisedCalls.add(mxCall.callId)
callsByCallId[mxCall.callId] = webRtcCall callsByCallId[mxCall.callId] = webRtcCall
callsByRoomId.getOrPut(nativeRoomId) { ArrayList(1) }
.add(webRtcCall)
callsByRoomId.getOrPut(mxCall.roomId) { ArrayList(1) } callsByRoomId.getOrPut(mxCall.roomId) { ArrayList(1) }
.add(webRtcCall) .add(webRtcCall)
if (getCurrentCall() == null) { if (getCurrentCall() == null) {
@ -317,12 +331,13 @@ class WebRtcCallManager @Inject constructor(
} }
fun endCallForRoom(roomId: String, originatedByMe: Boolean = true) { 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) { override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) {
Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}") 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") Timber.w("## VOIP you already have a call in this room")
return return
} }
@ -331,7 +346,7 @@ class WebRtcCallManager @Inject constructor(
// Just ignore, maybe we could answer from other session? // Just ignore, maybe we could answer from other session?
return return
} }
createWebRtcCall(mxCall).apply { createWebRtcCall(mxCall, nativeRoomId).apply {
offerSdp = callInviteContent.offer offerSdp = callInviteContent.offer
} }
// Start background service with notification // Start background service with notification

View File

@ -252,7 +252,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
} }
private fun isBackupKeyInQuadS(): Boolean { private fun isBackupKeyInQuadS(): Boolean {
val sssBackupSecret = session.getAccountDataEvent(KEYBACKUP_SECRET_SSSS_NAME) val sssBackupSecret = session.userAccountDataService().getAccountDataEvent(KEYBACKUP_SECRET_SSSS_NAME)
?: return false ?: return false
// Some sanity ? // Some sanity ?

View File

@ -218,7 +218,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
args.requestedSecrets.forEach { args.requestedSecrets.forEach {
if (session.getAccountDataEvent(it) != null) { if (session.userAccountDataService().getAccountDataEvent(it) != null) {
val res = session.sharedSecretStorageService.getSecret( val res = session.sharedSecretStorageService.getSecret(
name = it, name = it,
keyId = keyInfo.id, keyId = keyInfo.id,
@ -287,7 +287,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
args.requestedSecrets.forEach { args.requestedSecrets.forEach {
if (session.getAccountDataEvent(it) != null) { if (session.userAccountDataService().getAccountDataEvent(it) != null) {
val res = session.sharedSecretStorageService.getSecret( val res = session.sharedSecretStorageService.getSecret(
name = it, name = it,
keyId = keyInfo.id, keyId = keyInfo.id,

View File

@ -409,7 +409,7 @@ class HomeDetailFragment @Inject constructor(
VectorCallActivity.newIntent( VectorCallActivity.newIntent(
context = requireContext(), context = requireContext(),
callId = call.callId, callId = call.callId,
roomId = call.mxCall.roomId, signalingRoomId = call.signalingRoomId,
otherUserId = call.mxCall.opponentUserId, otherUserId = call.mxCall.opponentUserId,
isIncomingCall = !call.mxCall.isOutgoing, isIncomingCall = !call.mxCall.isOutgoing,
isVideoCall = call.mxCall.isVideoCall, isVideoCall = call.mxCall.isVideoCall,

View File

@ -417,7 +417,7 @@ class RoomDetailFragment @Inject constructor(
private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) { private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) {
val intent = VectorCallActivity.newIntent( val intent = VectorCallActivity.newIntent(
context = vectorBaseActivity, context = vectorBaseActivity,
mxCall = event.call.mxCall, call = event.call,
mode = VectorCallActivity.INCOMING_ACCEPT mode = VectorCallActivity.INCOMING_ACCEPT
) )
startActivity(intent) startActivity(intent)
@ -2042,7 +2042,7 @@ class RoomDetailFragment @Inject constructor(
VectorCallActivity.newIntent( VectorCallActivity.newIntent(
context = requireContext(), context = requireContext(),
callId = call.callId, callId = call.callId,
roomId = call.roomId, signalingRoomId = call.signalingRoomId,
otherUserId = call.mxCall.opponentUserId, otherUserId = call.mxCall.opponentUserId,
isIncomingCall = !call.mxCall.isOutgoing, isIncomingCall = !call.mxCall.isOutgoing,
isVideoCall = call.mxCall.isVideoCall, isVideoCall = call.mxCall.isVideoCall,

View File

@ -40,6 +40,7 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.call.conference.JitsiService import im.vector.app.features.call.conference.JitsiService
import im.vector.app.features.call.dialpad.DialPadLookup 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.call.webrtc.WebRtcCallManager
import im.vector.app.features.command.CommandParser import im.vector.app.features.command.CommandParser
import im.vector.app.features.command.ParsedCommand 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.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session 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.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.LocalEcho
@ -121,7 +121,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val jitsiService: JitsiService, private val jitsiService: JitsiService,
timelineSettingsFactory: TimelineSettingsFactory timelineSettingsFactory: TimelineSettingsFactory
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
Timeline.Listener, ChatEffectManager.Delegate, PSTNProtocolChecker.Listener { Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
private val eventId = initialState.eventId private val eventId = initialState.eventId
@ -185,8 +185,8 @@ class RoomDetailViewModel @AssistedInject constructor(
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
tryOrNull { session.onRoomDisplayed(initialState.roomId) } tryOrNull { session.onRoomDisplayed(initialState.roomId) }
} }
callManager.addPstnSupportListener(this) callManager.addProtocolsCheckerListener(this)
callManager.checkForPSTNSupportIfNeeded() callManager.checkForProtocolsSupportIfNeeded()
chatEffectManager.delegate = this chatEffectManager.delegate = this
// Ensure to share the outbound session keys with all members // Ensure to share the outbound session keys with all members
@ -330,7 +330,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun handleStartCallWithPhoneNumber(action: RoomDetailAction.StartCallWithPhoneNumber) { private fun handleStartCallWithPhoneNumber(action: RoomDetailAction.StartCallWithPhoneNumber) {
viewModelScope.launch { viewModelScope.launch {
try { 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) callManager.startOutgoingCall(result.roomId, result.userId, action.videoCall)
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
@ -391,8 +391,10 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
private fun handleStartCall(action: RoomDetailAction.StartCall) { private fun handleStartCall(action: RoomDetailAction.StartCall) {
room.roomSummary()?.otherMemberIds?.firstOrNull()?.let { viewModelScope.launch {
callManager.startOutgoingCall(room.roomId, it, action.isVideo) room.roomSummary()?.otherMemberIds?.firstOrNull()?.let {
callManager.startOutgoingCall(room.roomId, it, action.isVideo)
}
} }
} }
@ -1508,7 +1510,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
chatEffectManager.delegate = null chatEffectManager.delegate = null
chatEffectManager.dispose() chatEffectManager.dispose()
callManager.removePstnSupportListener(this) callManager.removeProtocolsCheckerListener(this)
super.onCleared() super.onCleared()
} }
} }

View File

@ -103,7 +103,7 @@ class StartCallActionsHandler(
val currentCall = callManager.getCurrentCall() val currentCall = callManager.getCurrentCall()
if (currentCall != null) { if (currentCall != null) {
// resume existing if same room, if not prompt to kill and then restart new call? // resume existing if same room, if not prompt to kill and then restart new call?
if (currentCall.roomId == roomId) { if (currentCall.signalingRoomId == roomId) {
onTapToReturnToCall() onTapToReturnToCall()
} }
// else { // else {

View File

@ -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.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent 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.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.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -51,7 +51,7 @@ class CallItemFactory @Inject constructor(
if (event.root.eventId == null) return null if (event.root.eventId == null) return null
val roomId = event.roomId val roomId = event.roomId
val informationData = messageInformationDataFactory.create(params) val informationData = messageInformationDataFactory.create(params)
val callSignalingContent = event.getCallSignallingContent() ?: return null val callSignalingContent = event.getCallSignalingContent() ?: return null
val callId = callSignalingContent.callId ?: return null val callId = callSignalingContent.callId ?: return null
val call = callManager.getCallById(callId) val call = callManager.getCallById(callId)
val callKind = when { 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()) { return when (root.getClearType()) {
EventType.CALL_INVITE -> root.getClearContent().toModel<CallInviteContent>() EventType.CALL_INVITE -> root.getClearContent().toModel<CallInviteContent>()
EventType.CALL_HANGUP -> root.getClearContent().toModel<CallHangupContent>() EventType.CALL_HANGUP -> root.getClearContent().toModel<CallHangupContent>()

View File

@ -51,12 +51,12 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.startNotificationChannelSettingsIntent import im.vector.app.core.utils.startNotificationChannelSettingsIntent
import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.service.CallHeadsUpActionReceiver 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.HomeActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.home.room.detail.RoomDetailArgs
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver
import org.matrix.android.sdk.api.session.call.MxCall
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -287,7 +287,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
* @return the call notification. * @return the call notification.
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun buildIncomingCallNotification(mxCall: MxCall, fun buildIncomingCallNotification(call: WebRtcCall,
title: String, title: String,
fromBg: Boolean): Notification { fromBg: Boolean): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) 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) val builder = NotificationCompat.Builder(context, notificationChannel)
.setContentTitle(ensureTitleNotEmpty(title)) .setContentTitle(ensureTitleNotEmpty(title))
.apply { .apply {
if (mxCall.isVideoCall) { if (call.mxCall.isVideoCall) {
setContentText(stringProvider.getString(R.string.incoming_video_call)) setContentText(stringProvider.getString(R.string.incoming_video_call))
} else { } else {
setContentText(stringProvider.getString(R.string.incoming_voice_call)) setContentText(stringProvider.getString(R.string.incoming_voice_call))
@ -308,11 +308,11 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentIntent = VectorCallActivity.newIntent( val contentIntent = VectorCallActivity.newIntent(
context = context, context = context,
mxCall = mxCall, call = call,
mode = VectorCallActivity.INCOMING_RINGING mode = VectorCallActivity.INCOMING_RINGING
).apply { ).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP 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 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)) .addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(VectorCallActivity.newIntent( .addNextIntent(VectorCallActivity.newIntent(
context = context, context = context,
mxCall = mxCall, call = call,
mode = VectorCallActivity.INCOMING_ACCEPT) mode = VectorCallActivity.INCOMING_ACCEPT)
) )
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId) val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId)
builder.addAction( builder.addAction(
NotificationCompat.Action( NotificationCompat.Action(
@ -351,7 +351,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
return builder.build() return builder.build()
} }
fun buildOutgoingRingingCallNotification(mxCall: MxCall, fun buildOutgoingRingingCallNotification(call: WebRtcCall,
title: String): Notification { title: String): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) 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( val contentIntent = VectorCallActivity.newIntent(
context = context, context = context,
mxCall = mxCall, call = call,
mode = null).apply { mode = null).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP 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 contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId) val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId)
builder.addAction( builder.addAction(
NotificationCompat.Action( NotificationCompat.Action(
@ -397,12 +397,12 @@ class NotificationUtils @Inject constructor(private val context: Context,
* @return the call notification. * @return the call notification.
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun buildPendingCallNotification(mxCall: MxCall, fun buildPendingCallNotification(call: WebRtcCall,
title: String): Notification { title: String): Notification {
val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(ensureTitleNotEmpty(title)) .setContentTitle(ensureTitleNotEmpty(title))
.apply { .apply {
if (mxCall.isVideoCall) { if (call.mxCall.isVideoCall) {
setContentText(stringProvider.getString(R.string.video_call_in_progress)) setContentText(stringProvider.getString(R.string.video_call_in_progress))
} else { } else {
setContentText(stringProvider.getString(R.string.call_in_progress)) 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) .setSmallIcon(R.drawable.incoming_call_notification_transparent)
.setCategory(NotificationCompat.CATEGORY_CALL) .setCategory(NotificationCompat.CATEGORY_CALL)
val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId) val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId)
builder.addAction( builder.addAction(
NotificationCompat.Action( NotificationCompat.Action(
@ -422,7 +422,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentPendingIntent = TaskStackBuilder.create(context) val contentPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(HomeActivity.newIntent(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) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(contentPendingIntent) builder.setContentIntent(contentPendingIntent)

View File

@ -21,6 +21,8 @@ import androidx.lifecycle.MutableLiveData
import im.vector.app.core.extensions.postLiveEvent import im.vector.app.core.extensions.postLiveEvent
import im.vector.app.core.utils.LiveEvent import im.vector.app.core.utils.LiveEvent
import kotlinx.coroutines.cancelChildren 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.failure.GlobalError
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject import javax.inject.Inject
@ -37,6 +39,12 @@ class SessionListener @Inject constructor() : Session.Listener {
_globalErrorLiveData.postLiveEvent(globalError) _globalErrorLiveData.postLiveEvent(globalError)
} }
override fun onNewInvitedRoom(session: Session, roomId: String) {
session.coroutineScope.launch {
session.vectorCallService.userMapper.onNewInvitedRoom(roomId)
}
}
override fun onSessionStopped(session: Session) { override fun onSessionStopped(session: Session) {
session.coroutineScope.coroutineContext.cancelChildren() session.coroutineScope.coroutineContext.cancelChildren()
} }

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.session
import org.matrix.android.sdk.api.session.Session
import kotlin.reflect.KProperty
/**
* This is a simple hack for having some Session scope dependencies.
* Probably a temporary solution waiting for refactoring the Dagger management of Session.
* You should use it with an extension property :
val Session.myProperty: MyProperty by SessionScopedProperty {
init code
}
*
*/
class SessionScopedProperty<T : Any>(val initializer: (Session) -> T) {
private val propertyBySessionId = HashMap<String, T>()
private val sessionListener = object : Session.Listener {
override fun onSessionStopped(session: Session) {
synchronized(propertyBySessionId) {
session.removeListener(this)
propertyBySessionId.remove(session.sessionId)
}
}
}
operator fun getValue(thisRef: Session, property: KProperty<*>): T = synchronized(propertyBySessionId) {
propertyBySessionId.getOrPut(thisRef.sessionId) {
thisRef.addListener(sessionListener)
initializer(thisRef)
}
}
}

View File

@ -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.genericFooterItem
import im.vector.app.core.ui.list.genericItemWithValue import im.vector.app.core.ui.list.genericItemWithValue
import im.vector.app.core.utils.DebouncedClickListener 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 import javax.inject.Inject
class AccountDataEpoxyController @Inject constructor( class AccountDataEpoxyController @Inject constructor(
@ -35,8 +35,8 @@ class AccountDataEpoxyController @Inject constructor(
) : TypedEpoxyController<AccountDataViewState>() { ) : TypedEpoxyController<AccountDataViewState>() {
interface InteractionListener { interface InteractionListener {
fun didTap(data: UserAccountDataEvent) fun didTap(data: AccountDataEvent)
fun didLongTap(data: UserAccountDataEvent) fun didLongTap(data: AccountDataEvent)
} }
var interactionListener: InteractionListener? = null var interactionListener: InteractionListener? = null

View File

@ -35,7 +35,7 @@ import im.vector.app.core.utils.createJSonViewerStyleProvider
import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.databinding.FragmentGenericRecyclerBinding
import org.billcarsonfr.jsonviewer.JSonViewerDialog 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 org.matrix.android.sdk.internal.di.MoshiProvider
import javax.inject.Inject import javax.inject.Inject
@ -73,9 +73,9 @@ class AccountDataFragment @Inject constructor(
super.onDestroyView() super.onDestroyView()
} }
override fun didTap(data: UserAccountDataEvent) { override fun didTap(data: AccountDataEvent) {
val jsonString = MoshiProvider.providesMoshi() val jsonString = MoshiProvider.providesMoshi()
.adapter(UserAccountDataEvent::class.java) .adapter(AccountDataEvent::class.java)
.toJson(data) .toJson(data)
JSonViewerDialog.newInstance( JSonViewerDialog.newInstance(
jsonString, jsonString,
@ -84,7 +84,7 @@ class AccountDataFragment @Inject constructor(
).show(childFragmentManager, "JSON_VIEWER") ).show(childFragmentManager, "JSON_VIEWER")
} }
override fun didLongTap(data: UserAccountDataEvent) { override fun didLongTap(data: AccountDataEvent) {
AlertDialog.Builder(requireActivity()) AlertDialog.Builder(requireActivity())
.setTitle(R.string.delete) .setTitle(R.string.delete)
.setMessage(getString(R.string.delete_account_data_warning, data.type)) .setMessage(getString(R.string.delete_account_data_warning, data.type))

View File

@ -31,11 +31,11 @@ import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session 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 import org.matrix.android.sdk.rx.rx
data class AccountDataViewState( data class AccountDataViewState(
val accountData: Async<List<UserAccountDataEvent>> = Uninitialized val accountData: Async<List<AccountDataEvent>> = Uninitialized
) : MvRxState ) : MvRxState
class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState, class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState,
@ -43,7 +43,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A
: VectorViewModel<AccountDataViewState, AccountDataAction, EmptyViewEvents>(initialState) { : VectorViewModel<AccountDataViewState, AccountDataAction, EmptyViewEvents>(initialState) {
init { init {
session.rx().liveAccountData(emptySet()) session.rx().liveUserAccountData(emptySet())
.execute { .execute {
copy(accountData = it) copy(accountData = it)
} }
@ -57,7 +57,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A
private fun handleDeleteAccountData(action: AccountDataAction.DeleteAccountData) { private fun handleDeleteAccountData(action: AccountDataAction.DeleteAccountData) {
viewModelScope.launch { viewModelScope.launch {
session.updateAccountData(action.type, emptyMap()) session.userAccountDataService().updateAccountData(action.type, emptyMap())
} }
} }

View File

@ -284,7 +284,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
) )
) )
launchWidgetAPIAction(widgetPostAPIMediator, eventData) { launchWidgetAPIAction(widgetPostAPIMediator, eventData) {
session.updateAccountData( session.userAccountDataService().updateAccountData(
type = UserAccountDataTypes.TYPE_WIDGETS, type = UserAccountDataTypes.TYPE_WIDGETS,
content = addUserWidgetBody content = addUserWidgetBody
) )

View File

@ -35,7 +35,7 @@ import io.reactivex.functions.Function4
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session 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.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME 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 keysBackupState.value = session.cryptoService().keysBackupService().state
Observable.combineLatest<List<UserAccountDataEvent>, Optional<MXCrossSigningInfo>, KeysBackupState, Optional<PrivateKeysInfo>, BannerState>( Observable.combineLatest<List<AccountDataEvent>, Optional<MXCrossSigningInfo>, KeysBackupState, Optional<PrivateKeysInfo>, BannerState>(
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)),
session.rx().liveCrossSigningInfo(session.myUserId), session.rx().liveCrossSigningInfo(session.myUserId),
keyBackupPublishSubject, keyBackupPublishSubject,
session.rx().liveCrossSigningPrivateKeys(), session.rx().liveCrossSigningPrivateKeys(),

View File

@ -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 { .map {
session.sharedSecretStorageService.isRecoverySetup() session.sharedSecretStorageService.isRecoverySetup()
} }