mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 18:35:40 +03:00
Merge pull request #959 from vector-im/feature/roomshields_perf
Refactor Room Shield / Profile shield
This commit is contained in:
commit
67bc100782
35 changed files with 498 additions and 259 deletions
|
@ -58,7 +58,7 @@ private class LiveDataObservable<T>(
|
|||
}
|
||||
}
|
||||
|
||||
internal fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
|
@ -30,103 +28,22 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
|
|||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.BiFunction
|
||||
import timber.log.Timber
|
||||
|
||||
class RxRoom(private val room: Room, private val session: Session) {
|
||||
class RxRoom(private val room: Room) {
|
||||
|
||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||
val summaryObservable = room.getRoomSummaryLive()
|
||||
return room.getRoomSummaryLive()
|
||||
.asObservable()
|
||||
.startWithCallable {
|
||||
room.roomSummary().toOptional()
|
||||
}
|
||||
.doOnNext { Timber.v("RX: summary emitted for: ${it.getOrNull()?.roomId}") }
|
||||
|
||||
val memberIdsChangeObservable = summaryObservable
|
||||
.map {
|
||||
it.getOrNull()?.let { roomSummary ->
|
||||
if (roomSummary.isEncrypted) {
|
||||
// Return the list of other users
|
||||
roomSummary.otherMemberIds + listOf(session.myUserId)
|
||||
} else {
|
||||
// Return an empty list, the room is not encrypted
|
||||
emptyList()
|
||||
}
|
||||
}.orEmpty()
|
||||
}.distinctUntilChanged()
|
||||
.doOnNext { Timber.v("RX: memberIds emitted. Size: ${it.size}") }
|
||||
|
||||
// Observe the device info of the users in the room
|
||||
val cryptoDeviceInfoObservable = memberIdsChangeObservable
|
||||
.switchMap { membersIds ->
|
||||
session.getLiveCryptoDeviceInfo(membersIds)
|
||||
.asObservable()
|
||||
.map {
|
||||
// If any key change, emit the userIds list
|
||||
membersIds
|
||||
}
|
||||
.startWith(membersIds)
|
||||
.doOnNext { Timber.v("RX: CryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||
}
|
||||
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted 2. Size: ${it.size}") }
|
||||
|
||||
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
|
||||
.map { userIds ->
|
||||
if (userIds.isEmpty()) {
|
||||
Optional<RoomEncryptionTrustLevel>(null)
|
||||
} else {
|
||||
session.getCrossSigningService().getTrustLevelForUsers(userIds).toOptional()
|
||||
}
|
||||
}
|
||||
.doOnNext { Timber.v("RX: roomEncryptionTrustLevel emitted: ${it.getOrNull()?.name}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
|
||||
summaryObservable,
|
||||
roomEncryptionTrustLevelObservable,
|
||||
BiFunction { summary, level ->
|
||||
summary.getOrNull()?.copy(
|
||||
roomEncryptionTrustLevel = level.getOrNull()
|
||||
).toOptional()
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.v("RX: final room summary emitted for ${it.getOrNull()?.roomId}") }
|
||||
.startWithCallable { room.roomSummary().toOptional() }
|
||||
}
|
||||
|
||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
|
||||
return room.getRoomMembersLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
room.getRoomMembers(queryParams)
|
||||
}
|
||||
.doOnNext { Timber.v("RX: room members emitted. Size: ${it.size}") }
|
||||
|
||||
// TODO Do it only for room members of the room (switchMap)
|
||||
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||
.startWith(emptyList<CryptoDeviceInfo>())
|
||||
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<List<RoomMemberSummary>, List<CryptoDeviceInfo>, List<RoomMemberSummary>>(
|
||||
roomMembersObservable,
|
||||
cryptoDeviceInfoObservable,
|
||||
BiFunction { summaries, _ ->
|
||||
summaries.map {
|
||||
if (room.isEncrypted()) {
|
||||
it.copy(
|
||||
// Get the trust level of a virtual room with only this user
|
||||
userEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(listOf(it.userId))
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.v("RX: final room members emitted. Size: ${it.size}") }
|
||||
}
|
||||
|
||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||
|
@ -180,6 +97,6 @@ class RxRoom(private val room: Room, private val session: Session) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Room.rx(session: Session): RxRoom {
|
||||
return RxRoom(this, session)
|
||||
fun Room.rx(): RxRoom {
|
||||
return RxRoom(this)
|
||||
}
|
||||
|
|
|
@ -33,40 +33,14 @@ import im.vector.matrix.android.api.util.toOptional
|
|||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.BiFunction
|
||||
import timber.log.Timber
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
|
||||
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
session.getRoomSummaries(queryParams)
|
||||
}
|
||||
.doOnNext { Timber.v("RX: summaries emitted: size: ${it.size}") }
|
||||
|
||||
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||
.startWith(emptyList<CryptoDeviceInfo>())
|
||||
.doOnNext { Timber.v("RX: crypto device info emitted: size: ${it.size}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
|
||||
summariesObservable,
|
||||
cryptoDeviceInfoObservable,
|
||||
BiFunction { summaries, _ ->
|
||||
summaries.map {
|
||||
if (it.isEncrypted) {
|
||||
it.copy(
|
||||
roomEncryptionTrustLevel = session.getCrossSigningService()
|
||||
.getTrustLevelForUsers(it.otherMemberIds + session.myUserId)
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.d("RX: final summaries emitted: size: ${it.size}") }
|
||||
}
|
||||
|
||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||
|
@ -136,12 +110,16 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
|
||||
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
|
||||
return session.getLiveCryptoDeviceInfo(userId).asObservable()
|
||||
return session.getLiveCryptoDeviceInfo(userId).asObservable().startWithCallable {
|
||||
session.getCryptoDeviceInfo(userId)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
|
||||
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable()
|
||||
.startWith(session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional())
|
||||
.startWithCallable {
|
||||
session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.api.session.crypto.crosssigning
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
||||
|
@ -63,6 +62,4 @@ interface CrossSigningService {
|
|||
fun checkDeviceTrust(otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
locallyTrusted: Boolean?): DeviceTrustResult
|
||||
|
||||
fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
|
||||
/**
|
||||
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
|
||||
*/
|
||||
|
@ -25,7 +23,5 @@ data class RoomMemberSummary constructor(
|
|||
val membership: Membership,
|
||||
val userId: String,
|
||||
val displayName: String? = null,
|
||||
val avatarUrl: String? = null,
|
||||
// TODO Warning: Will not be populated if not using RxRoom
|
||||
val userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||
val avatarUrl: String? = null
|
||||
)
|
||||
|
|
|
@ -19,10 +19,11 @@ package im.vector.matrix.android.internal.crypto
|
|||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.ComputeTrustTask
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultComputeTrustTask
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
||||
|
@ -137,15 +138,6 @@ internal abstract class CryptoModule {
|
|||
return RealmClearCacheTask(realmConfiguration)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
fun providesCryptoStore(@CryptoDatabase
|
||||
realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore {
|
||||
return RealmCryptoStore(
|
||||
realmConfiguration,
|
||||
credentials)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SessionScope
|
||||
|
@ -249,4 +241,10 @@ internal abstract class CryptoModule {
|
|||
|
||||
@Binds
|
||||
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
|
||||
|
||||
@Binds
|
||||
abstract fun bindCryptoStore(realmCryptoStore: RealmCryptoStore): IMXCryptoStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindComputeShieldTrustTask(defaultShieldTrustUpdater: DefaultComputeTrustTask): ComputeTrustTask
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
|||
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -36,10 +39,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
private val olmDevice: MXOlmDevice,
|
||||
private val syncTokenStore: SyncTokenStore,
|
||||
private val credentials: Credentials,
|
||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
|
||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
|
||||
coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
taskExecutor: TaskExecutor) {
|
||||
|
||||
interface UserDevicesUpdateListener {
|
||||
fun onUsersDeviceUpdate(users: List<String>)
|
||||
fun onUsersDeviceUpdate(userIds: List<String>)
|
||||
}
|
||||
|
||||
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
|
||||
|
@ -72,6 +77,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
private val notReadyToRetryHS = mutableSetOf<String>()
|
||||
|
||||
init {
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
|
||||
var isUpdated = false
|
||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||
for ((userId, status) in deviceTrackingStatuses) {
|
||||
|
@ -85,6 +91,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the key downloads should be tried
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright 2020 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.matrix.android.internal.crypto.crosssigning
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncryptionTrustLevel> {
|
||||
data class Params(
|
||||
val userIds: List<String>
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultComputeTrustTask @Inject constructor(
|
||||
val cryptoStore: IMXCryptoStore
|
||||
) : ComputeTrustTask {
|
||||
|
||||
override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel {
|
||||
val allTrustedUserIds = params.userIds
|
||||
.filter { userId -> getUserCrossSigningKeys(userId)?.isTrusted() == true }
|
||||
|
||||
return if (allTrustedUserIds.isEmpty()) {
|
||||
RoomEncryptionTrustLevel.Default
|
||||
} else {
|
||||
// If one of the verified user as an untrusted device -> warning
|
||||
// If all devices of all verified users are trusted -> green
|
||||
// else -> black
|
||||
allTrustedUserIds
|
||||
.mapNotNull { cryptoStore.getUserDeviceList(it) }
|
||||
.flatten()
|
||||
.let { allDevices ->
|
||||
if (getMyCrossSigningKeys() != null) {
|
||||
allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
|
||||
} else {
|
||||
// Legacy method
|
||||
allDevices.any { !it.isVerified }
|
||||
}
|
||||
}
|
||||
.let { hasWarning ->
|
||||
if (hasWarning) {
|
||||
RoomEncryptionTrustLevel.Warning
|
||||
} else {
|
||||
if (params.userIds.size == allTrustedUserIds.size) {
|
||||
// all users are trusted and all devices are verified
|
||||
RoomEncryptionTrustLevel.Trusted
|
||||
} else {
|
||||
RoomEncryptionTrustLevel.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||
return cryptoStore.getCrossSigningInfo(otherUserId)
|
||||
}
|
||||
|
||||
private fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||
return cryptoStore.getMyCrossSigningInfo()
|
||||
}
|
||||
}
|
|
@ -19,8 +19,6 @@ package im.vector.matrix.android.internal.crypto.crosssigning
|
|||
import androidx.lifecycle.LiveData
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
@ -37,11 +35,14 @@ import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
|
|||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.TaskThread
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.withoutPrefix
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.olm.OlmPkSigning
|
||||
import org.matrix.olm.OlmUtility
|
||||
import timber.log.Timber
|
||||
|
@ -56,9 +57,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
private val deviceListManager: DeviceListManager,
|
||||
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
||||
private val uploadSignaturesTask: UploadSignaturesTask,
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val computeTrustTask: ComputeTrustTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
|
||||
|
||||
private var olmUtility: OlmUtility? = null
|
||||
|
||||
|
@ -210,6 +213,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
|
||||
|
||||
uploadSigningKeysTask.configureWith(params) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.i("## CrossSigning - Keys successfully uploaded")
|
||||
|
@ -245,6 +249,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
resetTrustOnKeyChange()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
|
||||
// this.retryCount = 3
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.i("## CrossSigning - signatures successfully uploaded")
|
||||
|
@ -497,6 +502,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
|
||||
.build()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}.executeBy(taskExecutor)
|
||||
}
|
||||
|
@ -543,6 +549,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
.withDeviceInfo(toUpload)
|
||||
.build()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}.executeBy(taskExecutor)
|
||||
}
|
||||
|
@ -608,10 +615,10 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUsersDeviceUpdate(users: List<String>) {
|
||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users")
|
||||
users.forEach { otherUserId ->
|
||||
|
||||
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users")
|
||||
userIds.forEach { otherUserId ->
|
||||
checkUserTrust(otherUserId).let {
|
||||
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
|
||||
setUserKeysAsTrusted(otherUserId, it.isVerified())
|
||||
|
@ -630,13 +637,16 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
||||
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
||||
}
|
||||
|
||||
eventBus.post(CryptoToSessionUserTrustChange(userIds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
||||
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
||||
|
||||
// If it's me, recheck trust of all users and devices?
|
||||
val users = ArrayList<String>()
|
||||
if (otherUserId == userId && currentTrust != trusted) {
|
||||
|
@ -654,37 +664,5 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
|
||||
val allTrusted = userIds
|
||||
.filter { getUserCrossSigningKeys(it)?.isTrusted() == true }
|
||||
|
||||
val allUsersAreVerified = userIds.size == allTrusted.size
|
||||
|
||||
return if (allTrusted.isEmpty()) {
|
||||
RoomEncryptionTrustLevel.Default
|
||||
} else {
|
||||
// If one of the verified user as an untrusted device -> warning
|
||||
// Green if all devices of all verified users are trusted -> green
|
||||
// else black
|
||||
val allDevices = allTrusted.mapNotNull {
|
||||
cryptoStore.getUserDeviceList(it)
|
||||
}.flatten()
|
||||
if (getMyCrossSigningKeys() != null) {
|
||||
val hasWarning = allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
|
||||
if (hasWarning) {
|
||||
RoomEncryptionTrustLevel.Warning
|
||||
} else {
|
||||
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
|
||||
}
|
||||
} else {
|
||||
val hasWarningLegacy = allDevices.any { !it.isVerified }
|
||||
if (hasWarningLegacy) {
|
||||
RoomEncryptionTrustLevel.Warning
|
||||
} else {
|
||||
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2019 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.matrix.android.internal.crypto.crosssigning
|
||||
|
||||
data class SessionToCryptoRoomMembersUpdate(
|
||||
val roomId: String,
|
||||
val userIds: List<String>
|
||||
)
|
||||
|
||||
data class CryptoToSessionUserTrustChange(
|
||||
val userIds: List<String>
|
||||
)
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright 2020 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.matrix.android.internal.crypto.crosssigning
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ShieldTrustUpdater @Inject constructor(
|
||||
private val eventBus: EventBus,
|
||||
private val computeTrustTask: ComputeTrustTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
@CryptoDatabase private val cryptoRealmConfiguration: RealmConfiguration,
|
||||
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
|
||||
}
|
||||
|
||||
private val backgroundCryptoRealm = AtomicReference<Realm>()
|
||||
private val backgroundSessionRealm = AtomicReference<Realm>()
|
||||
|
||||
// private var cryptoDevicesResult: RealmResults<DeviceInfoEntity>? = null
|
||||
|
||||
// private val cryptoDeviceChangeListener = object : OrderedRealmCollectionChangeListener<RealmResults<DeviceInfoEntity>> {
|
||||
// override fun onChange(t: RealmResults<DeviceInfoEntity>, changeSet: OrderedCollectionChangeSet) {
|
||||
// val grouped = t.groupBy { it.userId }
|
||||
// onCryptoDevicesChange(grouped.keys.mapNotNull { it })
|
||||
// }
|
||||
// }
|
||||
|
||||
fun start() {
|
||||
eventBus.register(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
val cryptoRealm = Realm.getInstance(cryptoRealmConfiguration)
|
||||
backgroundCryptoRealm.set(cryptoRealm)
|
||||
// cryptoDevicesResult = cryptoRealm.where<DeviceInfoEntity>().findAll()
|
||||
// cryptoDevicesResult?.addChangeListener(cryptoDeviceChangeListener)
|
||||
|
||||
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
eventBus.unregister(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
// cryptoDevicesResult?.removeAllChangeListeners()
|
||||
backgroundCryptoRealm.getAndSet(null).also {
|
||||
it?.close()
|
||||
}
|
||||
backgroundSessionRealm.getAndSet(null).also {
|
||||
it?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) {
|
||||
taskExecutor.executorScope.launch {
|
||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
|
||||
// We need to send that back to session base
|
||||
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.get().executeTransaction { realm ->
|
||||
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onTrustUpdate(update: CryptoToSessionUserTrustChange) {
|
||||
onCryptoDevicesChange(update.userIds)
|
||||
}
|
||||
|
||||
private fun onCryptoDevicesChange(users: List<String>) {
|
||||
BACKGROUND_HANDLER.post {
|
||||
val impactedRoomsId = backgroundSessionRealm.get().where(RoomMemberSummaryEntity::class.java)
|
||||
.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
|
||||
.findAll()
|
||||
.map { it.roomId }
|
||||
.distinct()
|
||||
|
||||
val map = HashMap<String, List<String>>()
|
||||
impactedRoomsId.forEach { roomId ->
|
||||
RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId).findAll()?.let { results ->
|
||||
map[roomId] = results.map { it.userId }
|
||||
}
|
||||
}
|
||||
|
||||
map.forEach { entry ->
|
||||
val roomId = entry.key
|
||||
val userList = entry.value
|
||||
taskExecutor.executorScope.launch {
|
||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.get().executeTransaction { realm ->
|
||||
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,6 +61,7 @@ import im.vector.matrix.android.internal.crypto.store.db.query.delete
|
|||
import im.vector.matrix.android.internal.crypto.store.db.query.get
|
||||
import im.vector.matrix.android.internal.crypto.store.db.query.getById
|
||||
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
|
@ -70,10 +71,12 @@ import io.realm.kotlin.where
|
|||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmException
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.set
|
||||
|
||||
@SessionScope
|
||||
internal class RealmCryptoStore(private val realmConfiguration: RealmConfiguration,
|
||||
internal class RealmCryptoStore @Inject constructor(
|
||||
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
|
||||
private val credentials: Credentials) : IMXCryptoStore {
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -403,14 +406,14 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
|||
{ realm: Realm ->
|
||||
realm
|
||||
.where<UserEntity>()
|
||||
.`in`(UserEntityFields.USER_ID, userIds.toTypedArray())
|
||||
.`in`(UserEntityFields.USER_ID, userIds.distinct().toTypedArray())
|
||||
},
|
||||
{ entity ->
|
||||
entity.devices.map { CryptoMapper.mapToModel(it) }
|
||||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull() ?: emptyList()
|
||||
it.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,7 @@ internal object RoomMemberSummaryMapper {
|
|||
userId = roomMemberSummaryEntity.userId,
|
||||
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
||||
displayName = roomMemberSummaryEntity.displayName,
|
||||
membership = roomMemberSummaryEntity.membership,
|
||||
userEncryptionTrustLevel = null
|
||||
membership = roomMemberSummaryEntity.membership
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -49,7 +49,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
} catch (e: MXCryptoError) {
|
||||
} catch (e: Throwable) {
|
||||
Timber.d(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +76,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||
aliases = roomSummaryEntity.aliases.toList(),
|
||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
|
||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex
|
||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database.model
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||
|
@ -47,7 +48,8 @@ internal open class RoomSummaryEntity(
|
|||
// this is required for querying
|
||||
var flatAliases: String = "",
|
||||
var isEncrypted: Boolean = false,
|
||||
var typingUserIds: RealmList<String> = RealmList()
|
||||
var typingUserIds: RealmList<String> = RealmList(),
|
||||
var roomEncryptionTrustLevelStr: String? = null
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
|
@ -68,5 +70,19 @@ internal open class RoomSummaryEntity(
|
|||
versioningStateStr = value.name
|
||||
}
|
||||
|
||||
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
|
||||
get() {
|
||||
return roomEncryptionTrustLevelStr?.let {
|
||||
try {
|
||||
RoomEncryptionTrustLevel.valueOf(it)
|
||||
} catch (failure: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
roomEncryptionTrustLevelStr = value?.name
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.sync.SyncState
|
|||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||
|
@ -89,7 +90,8 @@ internal class DefaultSession @Inject constructor(
|
|||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
||||
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
|
||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||
: Session,
|
||||
RoomService by roomService.get(),
|
||||
RoomDirectoryService by roomDirectoryService.get(),
|
||||
|
@ -119,6 +121,7 @@ internal class DefaultSession @Inject constructor(
|
|||
isOpen = true
|
||||
liveEntityObservers.forEach { it.start() }
|
||||
eventBus.register(this)
|
||||
shieldTrustUpdater.start()
|
||||
}
|
||||
|
||||
override fun requireBackgroundSync() {
|
||||
|
@ -160,6 +163,7 @@ internal class DefaultSession @Inject constructor(
|
|||
isOpen = false
|
||||
eventBus.unregister(this)
|
||||
syncTaskSequencer.close()
|
||||
shieldTrustUpdater.stop()
|
||||
}
|
||||
|
||||
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.internal.session.room
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
|
@ -24,6 +25,7 @@ import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
|||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
|
@ -43,12 +45,14 @@ import im.vector.matrix.android.internal.session.sync.RoomSyncHandler
|
|||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
|
||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||
import io.realm.Realm
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryUpdater @Inject constructor(
|
||||
@UserId private val userId: String,
|
||||
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
||||
private val roomAvatarResolver: RoomAvatarResolver,
|
||||
private val eventBus: EventBus,
|
||||
private val monarchy: Monarchy) {
|
||||
|
||||
companion object {
|
||||
|
@ -139,6 +143,18 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
|
||||
roomSummaryEntity.otherMemberIds.clear()
|
||||
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
|
||||
if (roomSummaryEntity.isEncrypted) {
|
||||
eventBus.post(SessionToCryptoRoomMembersUpdate(roomId, roomSummaryEntity.otherMemberIds.toList() + userId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateShieldTrust(realm: Realm,
|
||||
roomId: String,
|
||||
trust: RoomEncryptionTrustLevel?) {
|
||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||
if (roomSummaryEntity.isEncrypted) {
|
||||
roomSummaryEntity.roomEncryptionTrustLevel = trust
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,12 +38,19 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
|||
lateinit var title: String
|
||||
@EpoxyAttribute
|
||||
var subtitle: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var iconRes: Int = 0
|
||||
|
||||
@EpoxyAttribute
|
||||
var tintIcon: Boolean = true
|
||||
|
||||
@EpoxyAttribute
|
||||
var editableRes: Int = R.drawable.ic_arrow_right
|
||||
|
||||
@EpoxyAttribute
|
||||
var accessoryRes: Int = 0
|
||||
|
||||
@EpoxyAttribute
|
||||
var editable: Boolean = true
|
||||
|
||||
|
@ -70,12 +77,23 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
|||
holder.subtitle.setTextOrHide(subtitle)
|
||||
if (iconRes != 0) {
|
||||
holder.icon.setImageResource(iconRes)
|
||||
if (tintIcon) {
|
||||
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(tintColor))
|
||||
} else {
|
||||
ImageViewCompat.setImageTintList(holder.icon, null)
|
||||
}
|
||||
holder.icon.isVisible = true
|
||||
} else {
|
||||
holder.icon.isVisible = false
|
||||
}
|
||||
|
||||
if (accessoryRes != 0) {
|
||||
holder.secondaryAccessory.setImageResource(accessoryRes)
|
||||
holder.secondaryAccessory.isVisible = true
|
||||
} else {
|
||||
holder.secondaryAccessory.isVisible = false
|
||||
}
|
||||
|
||||
if (editableRes != 0) {
|
||||
val tintColorSecondary = if (destructive) {
|
||||
tintColor
|
||||
|
@ -95,5 +113,6 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
|||
val title by bind<TextView>(R.id.actionTitle)
|
||||
val subtitle by bind<TextView>(R.id.actionSubtitle)
|
||||
val editable by bind<ImageView>(R.id.actionEditable)
|
||||
val secondaryAccessory by bind<ImageView>(R.id.actionSecondaryAccessory)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,19 +36,23 @@ fun EpoxyController.buildProfileAction(
|
|||
subtitle: String? = null,
|
||||
editable: Boolean = true,
|
||||
@DrawableRes icon: Int = 0,
|
||||
tintIcon: Boolean = true,
|
||||
@DrawableRes editableRes: Int? = null,
|
||||
destructive: Boolean = false,
|
||||
divider: Boolean = true,
|
||||
action: ClickListener? = null
|
||||
action: ClickListener? = null,
|
||||
@DrawableRes accessory: Int = 0
|
||||
) {
|
||||
profileActionItem {
|
||||
iconRes(icon)
|
||||
tintIcon(tintIcon)
|
||||
id("action_$id")
|
||||
subtitle(subtitle)
|
||||
editable(editable)
|
||||
editableRes?.let { editableRes(editableRes) }
|
||||
destructive(destructive)
|
||||
title(title)
|
||||
accessoryRes(accessory)
|
||||
listener { _ ->
|
||||
action?.invoke()
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ class RxConfig @Inject constructor(
|
|||
fun setupRxPlugin() {
|
||||
RxJavaPlugins.setErrorHandler { throwable ->
|
||||
Timber.e(throwable, "RxError")
|
||||
|
||||
// is InterruptedException -> fine, some blocking code was interrupted by a dispose call
|
||||
if (throwable !is InterruptedException) {
|
||||
// Avoid crash in production, except if user wants it
|
||||
if (vectorPreferences.failFast()) {
|
||||
throw throwable
|
||||
|
@ -39,3 +40,4 @@ class RxConfig @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
observeMyRoomMember()
|
||||
room.getRoomSummaryLive()
|
||||
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback())
|
||||
room.rx(session).loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
||||
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
||||
// Inform the SDK that the room is displayed
|
||||
session.onRoomDisplayed(initialState.roomId)
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
val queryParams = roomMemberQueryParams {
|
||||
this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE)
|
||||
}
|
||||
room.rx(session)
|
||||
room.rx()
|
||||
.liveRoomMembers(queryParams)
|
||||
.map {
|
||||
it.firstOrNull().toOptional()
|
||||
|
@ -255,7 +255,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
}
|
||||
|
||||
private fun observeDrafts() {
|
||||
room.rx(session).liveDrafts()
|
||||
room.rx().liveDrafts()
|
||||
.subscribe {
|
||||
Timber.d("Draft update --> SetState")
|
||||
setState {
|
||||
|
@ -896,7 +896,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
}
|
||||
|
||||
private fun observeRoomSummary() {
|
||||
room.rx(session).liveRoomSummary()
|
||||
room.rx().liveRoomSummary()
|
||||
.unwrap()
|
||||
.execute { async ->
|
||||
val typingRoomMembers =
|
||||
|
@ -914,7 +914,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
Observable
|
||||
.combineLatest<List<TimelineEvent>, RoomSummary, UnreadState>(
|
||||
timelineEvents.observeOn(Schedulers.computation()),
|
||||
room.rx(session).liveRoomSummary().unwrap(),
|
||||
room.rx().liveRoomSummary().unwrap(),
|
||||
BiFunction { timelineEvents, roomSummary ->
|
||||
computeUnreadState(timelineEvents, roomSummary)
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
|
||||
private fun observeEvent() {
|
||||
if (room == null) return
|
||||
room.rx(session)
|
||||
room.rx()
|
||||
.liveTimelineEvent(eventId)
|
||||
.unwrap()
|
||||
.execute {
|
||||
|
@ -144,7 +144,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
|
||||
private fun observeReactions() {
|
||||
if (room == null) return
|
||||
room.rx(session)
|
||||
room.rx()
|
||||
.liveAnnotationSummary(eventId)
|
||||
.map { annotations ->
|
||||
EmojiDataSource.quickEmojis.map { emoji ->
|
||||
|
|
|
@ -86,7 +86,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
|
|||
}
|
||||
|
||||
private fun observeEventAnnotationSummaries() {
|
||||
RxRoom(room, session)
|
||||
RxRoom(room)
|
||||
.liveAnnotationSummary(eventId)
|
||||
.unwrap()
|
||||
.flatMapSingle { summaries ->
|
||||
|
|
|
@ -54,7 +54,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia
|
|||
|
||||
private fun observeNotificationState() {
|
||||
room
|
||||
.rx(session)
|
||||
.rx()
|
||||
.liveNotificationState()
|
||||
.execute {
|
||||
copy(roomNotificationState = it)
|
||||
|
@ -63,7 +63,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia
|
|||
|
||||
private fun observeRoomSummary() {
|
||||
room
|
||||
.rx(session)
|
||||
.rx()
|
||||
.liveRoomSummary()
|
||||
.unwrap()
|
||||
.execute {
|
||||
|
|
|
@ -97,6 +97,7 @@ class RoomMemberProfileController @Inject constructor(
|
|||
dividerColor = dividerColor,
|
||||
editable = true,
|
||||
icon = icon,
|
||||
tintIcon = false,
|
||||
divider = false,
|
||||
action = { callback?.onShowDeviceList() }
|
||||
)
|
||||
|
|
|
@ -170,7 +170,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
|||
val queryParams = roomMemberQueryParams {
|
||||
this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE)
|
||||
}
|
||||
room.rx(session).liveRoomMembers(queryParams)
|
||||
room.rx().liveRoomMembers(queryParams)
|
||||
.map { it.firstOrNull()?.toMatrixItem().toOptional() }
|
||||
.unwrap()
|
||||
.execute {
|
||||
|
@ -193,8 +193,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
|||
}
|
||||
|
||||
private fun observeRoomSummaryAndPowerLevels(room: Room) {
|
||||
val roomSummaryLive = room.rx(session).liveRoomSummary().unwrap()
|
||||
val powerLevelsContentLive = room.rx(session).liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||
val roomSummaryLive = room.rx().liveRoomSummary().unwrap()
|
||||
val powerLevelsContentLive = room.rx().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
||||
.unwrap()
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package im.vector.riotx.features.roomprofile
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
||||
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
||||
|
@ -79,11 +80,13 @@ class RoomProfileController @Inject constructor(
|
|||
action = { callback?.onNotificationsClicked() }
|
||||
)
|
||||
val numberOfMembers = roomSummary.joinedMembersCount ?: 0
|
||||
val hasWarning = roomSummary.isEncrypted && roomSummary.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Warning
|
||||
buildProfileAction(
|
||||
id = "member_list",
|
||||
title = stringProvider.getQuantityString(R.plurals.room_profile_section_more_member_list, numberOfMembers, numberOfMembers),
|
||||
dividerColor = dividerColor,
|
||||
icon = R.drawable.ic_room_profile_member_list,
|
||||
accessory = R.drawable.ic_shield_warning.takeIf { hasWarning } ?: 0,
|
||||
action = { callback?.onMemberListClicked() }
|
||||
)
|
||||
buildProfileAction(
|
||||
|
|
|
@ -56,7 +56,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R
|
|||
}
|
||||
|
||||
private fun observeRoomSummary() {
|
||||
room.rx(session).liveRoomSummary()
|
||||
room.rx().liveRoomSummary()
|
||||
.unwrap()
|
||||
.execute {
|
||||
copy(roomSummary = it)
|
||||
|
|
|
@ -62,7 +62,7 @@ class RoomMemberListController @Inject constructor(
|
|||
id(roomMember.userId)
|
||||
matrixItem(roomMember.toMatrixItem())
|
||||
avatarRenderer(avatarRenderer)
|
||||
userEncryptionTrustLevel(roomMember.userEncryptionTrustLevel)
|
||||
userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId))
|
||||
clickListener { _ ->
|
||||
callback?.onRoomMemberClicked(roomMember)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
|
@ -31,13 +33,16 @@ import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
|||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsConstants
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import im.vector.matrix.rx.asObservable
|
||||
import im.vector.matrix.rx.mapOptional
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.matrix.rx.unwrap
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.BiFunction
|
||||
import timber.log.Timber
|
||||
|
||||
class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState,
|
||||
private val roomMemberSummaryComparator: RoomMemberSummaryComparator,
|
||||
|
@ -70,10 +75,11 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
|||
displayName = QueryStringValue.IsNotEmpty
|
||||
memberships = Membership.activeMemberships()
|
||||
}
|
||||
|
||||
Observable
|
||||
.combineLatest<List<RoomMemberSummary>, PowerLevelsContent, RoomMemberSummaries>(
|
||||
room.rx(session).liveRoomMembers(roomMemberQueryParams),
|
||||
room.rx(session)
|
||||
room.rx().liveRoomMembers(roomMemberQueryParams),
|
||||
room.rx()
|
||||
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
||||
.unwrap(),
|
||||
|
@ -84,10 +90,36 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
|||
.execute { async ->
|
||||
copy(roomMemberSummaries = async)
|
||||
}
|
||||
|
||||
if (room.isEncrypted()) {
|
||||
room.rx().liveRoomMembers(roomMemberQueryParams)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.switchMap { membersSummary ->
|
||||
session.getLiveCryptoDeviceInfo(membersSummary.map { it.userId })
|
||||
.asObservable()
|
||||
.doOnError { Timber.e(it) }
|
||||
.map { deviceList ->
|
||||
// If any key change, emit the userIds list
|
||||
deviceList.groupBy { it.userId }.mapValues {
|
||||
val allDeviceTrusted = it.value.fold(it.value.isNotEmpty()) { prev, next ->
|
||||
prev && next.trustLevel?.isCrossSigningVerified().orFalse()
|
||||
}
|
||||
if (session.getCrossSigningService().getUserCrossSigningKeys(it.key)?.isTrusted().orFalse()) {
|
||||
if (allDeviceTrusted) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Warning
|
||||
} else {
|
||||
RoomEncryptionTrustLevel.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.execute { async ->
|
||||
copy(trustLevelMap = async)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeRoomSummary() {
|
||||
room.rx(session).liveRoomSummary()
|
||||
room.rx().liveRoomSummary()
|
||||
.unwrap()
|
||||
.execute { async ->
|
||||
copy(roomSummary = async)
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.annotation.StringRes
|
|||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotx.R
|
||||
|
@ -28,7 +29,8 @@ import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
|||
data class RoomMemberListViewState(
|
||||
val roomId: String,
|
||||
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized
|
||||
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized,
|
||||
val trustLevelMap: Async<Map<String, RoomEncryptionTrustLevel?>> = Uninitialized
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||
|
|
|
@ -52,7 +52,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
}
|
||||
|
||||
private fun observeRoomSummary() {
|
||||
room.rx(session).liveRoomSummary()
|
||||
room.rx().liveRoomSummary()
|
||||
.unwrap()
|
||||
.execute { async ->
|
||||
copy(roomSummary = async)
|
||||
|
|
|
@ -426,6 +426,15 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
// ==============================================================================================================
|
||||
|
||||
private fun refreshMyDevice() {
|
||||
session.getUserDevices(session.myUserId).map {
|
||||
DeviceInfo(
|
||||
user_id = session.myUserId,
|
||||
deviceId = it.deviceId,
|
||||
displayName = it.displayName()
|
||||
)
|
||||
}.let {
|
||||
refreshCryptographyPreference(it)
|
||||
}
|
||||
// TODO Move to a ViewModel...
|
||||
session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
|
||||
override fun onSuccess(data: DevicesListResponse) {
|
||||
|
|
|
@ -115,11 +115,20 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
|||
* It can be any mobile devices, and any browsers.
|
||||
*/
|
||||
private fun refreshDevicesList() {
|
||||
if (session.isCryptoEnabled() && !session.sessionParams.credentials.deviceId.isNullOrEmpty()) {
|
||||
if (!session.sessionParams.credentials.deviceId.isNullOrEmpty()) {
|
||||
// display something asap
|
||||
val localKnown = session.getUserDevices(session.myUserId).map {
|
||||
DeviceInfo(
|
||||
user_id = session.myUserId,
|
||||
deviceId = it.deviceId,
|
||||
displayName = it.displayName()
|
||||
)
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
// Keep known list if we have it, and let refresh go in backgroung
|
||||
devices = this.devices.takeIf { it is Success } ?: Loading()
|
||||
devices = this.devices.takeIf { it is Success } ?: Success(localKnown)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:scaleType="center"
|
||||
android:tint="?riotx_text_primary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -60,13 +59,26 @@
|
|||
android:textSize="12sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/actionEditable"
|
||||
app:layout_constraintEnd_toStartOf="@+id/actionSecondaryAccessory"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toEndOf="@id/actionIcon"
|
||||
app:layout_constraintTop_toBottomOf="@id/actionTitle"
|
||||
app:layout_goneMarginStart="0dp"
|
||||
tools:text="@string/room_profile_encrypted_subtitle" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionSecondaryAccessory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:src="@drawable/ic_shield_warning"
|
||||
android:layout_marginRight="8dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/actionEditable"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionEditable"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
Loading…
Reference in a new issue