mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-17 20:40:07 +03:00
Merge branch 'release/1.1.9' into main
This commit is contained in:
commit
7f96749d63
187 changed files with 2678 additions and 891 deletions
19
CHANGES.md
19
CHANGES.md
|
@ -1,3 +1,22 @@
|
|||
Changes in Element v1.1.9 (2021-06-02)
|
||||
======================================
|
||||
|
||||
Features ✨:
|
||||
- Upgrade Olm dependency to 3.2.4
|
||||
- Allow user to add custom "network" in room search (#1458)
|
||||
- Add Gitter.im as a default in the Change Network menu (#3196)
|
||||
- VoIP: support for virtual rooms (#3355)
|
||||
- Compress thumbnail: change Jpeg quality from 100 to 80 (#3396)
|
||||
- Inconsistent usage of the term homeserver in Settings (#3404)
|
||||
- VoIP: support attended transfer (#3420)
|
||||
- /snow -> /snowfall and update wording (iso Element Web) (#3430)
|
||||
|
||||
Bugfixes 🐛:
|
||||
- Fix | On Android it seems to be impossible to view the complete description of a Space (without dev tools) (#3401)
|
||||
- Fix | Suggest Rooms, Show a detailed view of the room on click (#3406)
|
||||
- Fix app crashing when signing out (#3424)
|
||||
- Switch to stable endpoint/fields for MSC2858 (#3442)
|
||||
|
||||
Changes in Element 1.1.8 (2021-05-25)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ dependencies {
|
|||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
|
||||
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.recyclerview:recyclerview:1.2.0"
|
||||
|
||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40101090.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40101090.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: add support for gitter.im network.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.9
|
|
@ -24,7 +24,7 @@ import kotlinx.coroutines.rx2.rxSingle
|
|||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
|
@ -177,10 +177,10 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
}
|
||||
|
||||
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
|
||||
return session.getLiveAccountDataEvents(types).asObservable()
|
||||
fun liveUserAccountData(types: Set<String>): Observable<List<AccountDataEvent>> {
|
||||
return session.userAccountDataService().getLiveAccountDataEvents(types).asObservable()
|
||||
.startWithCallable {
|
||||
session.getAccountDataEvents(types)
|
||||
session.userAccountDataService().getAccountDataEvents(types)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,8 +201,8 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
|
||||
fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> {
|
||||
return Observable.combineLatest<List<UserAccountDataEvent>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
|
||||
liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
|
||||
return Observable.combineLatest<List<AccountDataEvent>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
|
||||
liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
|
||||
liveCrossSigningInfo(session.myUserId),
|
||||
liveCrossSigningPrivateKeys(),
|
||||
Function3 { _, crossSigningInfo, pInfo ->
|
||||
|
|
|
@ -112,7 +112,7 @@ dependencies {
|
|||
def lifecycle_version = '2.2.0'
|
||||
def arch_version = '2.1.0'
|
||||
def markwon_version = '3.1.0'
|
||||
def daggerVersion = '2.35.1'
|
||||
def daggerVersion = '2.36'
|
||||
def work_version = '2.5.0'
|
||||
def retrofit_version = '2.9.0'
|
||||
|
||||
|
@ -121,7 +121,7 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.3.0"
|
||||
implementation "androidx.core:core-ktx:1.3.2"
|
||||
implementation "androidx.core:core-ktx:1.5.0"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
@ -155,7 +155,7 @@ dependencies {
|
|||
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
|
||||
|
||||
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
|
||||
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.2'
|
||||
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4'
|
||||
|
||||
// DI
|
||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||
|
@ -169,7 +169,7 @@ dependencies {
|
|||
implementation 'com.otaliastudios:transcoder:0.10.3'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.23'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.matrix.android.sdk.common.TestConstants
|
|||
import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -73,12 +73,12 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
// Assert Account data is updated
|
||||
val accountDataLock = CountDownLatch(1)
|
||||
var accountData: UserAccountDataEvent? = null
|
||||
var accountData: AccountDataEvent? = null
|
||||
|
||||
val liveAccountData = runBlocking(Dispatchers.Main) {
|
||||
aliceSession.getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
|
||||
aliceSession.userAccountDataService().getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
|
||||
}
|
||||
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
|
||||
val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
|
||||
if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
|
||||
accountData = t.getOrNull()
|
||||
accountDataLock.countDown()
|
||||
|
@ -100,13 +100,13 @@ class QuadSTests : InstrumentedTest {
|
|||
quadS.setDefaultKey(TEST_KEY_ID)
|
||||
}
|
||||
|
||||
var defaultKeyAccountData: UserAccountDataEvent? = null
|
||||
var defaultKeyAccountData: AccountDataEvent? = null
|
||||
val defaultDataLock = CountDownLatch(1)
|
||||
|
||||
val liveDefAccountData = runBlocking(Dispatchers.Main) {
|
||||
aliceSession.getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
aliceSession.userAccountDataService().getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
}
|
||||
val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
|
||||
val accountDefDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
|
||||
if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
|
||||
defaultKeyAccountData = t.getOrNull()!!
|
||||
defaultDataLock.countDown()
|
||||
|
@ -206,7 +206,7 @@ class QuadSTests : InstrumentedTest {
|
|||
)
|
||||
}
|
||||
|
||||
val accountDataEvent = aliceSession.getAccountDataEvent("my.secret")
|
||||
val accountDataEvent = aliceSession.userAccountDataService().getAccountDataEvent("my.secret")
|
||||
val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
|
||||
|
||||
assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
|
||||
|
@ -275,14 +275,14 @@ class QuadSTests : InstrumentedTest {
|
|||
mTestHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
|
||||
private fun assertAccountData(session: Session, type: String): AccountDataEvent {
|
||||
val accountDataLock = CountDownLatch(1)
|
||||
var accountData: UserAccountDataEvent? = null
|
||||
var accountData: AccountDataEvent? = null
|
||||
|
||||
val liveAccountData = runBlocking(Dispatchers.Main) {
|
||||
session.getLiveAccountDataEvent(type)
|
||||
session.userAccountDataService().getLiveAccountDataEvent(type)
|
||||
}
|
||||
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
|
||||
val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
|
||||
if (t?.getOrNull()?.type == type) {
|
||||
accountData = t.getOrNull()
|
||||
accountDataLock.countDown()
|
||||
|
|
|
@ -51,12 +51,12 @@ data class SsoIdentityProvider(
|
|||
) : Parcelable, Comparable<SsoIdentityProvider> {
|
||||
|
||||
companion object {
|
||||
const val BRAND_GOOGLE = "org.matrix.google"
|
||||
const val BRAND_GITHUB = "org.matrix.github"
|
||||
const val BRAND_APPLE = "org.matrix.apple"
|
||||
const val BRAND_FACEBOOK = "org.matrix.facebook"
|
||||
const val BRAND_TWITTER = "org.matrix.twitter"
|
||||
const val BRAND_GITLAB = "org.matrix.gitlab"
|
||||
const val BRAND_GOOGLE = "google"
|
||||
const val BRAND_GITHUB = "github"
|
||||
const val BRAND_APPLE = "apple"
|
||||
const val BRAND_FACEBOOK = "facebook"
|
||||
const val BRAND_TWITTER = "twitter"
|
||||
const val BRAND_GITLAB = "gitlab"
|
||||
}
|
||||
|
||||
override fun compareTo(other: SsoIdentityProvider): Int {
|
||||
|
|
|
@ -78,7 +78,6 @@ interface Session :
|
|||
InitialSyncProgressService,
|
||||
HomeServerCapabilitiesService,
|
||||
SecureStorageService,
|
||||
AccountDataService,
|
||||
AccountService {
|
||||
|
||||
/**
|
||||
|
@ -239,6 +238,11 @@ interface Session :
|
|||
*/
|
||||
fun openIdService(): OpenIdService
|
||||
|
||||
/**
|
||||
* Returns the user account data service associated with the session
|
||||
*/
|
||||
fun userAccountDataService(): AccountDataService
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
|
@ -262,12 +266,17 @@ interface Session :
|
|||
* A global session listener to get notified for some events.
|
||||
*/
|
||||
interface Listener : SessionLifecycleObserver {
|
||||
/**
|
||||
* Called when the session received new invites to room so the client can react to it once.
|
||||
*/
|
||||
fun onNewInvitedRoom(session: Session, roomId: String) = Unit
|
||||
|
||||
/**
|
||||
* Possible cases:
|
||||
* - The access token is not valid anymore,
|
||||
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
|
||||
*/
|
||||
fun onGlobalError(session: Session, globalError: GlobalError)
|
||||
fun onGlobalError(session: Session, globalError: GlobalError) = Unit
|
||||
}
|
||||
|
||||
val sharedSecretStorageService: SharedSecretStorageService
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.events.model.Content
|
|||
* Currently used types are defined in [UserAccountDataTypes].
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserAccountDataEvent(
|
||||
data class AccountDataEvent(
|
||||
@Json(name = "type") val type: String,
|
||||
@Json(name = "content") val content: Content
|
||||
)
|
|
@ -20,28 +20,31 @@ import androidx.lifecycle.LiveData
|
|||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
||||
/**
|
||||
* This service can be attached globally to the session so it represents user data or attached to a single room.
|
||||
*/
|
||||
interface AccountDataService {
|
||||
/**
|
||||
* Retrieve the account data with the provided type or null if not found
|
||||
*/
|
||||
fun getAccountDataEvent(type: String): UserAccountDataEvent?
|
||||
fun getAccountDataEvent(type: String): AccountDataEvent?
|
||||
|
||||
/**
|
||||
* Observe the account data with the provided type
|
||||
*/
|
||||
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>>
|
||||
fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>>
|
||||
|
||||
/**
|
||||
* Retrieve the account data with the provided types. The return list can have a different size that
|
||||
* the size of the types set, because some AccountData may not exist.
|
||||
* If an empty set is provided, all the AccountData are retrieved
|
||||
*/
|
||||
fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent>
|
||||
fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent>
|
||||
|
||||
/**
|
||||
* Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
|
||||
*/
|
||||
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>>
|
||||
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>>
|
||||
|
||||
/**
|
||||
* Update the account data with the provided type and the provided account data content
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.call
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
object CallIdGenerator {
|
||||
fun generate() = UUID.randomUUID().toString()
|
||||
}
|
|
@ -20,8 +20,6 @@ interface CallSignalingService {
|
|||
|
||||
suspend fun getTurnServer(): TurnServerResponse
|
||||
|
||||
fun getPSTNProtocolChecker(): PSTNProtocolChecker
|
||||
|
||||
/**
|
||||
* Create an outgoing call
|
||||
*/
|
||||
|
|
|
@ -26,8 +26,12 @@ interface MxCallDetail {
|
|||
val callId: String
|
||||
val isOutgoing: Boolean
|
||||
val roomId: String
|
||||
val opponentUserId: String
|
||||
val isVideoCall: Boolean
|
||||
val ourPartyId: String
|
||||
val opponentPartyId: Optional<String>?
|
||||
val opponentVersion: Int
|
||||
val opponentUserId: String
|
||||
val capabilities: CallCapabilities?
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,12 +43,6 @@ interface MxCall : MxCallDetail {
|
|||
const val VOIP_PROTO_VERSION = 1
|
||||
}
|
||||
|
||||
val ourPartyId: String
|
||||
var opponentPartyId: Optional<String>?
|
||||
var opponentVersion: Int
|
||||
|
||||
var capabilities: CallCapabilities?
|
||||
|
||||
var state: CallState
|
||||
|
||||
/**
|
||||
|
@ -91,8 +89,12 @@ interface MxCall : MxCallDetail {
|
|||
|
||||
/**
|
||||
* Send a m.call.replaces event to initiate call transfer.
|
||||
* See [org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent] for documentation about the parameters
|
||||
*/
|
||||
suspend fun transfer(targetUserId: String, targetRoomId: String?)
|
||||
suspend fun transfer(targetUserId: String,
|
||||
targetRoomId: String?,
|
||||
createCallId: String?,
|
||||
awaitCallId: String?)
|
||||
|
||||
fun addListener(listener: StateListener)
|
||||
fun removeListener(listener: StateListener)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,9 +31,7 @@ object EventType {
|
|||
const val TYPING = "m.typing"
|
||||
const val REDACTION = "m.room.redaction"
|
||||
const val RECEIPT = "m.receipt"
|
||||
const val TAG = "m.tag"
|
||||
const val ROOM_KEY = "m.room_key"
|
||||
const val FULLY_READ = "m.fully_read"
|
||||
const val PLUMBING = "m.room.plumbing"
|
||||
const val BOT_OPTIONS = "m.room.bot.options"
|
||||
const val PREVIEW_URLS = "org.matrix.room.preview_urls"
|
||||
|
|
|
@ -54,7 +54,7 @@ interface PermalinkService {
|
|||
*
|
||||
* @return the permalink, or null in case of error
|
||||
*/
|
||||
fun createRoomPermalink(roomId: String): String?
|
||||
fun createRoomPermalink(roomId: String, viaServers: List<String>? = null): String?
|
||||
|
||||
/**
|
||||
* Creates a permalink for an event. If you have an event you can use [createPermalink]
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.matrix.android.sdk.api.session.room
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
|
||||
import org.matrix.android.sdk.api.session.room.alias.AliasService
|
||||
import org.matrix.android.sdk.api.session.room.call.RoomCallService
|
||||
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
|
||||
|
@ -55,7 +56,8 @@ interface Room :
|
|||
RoomCallService,
|
||||
RelationService,
|
||||
RoomCryptoService,
|
||||
RoomPushRuleService {
|
||||
RoomPushRuleService,
|
||||
AccountDataService {
|
||||
|
||||
/**
|
||||
* The roomId of this room
|
||||
|
@ -86,12 +88,12 @@ interface Room :
|
|||
* @return The search result
|
||||
*/
|
||||
suspend fun search(searchTerm: String,
|
||||
nextBatch: String?,
|
||||
orderByRecent: Boolean,
|
||||
limit: Int,
|
||||
beforeLimit: Int,
|
||||
afterLimit: Int,
|
||||
includeProfile: Boolean): SearchResult
|
||||
nextBatch: String?,
|
||||
orderByRecent: Boolean,
|
||||
limit: Int,
|
||||
beforeLimit: Int,
|
||||
afterLimit: Int,
|
||||
includeProfile: Boolean): SearchResult
|
||||
|
||||
/**
|
||||
* Use this room as a Space, if the type is correct.
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -32,5 +32,7 @@ data class SpaceChildInfo(
|
|||
val parentRoomId: String?,
|
||||
val suggested: Boolean?,
|
||||
val canonicalAlias: String?,
|
||||
val aliases: List<String>?
|
||||
val aliases: List<String>?,
|
||||
val worldReadable: Boolean
|
||||
|
||||
)
|
||||
|
|
|
@ -44,7 +44,7 @@ data class CallAnswerContent(
|
|||
* Capability advertisement.
|
||||
*/
|
||||
@Json(name = "capabilities") val capabilities: CallCapabilities? = null
|
||||
): CallSignallingContent {
|
||||
): CallSignalingContent {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Answer(
|
||||
|
|
|
@ -41,4 +41,4 @@ data class CallCandidatesContent(
|
|||
* Required. The version of the VoIP specification this messages adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String?
|
||||
): CallSignallingContent
|
||||
): CallSignalingContent
|
||||
|
|
|
@ -44,7 +44,7 @@ data class CallHangupContent(
|
|||
* One of: ["ice_failed", "invite_timeout"]
|
||||
*/
|
||||
@Json(name = "reason") val reason: Reason? = null
|
||||
) : CallSignallingContent {
|
||||
) : CallSignalingContent {
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Reason {
|
||||
@Json(name = "ice_failed")
|
||||
|
@ -56,6 +56,9 @@ data class CallHangupContent(
|
|||
@Json(name = "user_hangup")
|
||||
USER_HANGUP,
|
||||
|
||||
@Json(name = "replaced")
|
||||
REPLACED,
|
||||
|
||||
@Json(name = "user_media_failed")
|
||||
USER_MEDIA_FAILED,
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ data class CallInviteContent(
|
|||
*/
|
||||
@Json(name = "capabilities") val capabilities: CallCapabilities? = null
|
||||
|
||||
): CallSignallingContent {
|
||||
): CallSignalingContent {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Offer(
|
||||
/**
|
||||
|
|
|
@ -47,7 +47,7 @@ data class CallNegotiateContent(
|
|||
*/
|
||||
@Json(name = "version") override val version: String?
|
||||
|
||||
): CallSignallingContent {
|
||||
): CallSignalingContent {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Description(
|
||||
/**
|
||||
|
|
|
@ -37,4 +37,4 @@ data class CallRejectContent(
|
|||
* Required. The version of the VoIP specification this message adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String?
|
||||
) : CallSignallingContent
|
||||
) : CallSignalingContent
|
||||
|
|
|
@ -38,30 +38,30 @@ data class CallReplacesContent(
|
|||
*/
|
||||
@Json(name = "replacement_id") val replacementId: String? = null,
|
||||
/**
|
||||
* Optional. If specified, the transferee client waits for an invite to this room and joins it
|
||||
* (possibly waiting for user confirmation) and then continues the transfer in this room.
|
||||
* If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing.
|
||||
* Optional. If specified, the transferee client waits for an invite to this room and joins it
|
||||
* (possibly waiting for user confirmation) and then continues the transfer in this room.
|
||||
* If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing.
|
||||
*/
|
||||
@Json(name = "target_room") val targerRoomId: String? = null,
|
||||
@Json(name = "target_room") val targetRoomId: String? = null,
|
||||
/**
|
||||
* An object giving information about the transfer target
|
||||
* An object giving information about the transfer target
|
||||
*/
|
||||
@Json(name = "target_user") val targetUser: TargetUser? = null,
|
||||
/**
|
||||
* If specified, gives the call ID for the transferee's client to use when placing the replacement call.
|
||||
* Mutually exclusive with await_call
|
||||
* If specified, gives the call ID for the transferee's client to use when placing the replacement call.
|
||||
* Mutually exclusive with await_call
|
||||
*/
|
||||
@Json(name = "create_call") val createCall: String? = null,
|
||||
/**
|
||||
* If specified, gives the call ID that the transferee's client should wait for.
|
||||
* Mutually exclusive with create_call.
|
||||
* If specified, gives the call ID that the transferee's client should wait for.
|
||||
* Mutually exclusive with create_call.
|
||||
*/
|
||||
@Json(name = "await_call") val awaitCall: String? = null,
|
||||
/**
|
||||
* Required. The version of the VoIP specification this messages adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String?
|
||||
): CallSignallingContent {
|
||||
): CallSignalingContent {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TargetUser(
|
||||
|
@ -77,6 +77,5 @@ data class CallReplacesContent(
|
|||
* Optional. The avatar URL of the transfer target.
|
||||
*/
|
||||
@Json(name = "avatar_url") val avatarUrl: String?
|
||||
|
||||
)
|
||||
}
|
||||
|
|
|
@ -41,4 +41,4 @@ data class CallSelectAnswerContent(
|
|||
* Required. The version of the VoIP specification this message adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String?
|
||||
): CallSignallingContent
|
||||
): CallSignalingContent
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.room.model.call
|
||||
|
||||
interface CallSignallingContent {
|
||||
interface CallSignalingContent {
|
||||
/**
|
||||
* Required. A unique identifier for the call.
|
||||
*/
|
|
@ -35,5 +35,5 @@ object MessageType {
|
|||
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
||||
|
||||
const val MSGTYPE_CONFETTI = "nic.custom.confetti"
|
||||
const val MSGTYPE_SNOW = "io.element.effect.snowfall"
|
||||
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ sealed class PeekResult {
|
|||
val numJoinedMembers: Int?,
|
||||
val roomType: String?,
|
||||
val viaServers: List<String>,
|
||||
val someMembers: List<MatrixItem.UserItem>?
|
||||
val someMembers: List<MatrixItem.UserItem>?,
|
||||
val isPublic: Boolean
|
||||
) : PeekResult()
|
||||
|
||||
data class PeekingNotAllowed(
|
||||
|
|
|
@ -22,16 +22,16 @@ import org.matrix.android.sdk.api.util.JsonDict
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ThirdPartyUser(
|
||||
/*
|
||||
Required. A Matrix User ID represting a third party user.
|
||||
/**
|
||||
* Required. A Matrix User ID representing a third party user.
|
||||
*/
|
||||
@Json(name = "userid") val userId: String,
|
||||
/*
|
||||
Required. The protocol ID that the third party location is a part of.
|
||||
/**
|
||||
* Required. The protocol ID that the third party location is a part of.
|
||||
*/
|
||||
@Json(name = "protocol") val protocol: String,
|
||||
/*
|
||||
Required. Information used to identify this third party location.
|
||||
/**
|
||||
* Required. Information used to identify this third party location.
|
||||
*/
|
||||
@Json(name = "fields") val fields: JsonDict
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.matrix.android.sdk.BuildConfig
|
|||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
|
@ -38,6 +39,8 @@ sealed class MatrixItem(
|
|||
init {
|
||||
if (BuildConfig.DEBUG) checkId()
|
||||
}
|
||||
|
||||
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||
}
|
||||
|
||||
data class EventItem(override val id: String,
|
||||
|
@ -47,6 +50,8 @@ sealed class MatrixItem(
|
|||
init {
|
||||
if (BuildConfig.DEBUG) checkId()
|
||||
}
|
||||
|
||||
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||
}
|
||||
|
||||
data class RoomItem(override val id: String,
|
||||
|
@ -56,6 +61,19 @@ sealed class MatrixItem(
|
|||
init {
|
||||
if (BuildConfig.DEBUG) checkId()
|
||||
}
|
||||
|
||||
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||
}
|
||||
|
||||
data class SpaceItem(override val id: String,
|
||||
override val displayName: String? = null,
|
||||
override val avatarUrl: String? = null)
|
||||
: MatrixItem(id, displayName, avatarUrl) {
|
||||
init {
|
||||
if (BuildConfig.DEBUG) checkId()
|
||||
}
|
||||
|
||||
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||
}
|
||||
|
||||
data class RoomAliasItem(override val id: String,
|
||||
|
@ -68,6 +86,8 @@ sealed class MatrixItem(
|
|||
|
||||
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||
override fun getBestName() = id
|
||||
|
||||
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||
}
|
||||
|
||||
data class GroupItem(override val id: String,
|
||||
|
@ -80,6 +100,8 @@ sealed class MatrixItem(
|
|||
|
||||
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||
override fun getBestName() = id
|
||||
|
||||
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||
}
|
||||
|
||||
open fun getBestName(): String {
|
||||
|
@ -92,12 +114,15 @@ sealed class MatrixItem(
|
|||
}
|
||||
}
|
||||
|
||||
abstract fun updateAvatar(newAvatar: String?): MatrixItem
|
||||
|
||||
/**
|
||||
* Return the prefix as defined in the matrix spec (and not extracted from the id)
|
||||
*/
|
||||
fun getIdPrefix() = when (this) {
|
||||
is UserItem -> '@'
|
||||
is EventItem -> '$'
|
||||
is SpaceItem,
|
||||
is RoomItem -> '!'
|
||||
is RoomAliasItem -> '#'
|
||||
is GroupItem -> '+'
|
||||
|
@ -148,7 +173,11 @@ fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
|||
|
||||
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
|
||||
|
||||
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||
fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
|
||||
MatrixItem.SpaceItem(roomId, displayName, avatarUrl)
|
||||
} else {
|
||||
MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||
}
|
||||
|
||||
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||
|
||||
|
@ -159,4 +188,8 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName,
|
|||
|
||||
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
|
||||
|
||||
fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias ?: "", avatarUrl)
|
||||
fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) {
|
||||
MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl)
|
||||
} else {
|
||||
MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias, avatarUrl)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
|||
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
||||
*/
|
||||
internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
||||
|
||||
internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
||||
|
||||
|
|
|
@ -88,11 +88,9 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
|
||||
return buildString {
|
||||
append(homeServerUrlBase)
|
||||
append(SSO_REDIRECT_PATH)
|
||||
if (providerId != null) {
|
||||
append(MSC2858_SSO_REDIRECT_PATH)
|
||||
append("/$providerId")
|
||||
} else {
|
||||
append(SSO_REDIRECT_PATH)
|
||||
}
|
||||
// Set the redirect url
|
||||
appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl)
|
||||
|
|
|
@ -42,7 +42,7 @@ internal data class LoginFlow(
|
|||
* the client can show a button for each of the supported providers
|
||||
* See MSC #2858
|
||||
*/
|
||||
@Json(name = "org.matrix.msc2858.identity_providers")
|
||||
@Json(name = "identity_providers")
|
||||
val ssoIdentityProvider: List<SsoIdentityProvider>? = null
|
||||
|
||||
)
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database
|
|||
import io.realm.DynamicRealm
|
||||
import io.realm.FieldAttribute
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
|
@ -30,6 +31,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
|||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
|
@ -44,7 +46,7 @@ import javax.inject.Inject
|
|||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 13L
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 14L
|
||||
}
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
|
@ -63,6 +65,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
if (oldVersion <= 10) migrateTo11(realm)
|
||||
if (oldVersion <= 11) migrateTo12(realm)
|
||||
if (oldVersion <= 12) migrateTo13(realm)
|
||||
if (oldVersion <= 13) migrateTo14(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
|
@ -278,11 +281,29 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
|
||||
private fun migrateTo13(realm: DynamicRealm) {
|
||||
Timber.d("Step 12 -> 13")
|
||||
|
||||
// Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12()
|
||||
realm.schema.get("SpaceChildSummaryEntity")
|
||||
?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) }
|
||||
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
|
||||
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
|
||||
}
|
||||
|
||||
private fun migrateTo14(realm: DynamicRealm) {
|
||||
Timber.d("Step 13 -> 14")
|
||||
val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
|
||||
.addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
|
||||
.addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
|
||||
|
||||
realm.schema.get("RoomEntity")
|
||||
?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, Boolean::class.java, FieldAttribute.INDEXED)
|
||||
?.transform {
|
||||
val isHiddenFromUser = it.getString(RoomSummaryEntityFields.VERSIONING_STATE_STR) == VersioningState.UPGRADED_ROOM_JOINED.name
|
||||
it.setBoolean(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, isHiddenFromUser)
|
||||
}
|
||||
|
||||
roomAccountDataSchema.isEmbedded = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,17 +17,25 @@
|
|||
package org.matrix.android.sdk.internal.database.mapper
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntity
|
||||
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
|
||||
|
||||
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
|
||||
fun map(entity: UserAccountDataEntity): UserAccountDataEvent {
|
||||
return UserAccountDataEvent(
|
||||
fun map(entity: UserAccountDataEntity): AccountDataEvent {
|
||||
return AccountDataEvent(
|
||||
type = entity.type ?: "",
|
||||
content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
fun map(entity: RoomAccountDataEntity): AccountDataEvent {
|
||||
return AccountDataEvent(
|
||||
type = entity.type ?: "",
|
||||
content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
|
||||
)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.database.mapper
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
|
||||
|
@ -92,7 +93,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||
parentRoomId = roomSummaryEntity.roomId,
|
||||
suggested = it.suggested,
|
||||
canonicalAlias = it.childSummaryEntity?.canonicalAlias,
|
||||
aliases = it.childSummaryEntity?.aliases?.toList()
|
||||
aliases = it.childSummaryEntity?.aliases?.toList(),
|
||||
worldReadable = it.childSummaryEntity?.joinRules == RoomJoinRules.PUBLIC
|
||||
)
|
||||
},
|
||||
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
|
||||
|
|
|
@ -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()
|
|
@ -23,7 +23,8 @@ import io.realm.annotations.PrimaryKey
|
|||
|
||||
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||
var chunks: RealmList<ChunkEntity> = RealmList(),
|
||||
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList()
|
||||
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||
var accountData: RealmList<RoomAccountDataEntity> = RealmList()
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
|
|
|
@ -232,6 +232,12 @@ internal open class RoomSummaryEntity(
|
|||
}
|
||||
}
|
||||
|
||||
@Index
|
||||
var isHiddenFromUser: Boolean = false
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
@Index
|
||||
private var versioningStateStr: String = VersioningState.NONE.name
|
||||
var versioningState: VersioningState
|
||||
|
|
|
@ -62,6 +62,7 @@ import io.realm.annotations.RealmModule
|
|||
UserAccountDataEntity::class,
|
||||
ScalarTokenEntity::class,
|
||||
WellknownIntegrationManagerConfigEntity::class,
|
||||
RoomAccountDataEntity::class,
|
||||
SpaceChildSummaryEntity::class,
|
||||
SpaceParentSummaryEntity::class
|
||||
])
|
||||
|
|
|
@ -29,6 +29,10 @@ internal fun RoomEntity.Companion.where(realm: Realm, roomId: String): RealmQuer
|
|||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||
}
|
||||
|
||||
internal fun RoomEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomEntity {
|
||||
return where(realm, roomId).findFirst() ?: realm.createObject(RoomEntity::class.java, roomId)
|
||||
}
|
||||
|
||||
internal fun RoomEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<RoomEntity> {
|
||||
val query = realm.where<RoomEntity>()
|
||||
if (membership != null) {
|
||||
|
|
|
@ -16,13 +16,13 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.network
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -44,7 +44,6 @@ internal class GlobalErrorHandler @Inject constructor(
|
|||
sessionParamsStore.setTokenInvalid(sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
listener?.onGlobalError(globalError)
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
|
|||
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
||||
import org.matrix.android.sdk.internal.session.sync.job.SyncThread
|
||||
import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService
|
||||
import org.matrix.android.sdk.internal.util.createUIHandler
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -117,7 +118,7 @@ internal class DefaultSession @Inject constructor(
|
|||
private val contentDownloadStateTracker: ContentDownloadStateTracker,
|
||||
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
|
||||
private val accountDataService: Lazy<AccountDataService>,
|
||||
private val accountDataService: Lazy<UserAccountDataService>,
|
||||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||
private val accountService: Lazy<AccountService>,
|
||||
private val eventService: Lazy<EventService>,
|
||||
|
@ -130,6 +131,7 @@ internal class DefaultSession @Inject constructor(
|
|||
@UnauthenticatedWithCertificate
|
||||
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
|
||||
) : Session,
|
||||
GlobalErrorHandler.Listener,
|
||||
RoomService by roomService.get(),
|
||||
RoomDirectoryService by roomDirectoryService.get(),
|
||||
GroupService by groupService.get(),
|
||||
|
@ -144,9 +146,7 @@ internal class DefaultSession @Inject constructor(
|
|||
SecureStorageService by secureStorageService.get(),
|
||||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
||||
ProfileService by profileService.get(),
|
||||
AccountDataService by accountDataService.get(),
|
||||
AccountService by accountService.get(),
|
||||
GlobalErrorHandler.Listener {
|
||||
AccountService by accountService.get() {
|
||||
|
||||
override val sharedSecretStorageService: SharedSecretStorageService
|
||||
get() = _sharedSecretStorageService.get()
|
||||
|
@ -164,16 +164,16 @@ internal class DefaultSession @Inject constructor(
|
|||
override fun open() {
|
||||
assert(!isOpen)
|
||||
isOpen = true
|
||||
globalErrorHandler.listener = this
|
||||
cryptoService.get().ensureDevice()
|
||||
uiHandler.post {
|
||||
lifecycleObservers.forEach {
|
||||
it.onSessionStarted(this)
|
||||
}
|
||||
sessionListeners.dispatch {
|
||||
it.onSessionStarted(this)
|
||||
sessionListeners.dispatch { _, listener ->
|
||||
listener.onSessionStarted(this)
|
||||
}
|
||||
}
|
||||
globalErrorHandler.listener = this
|
||||
}
|
||||
|
||||
override fun requireBackgroundSync() {
|
||||
|
@ -213,13 +213,13 @@ internal class DefaultSession @Inject constructor(
|
|||
// timelineEventDecryptor.destroy()
|
||||
uiHandler.post {
|
||||
lifecycleObservers.forEach { it.onSessionStopped(this) }
|
||||
sessionListeners.dispatch {
|
||||
it.onSessionStopped(this)
|
||||
sessionListeners.dispatch { _, listener ->
|
||||
listener.onSessionStopped(this)
|
||||
}
|
||||
}
|
||||
cryptoService.get().close()
|
||||
isOpen = false
|
||||
globalErrorHandler.listener = null
|
||||
isOpen = false
|
||||
}
|
||||
|
||||
override fun getSyncStateLive() = getSyncThread().liveState()
|
||||
|
@ -243,8 +243,8 @@ internal class DefaultSession @Inject constructor(
|
|||
lifecycleObservers.forEach {
|
||||
it.onClearCache(this)
|
||||
}
|
||||
sessionListeners.dispatch {
|
||||
it.onClearCache(this)
|
||||
sessionListeners.dispatch { _, listener ->
|
||||
listener.onClearCache(this)
|
||||
}
|
||||
}
|
||||
withContext(NonCancellable) {
|
||||
|
@ -254,8 +254,8 @@ internal class DefaultSession @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onGlobalError(globalError: GlobalError) {
|
||||
sessionListeners.dispatch {
|
||||
it.onGlobalError(this, globalError)
|
||||
sessionListeners.dispatch { _, listener ->
|
||||
listener.onGlobalError(this, globalError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,6 +293,8 @@ internal class DefaultSession @Inject constructor(
|
|||
|
||||
override fun openIdService(): OpenIdService = openIdService.get()
|
||||
|
||||
override fun userAccountDataService(): AccountDataService = accountDataService.get()
|
||||
|
||||
override fun getOkHttpClient(): OkHttpClient {
|
||||
return unauthenticatedWithCertificateOkHttpClient.get()
|
||||
}
|
||||
|
|
|
@ -16,10 +16,17 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SessionListeners @Inject constructor() {
|
||||
@SessionScope
|
||||
internal class SessionListeners @Inject constructor(
|
||||
@SessionId private val sessionId: String,
|
||||
private val sessionManager: SessionManager) {
|
||||
|
||||
private val listeners = mutableSetOf<Session.Listener>()
|
||||
|
||||
|
@ -35,11 +42,18 @@ internal class SessionListeners @Inject constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
fun dispatch(block: (Session.Listener) -> Unit) {
|
||||
fun dispatch(block: (Session, Session.Listener) -> Unit) {
|
||||
synchronized(listeners) {
|
||||
val session = getSession() ?: return Unit.also {
|
||||
Timber.w("You don't have any attached session")
|
||||
}
|
||||
listeners.forEach {
|
||||
block(it)
|
||||
tryOrNull { block(session, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSession(): Session? {
|
||||
return sessionManager.getSessionComponent(sessionId)?.session()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces
|
|||
import org.matrix.android.sdk.internal.session.room.tombstone.RoomTombstoneEventProcessor
|
||||
import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorageService
|
||||
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.DefaultAccountDataService
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService
|
||||
import org.matrix.android.sdk.internal.session.widgets.DefaultWidgetURLFormatter
|
||||
import org.matrix.android.sdk.internal.util.md5
|
||||
import retrofit2.Retrofit
|
||||
|
@ -364,7 +364,7 @@ internal abstract class SessionModule {
|
|||
abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
|
||||
|
||||
@Binds
|
||||
abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService
|
||||
abstract fun bindAccountDataService(service: UserAccountDataService): AccountDataService
|
||||
|
||||
@Binds
|
||||
abstract fun bindEventService(service: DefaultEventService): EventService
|
||||
|
|
|
@ -24,18 +24,15 @@ import org.matrix.android.sdk.api.session.events.model.EventType
|
|||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallSignallingContent
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
|
@ -192,6 +189,9 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
|
|||
// Ignore remote echo
|
||||
return
|
||||
}
|
||||
if (event.roomId == null || event.senderId == null) {
|
||||
return
|
||||
}
|
||||
if (event.senderId == userId) {
|
||||
// discard current call, it's answered by another of my session
|
||||
activeCallHandler.removeCall(call.callId)
|
||||
|
@ -201,20 +201,16 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
|
|||
Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
|
||||
return
|
||||
}
|
||||
call.apply {
|
||||
opponentPartyId = Optional.from(content.partyId)
|
||||
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
||||
capabilities = content.capabilities ?: CallCapabilities()
|
||||
}
|
||||
mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities)
|
||||
callListenersDispatcher.onCallAnswerReceived(content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MxCall.partyIdsMatches(contentSignallingContent: CallSignallingContent): Boolean {
|
||||
return opponentPartyId?.getOrNull() == contentSignallingContent.partyId
|
||||
private fun MxCall.partyIdsMatches(contentSignalingContent: CallSignalingContent): Boolean {
|
||||
return opponentPartyId?.getOrNull() == contentSignalingContent.partyId
|
||||
}
|
||||
|
||||
private fun CallSignallingContent.getCall(): MxCall? {
|
||||
private fun CallSignalingContent.getCall(): MxCall? {
|
||||
val currentCall = callId?.let {
|
||||
activeCallHandler.getCallWithId(it)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.call
|
|||
import org.matrix.android.sdk.api.session.call.CallListener
|
||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
|
@ -30,18 +29,13 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||
private val callSignalingHandler: CallSignalingHandler,
|
||||
private val mxCallFactory: MxCallFactory,
|
||||
private val activeCallHandler: ActiveCallHandler,
|
||||
private val turnServerDataSource: TurnServerDataSource,
|
||||
private val pstnProtocolChecker: PSTNProtocolChecker
|
||||
private val turnServerDataSource: TurnServerDataSource
|
||||
) : CallSignalingService {
|
||||
|
||||
override suspend fun getTurnServer(): TurnServerResponse {
|
||||
return turnServerDataSource.getTurnServer()
|
||||
}
|
||||
|
||||
override fun getPSTNProtocolChecker(): PSTNProtocolChecker {
|
||||
return pstnProtocolChecker
|
||||
}
|
||||
|
||||
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
|
||||
return mxCallFactory.createOutgoingCall(roomId, otherUserId, isVideoCall).also {
|
||||
activeCallHandler.addCall(it)
|
||||
|
|
|
@ -17,18 +17,17 @@
|
|||
package org.matrix.android.sdk.internal.session.call
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.session.call.CallIdGenerator
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
|
||||
import org.matrix.android.sdk.internal.di.DeviceId
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
|
||||
import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class MxCallFactory @Inject constructor(
|
||||
|
@ -48,32 +47,38 @@ internal class MxCallFactory @Inject constructor(
|
|||
roomId = roomId,
|
||||
userId = userId,
|
||||
ourPartyId = deviceId ?: "",
|
||||
opponentUserId = opponentUserId,
|
||||
isVideoCall = content.isVideo(),
|
||||
localEchoEventFactory = localEchoEventFactory,
|
||||
eventSenderProcessor = eventSenderProcessor,
|
||||
matrixConfiguration = matrixConfiguration,
|
||||
getProfileInfoTask = getProfileInfoTask
|
||||
).apply {
|
||||
opponentPartyId = Optional.from(content.partyId)
|
||||
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
||||
capabilities = content.capabilities ?: CallCapabilities()
|
||||
updateOpponentData(opponentUserId, content, content.capabilities)
|
||||
}
|
||||
}
|
||||
|
||||
fun createOutgoingCall(roomId: String, opponentUserId: String, isVideoCall: Boolean): MxCall {
|
||||
return MxCallImpl(
|
||||
callId = UUID.randomUUID().toString(),
|
||||
callId = CallIdGenerator.generate(),
|
||||
isOutgoing = true,
|
||||
roomId = roomId,
|
||||
userId = userId,
|
||||
ourPartyId = deviceId ?: "",
|
||||
opponentUserId = opponentUserId,
|
||||
isVideoCall = isVideoCall,
|
||||
localEchoEventFactory = localEchoEventFactory,
|
||||
eventSenderProcessor = eventSenderProcessor,
|
||||
matrixConfiguration = matrixConfiguration,
|
||||
getProfileInfoTask = getProfileInfoTask
|
||||
)
|
||||
).apply {
|
||||
// Setup with this userId, might be updated when processing the Answer event
|
||||
this.opponentUserId = opponentUserId
|
||||
}
|
||||
}
|
||||
|
||||
fun updateOutgoingCallWithOpponentData(call: MxCall,
|
||||
userId: String,
|
||||
content: CallSignalingContent,
|
||||
callCapabilities: CallCapabilities?) {
|
||||
(call as? MxCallImpl)?.updateOpponentData(userId, content, callCapabilities)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.matrix.android.sdk.internal.session.call.model
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.session.call.CallIdGenerator
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
|
@ -36,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
|||
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.SdpType
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
|
||||
|
@ -43,14 +45,13 @@ import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
|
|||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class MxCallImpl(
|
||||
override val callId: String,
|
||||
override val isOutgoing: Boolean,
|
||||
override val roomId: String,
|
||||
private val userId: String,
|
||||
override val opponentUserId: String,
|
||||
override val isVideoCall: Boolean,
|
||||
override val ourPartyId: String,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
|
@ -61,8 +62,16 @@ internal class MxCallImpl(
|
|||
|
||||
override var opponentPartyId: Optional<String>? = null
|
||||
override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
|
||||
override lateinit var opponentUserId: String
|
||||
override var capabilities: CallCapabilities? = null
|
||||
|
||||
fun updateOpponentData(userId: String, content: CallSignalingContent, callCapabilities: CallCapabilities?) {
|
||||
opponentPartyId = Optional.from(content.partyId)
|
||||
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
||||
opponentUserId = userId
|
||||
capabilities = callCapabilities ?: CallCapabilities()
|
||||
}
|
||||
|
||||
override var state: CallState = CallState.Idle
|
||||
set(value) {
|
||||
field = value
|
||||
|
@ -202,7 +211,10 @@ internal class MxCallImpl(
|
|||
.also { eventSenderProcessor.postEvent(it) }
|
||||
}
|
||||
|
||||
override suspend fun transfer(targetUserId: String, targetRoomId: String?) {
|
||||
override suspend fun transfer(targetUserId: String,
|
||||
targetRoomId: String?,
|
||||
createCallId: String?,
|
||||
awaitCallId: String?) {
|
||||
val profileInfoParams = GetProfileInfoTask.Params(targetUserId)
|
||||
val profileInfo = try {
|
||||
getProfileInfoTask.execute(profileInfoParams)
|
||||
|
@ -213,15 +225,16 @@ internal class MxCallImpl(
|
|||
CallReplacesContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
replacementId = UUID.randomUUID().toString(),
|
||||
replacementId = CallIdGenerator.generate(),
|
||||
version = MxCall.VOIP_PROTO_VERSION.toString(),
|
||||
targetUser = CallReplacesContent.TargetUser(
|
||||
id = targetUserId,
|
||||
displayName = profileInfo?.get(ProfileService.DISPLAY_NAME_KEY) as? String,
|
||||
avatarUrl = profileInfo?.get(ProfileService.AVATAR_URL_KEY) as? String
|
||||
),
|
||||
targerRoomId = targetRoomId,
|
||||
createCall = UUID.randomUUID().toString()
|
||||
targetRoomId = targetRoomId,
|
||||
awaitCall = awaitCallId,
|
||||
createCall = createCallId
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_REPLACES, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
|
|
|
@ -23,8 +23,11 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
|||
import org.matrix.android.sdk.api.util.MimeTypes
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayOutputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object ThumbnailExtractor {
|
||||
internal class ThumbnailExtractor @Inject constructor(
|
||||
private val context: Context
|
||||
) {
|
||||
|
||||
class ThumbnailData(
|
||||
val width: Int,
|
||||
|
@ -34,22 +37,22 @@ internal object ThumbnailExtractor {
|
|||
val mimeType: String
|
||||
)
|
||||
|
||||
fun extractThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? {
|
||||
fun extractThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
|
||||
return if (attachment.type == ContentAttachmentData.Type.VIDEO) {
|
||||
extractVideoThumbnail(context, attachment)
|
||||
extractVideoThumbnail(attachment)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? {
|
||||
private fun extractVideoThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
|
||||
var thumbnailData: ThumbnailData? = null
|
||||
val mediaMetadataRetriever = MediaMetadataRetriever()
|
||||
try {
|
||||
mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
|
||||
mediaMetadataRetriever.frameAtTime?.let { thumbnail ->
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
||||
thumbnail.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
|
||||
val thumbnailWidth = thumbnail.width
|
||||
val thumbnailHeight = thumbnail.height
|
||||
val thumbnailSize = outputStream.size()
|
||||
|
|
|
@ -82,6 +82,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||
@Inject lateinit var imageCompressor: ImageCompressor
|
||||
@Inject lateinit var videoCompressor: VideoCompressor
|
||||
@Inject lateinit var thumbnailExtractor: ThumbnailExtractor
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
@Inject lateinit var temporaryFileCreator: TemporaryFileCreator
|
||||
|
||||
|
@ -302,7 +303,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
* If appropriate, it will create and upload a thumbnail
|
||||
*/
|
||||
private suspend fun dealWithThumbnail(params: Params): UploadThumbnailResult? {
|
||||
return ThumbnailExtractor.extractThumbnail(context, params.attachment)
|
||||
return thumbnailExtractor.extractThumbnail(params.attachment)
|
||||
?.let { thumbnailData ->
|
||||
val thumbnailProgressListener = object : ProgressRequestBody.Listener {
|
||||
override fun onProgress(current: Long, total: Long) {
|
||||
|
|
|
@ -44,7 +44,7 @@ import org.matrix.android.sdk.internal.session.profile.BindThreePidsTask
|
|||
import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask
|
||||
import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.internal.util.ensureProtocol
|
||||
|
@ -77,7 +77,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
private val submitTokenForBindingTask: IdentitySubmitTokenForBindingTask,
|
||||
private val unbindThreePidsTask: UnbindThreePidsTask,
|
||||
private val identityApiProvider: IdentityApiProvider,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val accountDataDataSource: UserAccountDataDataSource,
|
||||
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
||||
private val sessionParams: SessionParams
|
||||
) : IdentityService, SessionLifecycleObserver {
|
||||
|
|
|
@ -33,8 +33,8 @@ import org.matrix.android.sdk.internal.extensions.observeNotNull
|
|||
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
|
||||
import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
|
||||
|
@ -57,7 +57,7 @@ import javax.inject.Inject
|
|||
internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val accountDataDataSource: UserAccountDataDataSource,
|
||||
private val widgetFactory: WidgetFactory)
|
||||
: SessionLifecycleObserver {
|
||||
|
||||
|
@ -240,7 +240,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
)
|
||||
}
|
||||
|
||||
private fun UserAccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
|
||||
private fun AccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
|
||||
return extractWidgetSequence(widgetFactory)
|
||||
.filter {
|
||||
WidgetType.IntegrationManager == it.type
|
||||
|
|
|
@ -33,8 +33,8 @@ internal class DefaultPermalinkService @Inject constructor(
|
|||
return permalinkFactory.createPermalink(id)
|
||||
}
|
||||
|
||||
override fun createRoomPermalink(roomId: String): String? {
|
||||
return permalinkFactory.createRoomPermalink(roomId)
|
||||
override fun createRoomPermalink(roomId: String, viaServers: List<String>?): String? {
|
||||
return permalinkFactory.createRoomPermalink(roomId, viaServers)
|
||||
}
|
||||
|
||||
override fun createPermalink(roomId: String, eventId: String): String {
|
||||
|
|
|
@ -40,11 +40,18 @@ internal class PermalinkFactory @Inject constructor(
|
|||
} else MATRIX_TO_URL_BASE + escape(id)
|
||||
}
|
||||
|
||||
fun createRoomPermalink(roomId: String): String? {
|
||||
fun createRoomPermalink(roomId: String, via: List<String>? = null): String? {
|
||||
return if (roomId.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
MATRIX_TO_URL_BASE + escape(roomId) + viaParameterFinder.computeViaParams(userId, roomId)
|
||||
buildString {
|
||||
append(MATRIX_TO_URL_BASE)
|
||||
append(escape(roomId))
|
||||
append(
|
||||
via?.takeIf { it.isNotEmpty() }?.let { viaParameterFinder.asUrlViaParameters(it) }
|
||||
?: viaParameterFinder.computeViaParams(userId, roomId)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,8 +39,11 @@ internal class ViaParameterFinder @Inject constructor(
|
|||
* current user one.
|
||||
*/
|
||||
fun computeViaParams(userId: String, roomId: String): String {
|
||||
return computeViaParams(userId, roomId, 3)
|
||||
.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
|
||||
return asUrlViaParameters(computeViaParams(userId, roomId, 3))
|
||||
}
|
||||
|
||||
fun asUrlViaParameters(viaList: List<String>): String {
|
||||
return viaList.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
|
||||
}
|
||||
|
||||
fun computeViaParams(userId: String, roomId: String, max: Int): List<String> {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.matrix.android.sdk.internal.session.room
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
|
@ -41,34 +42,35 @@ import org.matrix.android.sdk.api.session.space.Space
|
|||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
|
||||
import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService
|
||||
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||
import org.matrix.android.sdk.internal.session.search.SearchTask
|
||||
import org.matrix.android.sdk.internal.session.space.DefaultSpace
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import java.security.InvalidParameterException
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||
private val timelineService: TimelineService,
|
||||
private val sendService: SendService,
|
||||
private val draftService: DraftService,
|
||||
private val stateService: StateService,
|
||||
private val uploadsService: UploadsService,
|
||||
private val reportingService: ReportingService,
|
||||
private val roomCallService: RoomCallService,
|
||||
private val readService: ReadService,
|
||||
private val typingService: TypingService,
|
||||
private val aliasService: AliasService,
|
||||
private val tagsService: TagsService,
|
||||
private val cryptoService: CryptoService,
|
||||
private val relationService: RelationService,
|
||||
private val roomMembersService: MembershipService,
|
||||
private val roomPushRuleService: RoomPushRuleService,
|
||||
private val sendStateTask: SendStateTask,
|
||||
private val viaParameterFinder: ViaParameterFinder,
|
||||
private val searchTask: SearchTask) :
|
||||
internal class DefaultRoom(override val roomId: String,
|
||||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||
private val timelineService: TimelineService,
|
||||
private val sendService: SendService,
|
||||
private val draftService: DraftService,
|
||||
private val stateService: StateService,
|
||||
private val uploadsService: UploadsService,
|
||||
private val reportingService: ReportingService,
|
||||
private val roomCallService: RoomCallService,
|
||||
private val readService: ReadService,
|
||||
private val typingService: TypingService,
|
||||
private val aliasService: AliasService,
|
||||
private val tagsService: TagsService,
|
||||
private val cryptoService: CryptoService,
|
||||
private val relationService: RelationService,
|
||||
private val roomMembersService: MembershipService,
|
||||
private val roomPushRuleService: RoomPushRuleService,
|
||||
private val roomAccountDataService: RoomAccountDataService,
|
||||
private val sendStateTask: SendStateTask,
|
||||
private val viaParameterFinder: ViaParameterFinder,
|
||||
private val searchTask: SearchTask) :
|
||||
Room,
|
||||
TimelineService by timelineService,
|
||||
SendService by sendService,
|
||||
|
@ -83,7 +85,8 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
TagsService by tagsService,
|
||||
RelationService by relationService,
|
||||
MembershipService by roomMembersService,
|
||||
RoomPushRuleService by roomPushRuleService {
|
||||
RoomPushRuleService by roomPushRuleService,
|
||||
AccountDataService by roomAccountDataService {
|
||||
|
||||
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
|
||||
return roomSummaryDataSource.getRoomSummaryLive(roomId)
|
||||
|
|
|
@ -360,4 +360,13 @@ internal interface RoomAPI {
|
|||
suspend fun deleteTag(@Path("userId") userId: String,
|
||||
@Path("roomId") roomId: String,
|
||||
@Path("tag") tag: String)
|
||||
|
||||
/**
|
||||
* Set an AccountData event to the room.
|
||||
*/
|
||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/account_data/{type}")
|
||||
suspend fun setRoomAccountData(@Path("userId") userId: String,
|
||||
@Path("roomId") roomId: String,
|
||||
@Path("type") type: String,
|
||||
@Body content: JsonDict)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room
|
|||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService
|
||||
import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
|
||||
import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
|
||||
import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
|
||||
|
@ -60,6 +61,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
|
|||
private val relationServiceFactory: DefaultRelationService.Factory,
|
||||
private val membershipServiceFactory: DefaultMembershipService.Factory,
|
||||
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
|
||||
private val roomAccountDataServiceFactory: RoomAccountDataService.Factory,
|
||||
private val sendStateTask: SendStateTask,
|
||||
private val viaParameterFinder: ViaParameterFinder,
|
||||
private val searchTask: SearchTask) :
|
||||
|
@ -84,6 +86,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
|
|||
relationService = relationServiceFactory.create(roomId),
|
||||
roomMembersService = membershipServiceFactory.create(roomId),
|
||||
roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
|
||||
roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
|
||||
sendStateTask = sendStateTask,
|
||||
searchTask = searchTask,
|
||||
viaParameterFinder = viaParameterFinder
|
||||
|
|
|
@ -28,6 +28,8 @@ import org.matrix.android.sdk.api.session.space.SpaceService
|
|||
import org.matrix.android.sdk.internal.session.DefaultFileService
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
|
||||
import org.matrix.android.sdk.internal.session.room.accountdata.DefaultUpdateRoomAccountDataTask
|
||||
import org.matrix.android.sdk.internal.session.room.accountdata.UpdateRoomAccountDataTask
|
||||
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
|
||||
import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask
|
||||
import org.matrix.android.sdk.internal.session.room.alias.DefaultDeleteRoomAliasTask
|
||||
|
@ -236,6 +238,9 @@ internal abstract class RoomModule {
|
|||
@Binds
|
||||
abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindUpdateRoomAccountDataTask(task: DefaultUpdateRoomAccountDataTask): UpdateRoomAccountDataTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveP
|
|||
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst()
|
||||
?: RoomSummaryEntity(predecessorRoomId)
|
||||
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
|
||||
predecessorRoomSummary.isHiddenFromUser = true
|
||||
realm.insertOrUpdate(predecessorRoomSummary)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
|||
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||
|
@ -105,7 +107,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
|||
numJoinedMembers = publicRepoResult.numJoinedMembers,
|
||||
viaServers = serverList,
|
||||
roomType = null, // would be nice to get that from directory...
|
||||
someMembers = null
|
||||
someMembers = null,
|
||||
isPublic = true
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -143,6 +146,11 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val historyVisibility =
|
||||
stateEvents
|
||||
.lastOrNull { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY && it.stateKey?.isNotEmpty() == true }
|
||||
?.let { it.content?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility }
|
||||
|
||||
val roomType = stateEvents
|
||||
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
|
||||
?.content
|
||||
|
@ -158,7 +166,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
|||
numJoinedMembers = memberCount,
|
||||
roomType = roomType,
|
||||
viaServers = serverList,
|
||||
someMembers = someMembers
|
||||
someMembers = someMembers,
|
||||
isPublic = historyVisibility == RoomHistoryVisibility.WORLD_READABLE
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
// Would be M_FORBIDDEN if cannot peek :/
|
||||
|
|
|
@ -73,6 +73,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
@UserId private val userId: String,
|
||||
private val markdownParser: MarkdownParser,
|
||||
private val textPillsUtils: TextPillsUtils,
|
||||
private val thumbnailExtractor: ThumbnailExtractor,
|
||||
private val localEchoRepository: LocalEchoRepository,
|
||||
private val permalinkFactory: PermalinkFactory
|
||||
) {
|
||||
|
@ -261,7 +262,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
val width = firstFrame?.width ?: 0
|
||||
mediaDataRetriever.release()
|
||||
|
||||
val thumbnailInfo = ThumbnailExtractor.extractThumbnail(context, attachment)?.let {
|
||||
val thumbnailInfo = thumbnailExtractor.extractThumbnail(attachment)?.let {
|
||||
ThumbnailInfo(
|
||||
width = it.width,
|
||||
height = it.height,
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
|
|||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
|
@ -244,7 +243,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
|||
query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
||||
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||
query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||
query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false)
|
||||
|
||||
queryParams.roomCategoryFilter?.let {
|
||||
when (it) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import io.realm.kotlin.createObject
|
|||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
||||
|
@ -28,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
|||
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||
|
@ -55,10 +57,10 @@ import org.matrix.android.sdk.internal.di.UserId
|
|||
import org.matrix.android.sdk.internal.extensions.clearWith
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
|
||||
import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||
import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
|
||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||
import timber.log.Timber
|
||||
|
@ -71,7 +73,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
private val roomAvatarResolver: RoomAvatarResolver,
|
||||
private val eventDecryptor: EventDecryptor,
|
||||
private val crossSigningService: DefaultCrossSigningService,
|
||||
private val stateEventDataSource: StateEventDataSource) {
|
||||
private val roomAccountDataDataSource: RoomAccountDataDataSource) {
|
||||
|
||||
fun update(realm: Realm,
|
||||
roomId: String,
|
||||
|
@ -100,6 +102,10 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
roomSummaryEntity.membership = membership
|
||||
}
|
||||
|
||||
// Hard to filter from the app now we use PagedList...
|
||||
roomSummaryEntity.isHiddenFromUser = roomSummaryEntity.versioningState == VersioningState.UPGRADED_ROOM_JOINED
|
||||
|| roomAccountDataDataSource.getAccountDataEvent(roomId, RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM) != null
|
||||
|
||||
val lastNameEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
|
||||
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
||||
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
||||
|
@ -297,7 +303,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
// Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}")
|
||||
|
||||
lookupMap.entries
|
||||
.filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
|
||||
.filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
|
||||
.forEach { entry ->
|
||||
val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst()
|
||||
if (parent != null) {
|
||||
|
|
|
@ -147,7 +147,8 @@ internal class DefaultSpaceService @Inject constructor(
|
|||
parentRoomId = childStateEv.roomId,
|
||||
suggested = childStateEvContent.suggested,
|
||||
canonicalAlias = childSummary.canonicalAlias,
|
||||
aliases = childSummary.aliases
|
||||
aliases = childSummary.aliases,
|
||||
worldReadable = childSummary.worldReadable
|
||||
)
|
||||
}
|
||||
}.orEmpty()
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
|||
import org.matrix.android.sdk.api.session.initsync.InitSyncStep
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
|
@ -55,7 +54,6 @@ import org.matrix.android.sdk.internal.session.initsync.mapWithProgress
|
|||
import org.matrix.android.sdk.internal.session.initsync.reportSubtask
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler
|
||||
import org.matrix.android.sdk.internal.session.room.read.FullyReadContent
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput
|
||||
|
@ -63,16 +61,15 @@ import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent
|
|||
import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync
|
||||
import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSync
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse
|
||||
import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler
|
||||
import org.matrix.android.sdk.internal.util.computeBestChunkSize
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||
private val roomTagHandler: RoomTagHandler,
|
||||
private val roomFullyReadHandler: RoomFullyReadHandler,
|
||||
private val roomAccountDataHandler: RoomSyncAccountDataHandler,
|
||||
private val cryptoService: DefaultCryptoService,
|
||||
private val roomMemberEventHandler: RoomMemberEventHandler,
|
||||
private val roomTypingUsersHandler: RoomTypingUsersHandler,
|
||||
|
@ -198,11 +195,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
?.takeIf { it.isNotEmpty() }
|
||||
?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC, aggregator) }
|
||||
|
||||
if (roomSync.accountData?.events?.isNotEmpty() == true) {
|
||||
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
|
||||
if (roomSync.accountData != null) {
|
||||
roomAccountDataHandler.handle(realm, roomId, roomSync.accountData)
|
||||
}
|
||||
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||
val roomEntity = RoomEntity.getOrCreate(realm, roomId)
|
||||
|
||||
if (roomEntity.membership == Membership.INVITE) {
|
||||
roomEntity.chunks.deleteAllFromRealm()
|
||||
|
@ -265,7 +262,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
insertType: EventInsertType,
|
||||
syncLocalTimestampMillis: Long): RoomEntity {
|
||||
Timber.v("Handle invited sync for room $roomId")
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||
val roomEntity = RoomEntity.getOrCreate(realm, roomId)
|
||||
roomEntity.membership = Membership.INVITE
|
||||
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
|
||||
roomSync.inviteState.events.forEach { event ->
|
||||
|
@ -294,7 +291,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
roomSync: RoomSync,
|
||||
insertType: EventInsertType,
|
||||
syncLocalTimestampMillis: Long): RoomEntity {
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||
val roomEntity = RoomEntity.getOrCreate(realm, roomId)
|
||||
for (event in roomSync.state?.events.orEmpty()) {
|
||||
if (event.eventId == null || event.stateKey == null || event.type == null) {
|
||||
continue
|
||||
|
@ -460,17 +457,4 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
|
||||
accountData.events?.forEach { event ->
|
||||
val eventType = event.getClearType()
|
||||
if (eventType == EventType.TAG) {
|
||||
val content = event.getClearContent().toModel<RoomTagContent>()
|
||||
roomTagHandler.handle(realm, roomId, content)
|
||||
} else if (eventType == EventType.FULLY_READ) {
|
||||
val content = event.getClearContent().toModel<FullyReadContent>()
|
||||
roomFullyReadHandler.handle(realm, roomId, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
|||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||
import org.matrix.android.sdk.internal.session.SessionListeners
|
||||
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
|
||||
import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
|
||||
import org.matrix.android.sdk.internal.session.initsync.reportSubtask
|
||||
|
@ -44,6 +45,7 @@ private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
|
|||
internal class SyncResponseHandler @Inject constructor(
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
@SessionId private val sessionId: String,
|
||||
private val sessionListeners: SessionListeners,
|
||||
private val workManagerProvider: WorkManagerProvider,
|
||||
private val roomSyncHandler: RoomSyncHandler,
|
||||
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
||||
|
@ -125,6 +127,7 @@ internal class SyncResponseHandler @Inject constructor(
|
|||
syncResponse.rooms?.let {
|
||||
checkPushRules(it, isInitialSync)
|
||||
userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite)
|
||||
dispatchInvitedRoom(it)
|
||||
}
|
||||
syncResponse.groups?.let {
|
||||
scheduleGroupDataFetchingIfNeeded(it)
|
||||
|
@ -139,6 +142,13 @@ internal class SyncResponseHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun dispatchInvitedRoom(roomsSyncResponse: RoomsSyncResponse) {
|
||||
roomsSyncResponse.invite.keys.forEach { roomId ->
|
||||
sessionListeners.dispatch { session, listener ->
|
||||
listener.onNewInvitedRoom(session, roomId) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* At the moment we don't get any group data through the sync, so we poll where every hour.
|
||||
* You can also force to refetch group data using [Group] API.
|
||||
|
|
|
@ -23,7 +23,7 @@ import io.realm.kotlin.where
|
|||
import org.matrix.android.sdk.api.pushrules.RuleScope
|
||||
import org.matrix.android.sdk.api.pushrules.RuleSetKey
|
||||
import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -113,7 +113,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handlePushRules(realm: Realm, event: UserAccountDataEvent) {
|
||||
private fun handlePushRules(realm: Realm, event: AccountDataEvent) {
|
||||
val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return
|
||||
realm.where(PushRulesEntity::class.java)
|
||||
.findAll()
|
||||
|
@ -155,7 +155,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
realm.insertOrUpdate(underrides)
|
||||
}
|
||||
|
||||
private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) {
|
||||
private fun handleDirectChatRooms(realm: Realm, event: AccountDataEvent) {
|
||||
val content = event.content.toModel<DirectMessagesContent>() ?: return
|
||||
content.forEach { (userId, roomIds) ->
|
||||
roomIds.forEach { roomId ->
|
||||
|
@ -181,7 +181,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) {
|
||||
private fun handleIgnoredUsers(realm: Realm, event: AccountDataEvent) {
|
||||
val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return
|
||||
realm.where(IgnoredUserEntity::class.java)
|
||||
.findAll()
|
||||
|
@ -191,7 +191,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
// TODO If not initial sync, we should execute a init sync
|
||||
}
|
||||
|
||||
private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) {
|
||||
private fun handleBreadcrumbs(realm: Realm, event: AccountDataEvent) {
|
||||
val recentRoomIds = event.content.toModel<BreadcrumbsContent>()?.recentRoomIds ?: return
|
||||
val entity = BreadcrumbsEntity.getOrCreate(realm)
|
||||
|
||||
|
|
|
@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataSync(
|
||||
@Json(name = "events") val list: List<UserAccountDataEvent> = emptyList()
|
||||
@Json(name = "events") val list: List<AccountDataEvent> = emptyList()
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ import org.matrix.android.sdk.internal.session.identity.IdentityAuthAPI
|
|||
import org.matrix.android.sdk.internal.session.identity.IdentityRegisterTask
|
||||
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
|
||||
import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTermsContent
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
|
||||
import javax.inject.Inject
|
||||
|
@ -38,7 +38,7 @@ import javax.inject.Inject
|
|||
internal class DefaultTermsService @Inject constructor(
|
||||
@UnauthenticatedWithCertificate
|
||||
private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val accountDataDataSource: UserAccountDataDataSource,
|
||||
private val termsAPI: TermsAPI,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val getOpenIdTokenTask: GetOpenIdTokenTask,
|
||||
|
|
|
@ -38,7 +38,7 @@ internal interface ThirdPartyAPI {
|
|||
*
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol
|
||||
*/
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}")
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/user/{protocol}")
|
||||
suspend fun getThirdPartyUser(@Path("protocol") protocol: String,
|
||||
@QueryMap params: Map<String, String>?): List<ThirdPartyUser>
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import androidx.lifecycle.Transformations
|
|||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
|
@ -31,27 +31,27 @@ import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityField
|
|||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
private val accountDataMapper: AccountDataMapper) {
|
||||
internal class UserAccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
private val accountDataMapper: AccountDataMapper) {
|
||||
|
||||
fun getAccountDataEvent(type: String): UserAccountDataEvent? {
|
||||
fun getAccountDataEvent(type: String): AccountDataEvent? {
|
||||
return getAccountDataEvents(setOf(type)).firstOrNull()
|
||||
}
|
||||
|
||||
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
|
||||
fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
|
||||
return Transformations.map(getLiveAccountDataEvents(setOf(type))) {
|
||||
it.firstOrNull()?.toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
|
||||
fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
|
||||
return realmSessionProvider.withRealm {
|
||||
accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map)
|
||||
}
|
||||
}
|
||||
|
||||
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
|
||||
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ accountDataEventsQuery(it, types) },
|
||||
accountDataMapper::map
|
|
@ -23,33 +23,33 @@ import org.matrix.android.sdk.api.session.events.model.Content
|
|||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.configureWith
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultAccountDataService @Inject constructor(
|
||||
internal class UserAccountDataService @Inject constructor(
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val accountDataDataSource: UserAccountDataDataSource,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : AccountDataService {
|
||||
|
||||
override fun getAccountDataEvent(type: String): UserAccountDataEvent? {
|
||||
override fun getAccountDataEvent(type: String): AccountDataEvent? {
|
||||
return accountDataDataSource.getAccountDataEvent(type)
|
||||
}
|
||||
|
||||
override fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
|
||||
override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
|
||||
return accountDataDataSource.getLiveAccountDataEvent(type)
|
||||
}
|
||||
|
||||
override fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
|
||||
override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
|
||||
return accountDataDataSource.getAccountDataEvents(types)
|
||||
}
|
||||
|
||||
override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
|
||||
override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
|
||||
return accountDataDataSource.getLiveAccountDataEvents(types)
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.Transformations
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -39,7 +39,7 @@ import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
|||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
|
||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
|
||||
import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
|
||||
import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
|
||||
import java.util.HashMap
|
||||
|
@ -47,7 +47,7 @@ import javax.inject.Inject
|
|||
|
||||
@SessionScope
|
||||
internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val accountDataDataSource: UserAccountDataDataSource,
|
||||
private val stateEventDataSource: StateEventDataSource,
|
||||
private val createWidgetTask: CreateWidgetTask,
|
||||
private val widgetFactory: WidgetFactory,
|
||||
|
@ -150,8 +150,8 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
|
|||
return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes)
|
||||
}
|
||||
|
||||
private fun UserAccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null): List<Widget> {
|
||||
private fun AccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null): List<Widget> {
|
||||
return extractWidgetSequence(widgetFactory)
|
||||
.filter {
|
||||
val widgetType = it.widgetContent.type ?: return@filter false
|
||||
|
|
|
@ -19,10 +19,10 @@ package org.matrix.android.sdk.internal.session.widgets.helper
|
|||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
|
||||
internal fun UserAccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
|
||||
internal fun AccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
|
||||
return content.asSequence()
|
||||
.mapNotNull {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
|
|
@ -43,7 +43,7 @@ android {
|
|||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
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'
|
||||
|
||||
// Log
|
||||
|
|
|
@ -14,7 +14,7 @@ kapt {
|
|||
// Note: 2 digits max for each value
|
||||
ext.versionMajor = 1
|
||||
ext.versionMinor = 1
|
||||
ext.versionPatch = 8
|
||||
ext.versionPatch = 9
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
@ -296,13 +296,13 @@ android {
|
|||
dependencies {
|
||||
|
||||
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 markwon_version = '4.1.2'
|
||||
def big_image_viewer_version = '1.8.0'
|
||||
def glide_version = '4.12.0'
|
||||
def moshi_version = '1.12.0'
|
||||
def daggerVersion = '2.35.1'
|
||||
def daggerVersion = '2.36'
|
||||
def autofill_version = "1.1.0"
|
||||
def work_version = '2.5.0'
|
||||
def arch_version = '2.1.0'
|
||||
|
@ -331,7 +331,7 @@ dependencies {
|
|||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
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 "org.threeten:threetenbp:1.4.0:no-tzdb"
|
||||
|
@ -349,7 +349,7 @@ dependencies {
|
|||
implementation 'com.facebook.stetho:stetho:1.6.0'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.23'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24'
|
||||
|
||||
// rx
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
||||
|
|
|
@ -104,7 +104,7 @@ class DefaultErrorFormatter @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
is Failure.OtherServerError -> {
|
||||
is Failure.OtherServerError -> {
|
||||
when (throwable.httpCode) {
|
||||
HttpURLConnection.HTTP_NOT_FOUND ->
|
||||
// homeserver not found
|
||||
|
@ -116,9 +116,9 @@ class DefaultErrorFormatter @Inject constructor(
|
|||
throwable.localizedMessage
|
||||
}
|
||||
}
|
||||
is DialPadLookup.Failure ->
|
||||
is DialPadLookup.Failure ->
|
||||
stringProvider.getString(R.string.call_dial_pad_lookup_error)
|
||||
else -> throwable.localizedMessage
|
||||
else -> throwable.localizedMessage
|
||||
}
|
||||
?: stringProvider.getString(R.string.unknown_error)
|
||||
}
|
||||
|
|
|
@ -94,6 +94,10 @@ fun <T : Fragment> AppCompatActivity.addFragmentToBackstack(
|
|||
}
|
||||
}
|
||||
|
||||
fun AppCompatActivity.popBackstack() {
|
||||
supportFragmentManager.popBackStack()
|
||||
}
|
||||
|
||||
fun AppCompatActivity.resetBackstack() {
|
||||
repeat(supportFragmentManager.backStackEntryCount) {
|
||||
supportFragmentManager.popBackStack()
|
||||
|
|
|
@ -32,6 +32,7 @@ fun Session.configureAndStart(context: Context) {
|
|||
setFilter(FilterService.FilterPreset.ElementFilter)
|
||||
startSyncing(context)
|
||||
refreshPushers()
|
||||
context.vectorComponent().webRtcCallManager().checkForProtocolsSupportIfNeeded()
|
||||
}
|
||||
|
||||
fun Session.startSyncing(context: Context) {
|
||||
|
|
|
@ -32,12 +32,12 @@ import im.vector.app.features.call.VectorCallActivity
|
|||
import im.vector.app.features.call.telecom.CallConnection
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.call.webrtc.getOpponentAsMatrixItem
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.notifications.NotificationUtils
|
||||
import im.vector.app.features.popup.IncomingCallAlert
|
||||
import im.vector.app.features.popup.PopupAlertManager
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
@ -176,7 +176,7 @@ class CallService : VectorService() {
|
|||
}
|
||||
alertManager.postVectorAlert(incomingCallAlert)
|
||||
val notification = notificationUtils.buildIncomingCallNotification(
|
||||
mxCall = call.mxCall,
|
||||
call = call,
|
||||
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
|
||||
fromBg = fromBg
|
||||
)
|
||||
|
@ -207,7 +207,7 @@ class CallService : VectorService() {
|
|||
private fun showCallScreen(call: WebRtcCall, mode: String) {
|
||||
val intent = VectorCallActivity.newIntent(
|
||||
context = this,
|
||||
mxCall = call.mxCall,
|
||||
call = call,
|
||||
mode = mode
|
||||
)
|
||||
startActivity(intent)
|
||||
|
@ -221,7 +221,7 @@ class CallService : VectorService() {
|
|||
val opponentMatrixItem = getOpponentMatrixItem(call)
|
||||
Timber.v("displayOutgoingCallNotification : display the dedicated notification")
|
||||
val notification = notificationUtils.buildOutgoingRingingCallNotification(
|
||||
mxCall = call.mxCall,
|
||||
call = call,
|
||||
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
|
||||
)
|
||||
if (knownCalls.isEmpty()) {
|
||||
|
@ -244,7 +244,7 @@ class CallService : VectorService() {
|
|||
val opponentMatrixItem = getOpponentMatrixItem(call)
|
||||
alertManager.cancelAlert(callId)
|
||||
val notification = notificationUtils.buildPendingCallNotification(
|
||||
mxCall = call.mxCall,
|
||||
call = call,
|
||||
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
|
||||
)
|
||||
if (knownCalls.isEmpty()) {
|
||||
|
@ -275,7 +275,9 @@ class CallService : VectorService() {
|
|||
}
|
||||
|
||||
private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? {
|
||||
return vectorComponent().currentSession().getUser(call.mxCall.opponentUserId)?.toMatrixItem()
|
||||
return vectorComponent().activeSessionHolder().getSafeActiveSession()?.let {
|
||||
call.getOpponentAsMatrixItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 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.core.ui.list
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
|
||||
/**
|
||||
* A generic item with empty space.
|
||||
*/
|
||||
@EpoxyModelClass(layout = R.layout.item_vertical_margin)
|
||||
abstract class VerticalMarginItem : VectorEpoxyModel<VerticalMarginItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var heightInPx: Int = 0
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.space.updateLayoutParams {
|
||||
height = heightInPx
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val space by bind<View>(R.id.item_vertical_margin_space)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ import im.vector.app.databinding.ActivityCallBinding
|
|||
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
|
||||
import im.vector.app.features.call.dialpad.DialPadFragment
|
||||
import im.vector.app.features.call.utils.EglUtils
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||
|
@ -54,7 +55,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
|
|||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxCallDetail
|
||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
import org.webrtc.EglBase
|
||||
|
@ -64,7 +64,7 @@ import javax.inject.Inject
|
|||
|
||||
@Parcelize
|
||||
data class CallArgs(
|
||||
val roomId: String,
|
||||
val signalingRoomId: String,
|
||||
val callId: String,
|
||||
val participantUserId: String,
|
||||
val isIncomingCall: Boolean,
|
||||
|
@ -175,7 +175,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
when (callState) {
|
||||
is CallState.Idle,
|
||||
is CallState.CreateOffer,
|
||||
is CallState.Dialing -> {
|
||||
is CallState.Dialing -> {
|
||||
views.callVideoGroup.isInvisible = true
|
||||
views.callInfoGroup.isVisible = true
|
||||
views.callStatusText.setText(R.string.call_ring)
|
||||
|
@ -189,16 +189,27 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
configureCallInfo(state)
|
||||
}
|
||||
|
||||
is CallState.Answering -> {
|
||||
is CallState.Answering -> {
|
||||
views.callVideoGroup.isInvisible = true
|
||||
views.callInfoGroup.isVisible = true
|
||||
views.callStatusText.setText(R.string.call_connecting)
|
||||
views.callConnectingProgress.isVisible = true
|
||||
configureCallInfo(state)
|
||||
}
|
||||
is CallState.Connected -> {
|
||||
is CallState.Connected -> {
|
||||
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||
if (state.isLocalOnHold || state.isRemoteOnHold) {
|
||||
if (state.transferee !is VectorCallViewState.TransfereeState.NoTransferee) {
|
||||
val transfereeName = if (state.transferee is VectorCallViewState.TransfereeState.KnownTransferee) {
|
||||
state.transferee.name
|
||||
} else {
|
||||
getString(R.string.call_transfer_unknown_person)
|
||||
}
|
||||
views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, transfereeName)
|
||||
views.callActionText.isVisible = true
|
||||
views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) }
|
||||
views.callStatusText.text = state.formattedDuration
|
||||
configureCallInfo(state)
|
||||
} else if (state.isLocalOnHold || state.isRemoteOnHold) {
|
||||
views.smallIsHeldIcon.isVisible = true
|
||||
views.callVideoGroup.isInvisible = true
|
||||
views.callInfoGroup.isVisible = true
|
||||
|
@ -220,7 +231,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
if (callArgs.isVideoCall) {
|
||||
views.callVideoGroup.isVisible = true
|
||||
views.callInfoGroup.isVisible = false
|
||||
views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null
|
||||
views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null
|
||||
} else {
|
||||
views.callVideoGroup.isInvisible = true
|
||||
views.callInfoGroup.isVisible = true
|
||||
|
@ -235,10 +246,10 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
views.callConnectingProgress.isVisible = true
|
||||
}
|
||||
}
|
||||
is CallState.Terminated -> {
|
||||
is CallState.Terminated -> {
|
||||
finish()
|
||||
}
|
||||
null -> {
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +258,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
state.callInfo.otherUserItem?.let {
|
||||
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
|
||||
avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter)
|
||||
views.participantNameText.text = it.getBestName()
|
||||
if (state.transferee is VectorCallViewState.TransfereeState.NoTransferee) {
|
||||
views.participantNameText.text = it.getBestName()
|
||||
} else {
|
||||
views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName())
|
||||
}
|
||||
if (blurAvatar) {
|
||||
avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter)
|
||||
} else {
|
||||
|
@ -276,7 +291,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
views.otherKnownCallAvatarView.setOnClickListener {
|
||||
withState(callViewModel) {
|
||||
val otherCall = callManager.getCallById(it.otherKnownCallInfo?.callId ?: "") ?: return@withState
|
||||
startActivity(newIntent(this, otherCall.mxCall, null))
|
||||
startActivity(newIntent(this, otherCall, null))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
@ -322,13 +337,13 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
private fun handleViewEvents(event: VectorCallViewEvents?) {
|
||||
Timber.v("## VOIP handleViewEvents $event")
|
||||
when (event) {
|
||||
VectorCallViewEvents.DismissNoCall -> {
|
||||
VectorCallViewEvents.DismissNoCall -> {
|
||||
finish()
|
||||
}
|
||||
is VectorCallViewEvents.ConnectionTimeout -> {
|
||||
is VectorCallViewEvents.ConnectionTimeout -> {
|
||||
onErrorTimoutConnect(event.turn)
|
||||
}
|
||||
is VectorCallViewEvents.ShowDialPad -> {
|
||||
is VectorCallViewEvents.ShowDialPad -> {
|
||||
CallDialPadBottomSheet.newInstance(false).apply {
|
||||
callback = dialPadCallback
|
||||
}.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG)
|
||||
|
@ -336,7 +351,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
is VectorCallViewEvents.ShowCallTransferScreen -> {
|
||||
navigator.openCallTransfer(this, callArgs.callId)
|
||||
}
|
||||
null -> {
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -364,18 +379,18 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
const val INCOMING_RINGING = "INCOMING_RINGING"
|
||||
const val INCOMING_ACCEPT = "INCOMING_ACCEPT"
|
||||
|
||||
fun newIntent(context: Context, mxCall: MxCallDetail, mode: String?): Intent {
|
||||
fun newIntent(context: Context, call: WebRtcCall, mode: String?): Intent {
|
||||
return Intent(context, VectorCallActivity::class.java).apply {
|
||||
// what could be the best flags?
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.opponentUserId, !mxCall.isOutgoing, mxCall.isVideoCall))
|
||||
putExtra(MvRx.KEY_ARG, CallArgs(call.nativeRoomId, call.callId, call.mxCall.opponentUserId, !call.mxCall.isOutgoing, call.mxCall.isVideoCall))
|
||||
putExtra(EXTRA_MODE, mode)
|
||||
}
|
||||
}
|
||||
|
||||
fun newIntent(context: Context,
|
||||
callId: String,
|
||||
roomId: String,
|
||||
signalingRoomId: String,
|
||||
otherUserId: String,
|
||||
isIncomingCall: Boolean,
|
||||
isVideoCall: Boolean,
|
||||
|
@ -383,7 +398,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
return Intent(context, VectorCallActivity::class.java).apply {
|
||||
// what could be the best flags?
|
||||
flags = FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(MvRx.KEY_ARG, CallArgs(roomId, callId, otherUserId, isIncomingCall, isVideoCall))
|
||||
putExtra(MvRx.KEY_ARG, CallArgs(signalingRoomId, callId, otherUserId, isIncomingCall, isVideoCall))
|
||||
putExtra(EXTRA_MODE, mode)
|
||||
}
|
||||
}
|
||||
|
@ -410,7 +425,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
}
|
||||
|
||||
override fun returnToChat() {
|
||||
val args = RoomDetailArgs(callArgs.roomId)
|
||||
val args = RoomDetailArgs(callArgs.signalingRoomId)
|
||||
val intent = RoomDetailActivity.newIntent(this, args).apply {
|
||||
flags = FLAG_ACTIVITY_CLEAR_TOP
|
||||
}
|
||||
|
|
|
@ -34,4 +34,5 @@ sealed class VectorCallViewActions : VectorViewModelAction {
|
|||
object ToggleCamera : VectorCallViewActions()
|
||||
object ToggleHDSD : VectorCallViewActions()
|
||||
object InitiateCallTransfer : VectorCallViewActions()
|
||||
object TransferCall: VectorCallViewActions()
|
||||
}
|
||||
|
|
|
@ -23,13 +23,14 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.call.audio.CallAudioManager
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.call.webrtc.getOpponentAsMatrixItem
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -38,8 +39,6 @@ import org.matrix.android.sdk.api.session.call.CallState
|
|||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||
import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
|
||||
class VectorCallViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: VectorCallViewState,
|
||||
|
@ -112,12 +111,21 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
setState {
|
||||
copy(
|
||||
callState = Success(callState),
|
||||
canOpponentBeTransferred = call.capabilities.supportCallTransfer()
|
||||
canOpponentBeTransferred = call.capabilities.supportCallTransfer(),
|
||||
transferee = computeTransfereeState(call)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeTransfereeState(call: MxCall): VectorCallViewState.TransfereeState {
|
||||
val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return VectorCallViewState.TransfereeState.NoTransferee
|
||||
val transfereeRoom = session.getRoomSummary(transfereeCall.nativeRoomId)
|
||||
return transfereeRoom?.displayName?.let {
|
||||
VectorCallViewState.TransfereeState.KnownTransferee(it)
|
||||
} ?: VectorCallViewState.TransfereeState.UnknownTransferee
|
||||
}
|
||||
|
||||
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
||||
|
||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||
|
@ -152,7 +160,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
if (otherCall == null) {
|
||||
copy(otherKnownCallInfo = null)
|
||||
} else {
|
||||
val otherUserItem: MatrixItem? = session.getUser(otherCall.mxCall.opponentUserId)?.toMatrixItem()
|
||||
val otherUserItem = otherCall.getOpponentAsMatrixItem(session)
|
||||
copy(otherKnownCallInfo = VectorCallViewState.CallInfo(otherCall.callId, otherUserItem))
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +175,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
} else {
|
||||
call = webRtcCall
|
||||
callManager.addCurrentCallListener(currentCallListener)
|
||||
val item: MatrixItem? = session.getUser(webRtcCall.mxCall.opponentUserId)?.toMatrixItem()
|
||||
val item = webRtcCall.getOpponentAsMatrixItem(session)
|
||||
webRtcCall.addListener(callListener)
|
||||
val currentSoundDevice = callManager.audioManager.selectedDevice
|
||||
if (currentSoundDevice == CallAudioManager.Device.PHONE) {
|
||||
|
@ -186,7 +194,8 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
canSwitchCamera = webRtcCall.canSwitchCamera(),
|
||||
formattedDuration = webRtcCall.formattedDuration(),
|
||||
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD,
|
||||
canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer()
|
||||
canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer(),
|
||||
transferee = computeTransfereeState(webRtcCall.mxCall)
|
||||
)
|
||||
}
|
||||
updateOtherKnownCall(webRtcCall)
|
||||
|
@ -202,27 +211,27 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
|
||||
override fun handle(action: VectorCallViewActions) = withState { state ->
|
||||
when (action) {
|
||||
VectorCallViewActions.EndCall -> call?.endCall()
|
||||
VectorCallViewActions.AcceptCall -> {
|
||||
VectorCallViewActions.EndCall -> call?.endCall()
|
||||
VectorCallViewActions.AcceptCall -> {
|
||||
setState {
|
||||
copy(callState = Loading())
|
||||
}
|
||||
call?.acceptIncomingCall()
|
||||
}
|
||||
VectorCallViewActions.DeclineCall -> {
|
||||
VectorCallViewActions.DeclineCall -> {
|
||||
setState {
|
||||
copy(callState = Loading())
|
||||
}
|
||||
call?.endCall()
|
||||
}
|
||||
VectorCallViewActions.ToggleMute -> {
|
||||
VectorCallViewActions.ToggleMute -> {
|
||||
val muted = state.isAudioMuted
|
||||
call?.muteCall(!muted)
|
||||
setState {
|
||||
copy(isAudioMuted = !muted)
|
||||
}
|
||||
}
|
||||
VectorCallViewActions.ToggleVideo -> {
|
||||
VectorCallViewActions.ToggleVideo -> {
|
||||
if (state.isVideoCall) {
|
||||
val videoEnabled = state.isVideoEnabled
|
||||
call?.enableVideo(!videoEnabled)
|
||||
|
@ -232,14 +241,14 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
}
|
||||
Unit
|
||||
}
|
||||
VectorCallViewActions.ToggleHoldResume -> {
|
||||
VectorCallViewActions.ToggleHoldResume -> {
|
||||
val isRemoteOnHold = state.isRemoteOnHold
|
||||
call?.updateRemoteOnHold(!isRemoteOnHold)
|
||||
}
|
||||
is VectorCallViewActions.ChangeAudioDevice -> {
|
||||
callManager.audioManager.setAudioDevice(action.device)
|
||||
}
|
||||
VectorCallViewActions.SwitchSoundDevice -> {
|
||||
VectorCallViewActions.SwitchSoundDevice -> {
|
||||
_viewEvents.post(
|
||||
VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device)
|
||||
)
|
||||
|
@ -255,17 +264,17 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
}
|
||||
Unit
|
||||
}
|
||||
VectorCallViewActions.ToggleCamera -> {
|
||||
VectorCallViewActions.ToggleCamera -> {
|
||||
call?.switchCamera()
|
||||
}
|
||||
VectorCallViewActions.ToggleHDSD -> {
|
||||
VectorCallViewActions.ToggleHDSD -> {
|
||||
if (!state.isVideoCall) return@withState
|
||||
call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD)
|
||||
}
|
||||
VectorCallViewActions.OpenDialPad -> {
|
||||
VectorCallViewActions.OpenDialPad -> {
|
||||
_viewEvents.post(VectorCallViewEvents.ShowDialPad)
|
||||
}
|
||||
is VectorCallViewActions.SendDtmfDigit -> {
|
||||
is VectorCallViewActions.SendDtmfDigit -> {
|
||||
call?.sendDtmfDigit(action.digit)
|
||||
}
|
||||
VectorCallViewActions.InitiateCallTransfer -> {
|
||||
|
@ -273,9 +282,20 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
VectorCallViewEvents.ShowCallTransferScreen
|
||||
)
|
||||
}
|
||||
VectorCallViewActions.TransferCall -> {
|
||||
handleCallTransfer()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleCallTransfer() {
|
||||
viewModelScope.launch {
|
||||
val currentCall = call ?: return@launch
|
||||
val transfereeCall = callManager.getTransfereeForCallId(currentCall.callId) ?: return@launch
|
||||
currentCall.transferToCall(transfereeCall)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(initialState: VectorCallViewState): VectorCallViewModel
|
||||
|
|
|
@ -41,17 +41,24 @@ data class VectorCallViewState(
|
|||
val otherKnownCallInfo: CallInfo? = null,
|
||||
val callInfo: CallInfo = CallInfo(callId),
|
||||
val formattedDuration: String = "",
|
||||
val canOpponentBeTransferred: Boolean = false
|
||||
val canOpponentBeTransferred: Boolean = false,
|
||||
val transferee: TransfereeState = TransfereeState.NoTransferee
|
||||
) : MvRxState {
|
||||
|
||||
sealed class TransfereeState {
|
||||
object NoTransferee : TransfereeState()
|
||||
data class KnownTransferee(val name: String) : TransfereeState()
|
||||
object UnknownTransferee : TransfereeState()
|
||||
}
|
||||
|
||||
data class CallInfo(
|
||||
val callId: String,
|
||||
val otherUserItem: MatrixItem? = null
|
||||
)
|
||||
|
||||
constructor(callArgs: CallArgs): this(
|
||||
constructor(callArgs: CallArgs) : this(
|
||||
callId = callArgs.callId,
|
||||
roomId = callArgs.roomId,
|
||||
roomId = callArgs.signalingRoomId,
|
||||
isVideoCall = callArgs.isVideoCall
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,30 +16,24 @@
|
|||
|
||||
package im.vector.app.features.call.dialpad
|
||||
|
||||
import im.vector.app.features.call.lookup.pstnLookup
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import java.lang.IllegalStateException
|
||||
import javax.inject.Inject
|
||||
|
||||
class DialPadLookup @Inject constructor(
|
||||
private val session: Session,
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val callManager: WebRtcCallManager
|
||||
private val webRtcCallManager: WebRtcCallManager,
|
||||
private val directRoomHelper: DirectRoomHelper
|
||||
) {
|
||||
class Failure : Throwable()
|
||||
|
||||
data class Result(val userId: String, val roomId: String)
|
||||
|
||||
suspend fun lookupPhoneNumber(phoneNumber: String): Result {
|
||||
val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure()
|
||||
val thirdPartyUser = tryOrNull {
|
||||
session.thirdPartyService().getThirdPartyUser(
|
||||
protocol = supportedProtocolKey,
|
||||
fields = mapOf("m.id.phone" to phoneNumber)
|
||||
).firstOrNull()
|
||||
} ?: throw Failure()
|
||||
|
||||
val thirdPartyUser = session.pstnLookup(phoneNumber, webRtcCallManager.supportedPSTNProtocol).firstOrNull() ?: throw IllegalStateException()
|
||||
val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId)
|
||||
return Result(userId = thirdPartyUser.userId, roomId = roomId)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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()
|
||||
}
|
|
@ -28,13 +28,16 @@ import im.vector.app.core.platform.VectorViewModel
|
|||
import im.vector.app.features.call.dialpad.DialPadLookup
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||
import kotlinx.coroutines.launch
|
||||
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.MxCall
|
||||
|
||||
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
|
||||
private val dialPadLookup: DialPadLookup,
|
||||
callManager: WebRtcCallManager)
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val callManager: WebRtcCallManager)
|
||||
: VectorViewModel<CallTransferViewState, CallTransferAction, CallTransferViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -75,7 +78,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
|
||||
override fun handle(action: CallTransferAction) {
|
||||
when (action) {
|
||||
is CallTransferAction.ConnectWithUserId -> connectWithUserId(action)
|
||||
is CallTransferAction.ConnectWithUserId -> connectWithUserId(action)
|
||||
is CallTransferAction.ConnectWithPhoneNumber -> connectWithPhoneNumber(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
@ -83,8 +86,17 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
private fun connectWithUserId(action: CallTransferAction.ConnectWithUserId) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_viewEvents.post(CallTransferViewEvents.Loading)
|
||||
call?.mxCall?.transfer(action.selectedUserId, null)
|
||||
if (action.consultFirst) {
|
||||
val dmRoomId = directRoomHelper.ensureDMExists(action.selectedUserId)
|
||||
callManager.startOutgoingCall(
|
||||
nativeRoomId = dmRoomId,
|
||||
otherUserId = action.selectedUserId,
|
||||
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
|
||||
transferee = call
|
||||
)
|
||||
} else {
|
||||
call?.transferToUser(action.selectedUserId, null)
|
||||
}
|
||||
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
|
||||
|
@ -97,7 +109,16 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
try {
|
||||
_viewEvents.post(CallTransferViewEvents.Loading)
|
||||
val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber)
|
||||
call?.mxCall?.transfer(result.userId, result.roomId)
|
||||
if (action.consultFirst) {
|
||||
callManager.startOutgoingCall(
|
||||
nativeRoomId = result.roomId,
|
||||
otherUserId = result.userId,
|
||||
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
|
||||
transferee = call
|
||||
)
|
||||
} else {
|
||||
call?.transferToUser(result.userId, result.roomId)
|
||||
}
|
||||
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue