mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 21:48:50 +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())
|
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
package im.vector.matrix.rx
|
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.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
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.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
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>> {
|
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||||
val summaryObservable = room.getRoomSummaryLive()
|
return room.getRoomSummaryLive()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable { room.roomSummary().toOptional() }
|
||||||
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}") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||||
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
|
return room.getRoomMembersLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
room.getRoomMembers(queryParams)
|
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>> {
|
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 {
|
fun Room.rx(): RxRoom {
|
||||||
return RxRoom(this, session)
|
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 im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.functions.BiFunction
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
|
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
session.getRoomSummaries(queryParams)
|
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>> {
|
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||||
|
@ -136,12 +110,16 @@ class RxSession(private val session: Session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
|
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>> {
|
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
|
||||||
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable()
|
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 androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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.api.util.Optional
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
||||||
|
@ -63,6 +62,4 @@ interface CrossSigningService {
|
||||||
fun checkDeviceTrust(otherUserId: String,
|
fun checkDeviceTrust(otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String,
|
||||||
locallyTrusted: Boolean?): DeviceTrustResult
|
locallyTrusted: Boolean?): DeviceTrustResult
|
||||||
|
|
||||||
fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model
|
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
|
* 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 membership: Membership,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val displayName: String? = null,
|
val displayName: String? = null,
|
||||||
val avatarUrl: String? = null,
|
val avatarUrl: String? = null
|
||||||
// TODO Warning: Will not be populated if not using RxRoom
|
|
||||||
val userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,10 +19,11 @@ package im.vector.matrix.android.internal.crypto
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
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.CryptoService
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
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.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.crosssigning.DefaultCrossSigningService
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
|
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
||||||
|
@ -137,15 +138,6 @@ internal abstract class CryptoModule {
|
||||||
return RealmClearCacheTask(realmConfiguration)
|
return RealmClearCacheTask(realmConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Provides
|
|
||||||
fun providesCryptoStore(@CryptoDatabase
|
|
||||||
realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore {
|
|
||||||
return RealmCryptoStore(
|
|
||||||
realmConfiguration,
|
|
||||||
credentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
@ -249,4 +241,10 @@ internal abstract class CryptoModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
|
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.crypto.tasks.DownloadKeysForUsersTask
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
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 timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -36,10 +39,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val syncTokenStore: SyncTokenStore,
|
private val syncTokenStore: SyncTokenStore,
|
||||||
private val credentials: Credentials,
|
private val credentials: Credentials,
|
||||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
|
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
|
||||||
|
coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
taskExecutor: TaskExecutor) {
|
||||||
|
|
||||||
interface UserDevicesUpdateListener {
|
interface UserDevicesUpdateListener {
|
||||||
fun onUsersDeviceUpdate(users: List<String>)
|
fun onUsersDeviceUpdate(userIds: List<String>)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
|
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
|
||||||
|
@ -72,17 +77,19 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
private val notReadyToRetryHS = mutableSetOf<String>()
|
private val notReadyToRetryHS = mutableSetOf<String>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
var isUpdated = false
|
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
|
||||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
var isUpdated = false
|
||||||
for ((userId, status) in deviceTrackingStatuses) {
|
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||||
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
|
for ((userId, status) in deviceTrackingStatuses) {
|
||||||
// if a download was in progress when we got shut down, it isn't any more.
|
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
|
||||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
// if a download was in progress when we got shut down, it isn't any more.
|
||||||
isUpdated = true
|
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||||
|
isUpdated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isUpdated) {
|
||||||
|
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isUpdated) {
|
|
||||||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 androidx.lifecycle.LiveData
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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.CrossSigningService
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import im.vector.matrix.android.api.util.Optional
|
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.di.UserId
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
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.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.withoutPrefix
|
import im.vector.matrix.android.internal.util.withoutPrefix
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.olm.OlmPkSigning
|
import org.matrix.olm.OlmPkSigning
|
||||||
import org.matrix.olm.OlmUtility
|
import org.matrix.olm.OlmUtility
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -56,9 +57,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
||||||
private val uploadSignaturesTask: UploadSignaturesTask,
|
private val uploadSignaturesTask: UploadSignaturesTask,
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
private val computeTrustTask: ComputeTrustTask,
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
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
|
private var olmUtility: OlmUtility? = null
|
||||||
|
|
||||||
|
@ -210,6 +213,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
|
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
|
||||||
|
|
||||||
uploadSigningKeysTask.configureWith(params) {
|
uploadSigningKeysTask.configureWith(params) {
|
||||||
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
Timber.i("## CrossSigning - Keys successfully uploaded")
|
Timber.i("## CrossSigning - Keys successfully uploaded")
|
||||||
|
@ -245,6 +249,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
resetTrustOnKeyChange()
|
resetTrustOnKeyChange()
|
||||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
|
||||||
// this.retryCount = 3
|
// this.retryCount = 3
|
||||||
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
Timber.i("## CrossSigning - signatures successfully uploaded")
|
Timber.i("## CrossSigning - signatures successfully uploaded")
|
||||||
|
@ -497,6 +502,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
|
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
|
||||||
.build()
|
.build()
|
||||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||||
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}.executeBy(taskExecutor)
|
}.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -543,6 +549,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
.withDeviceInfo(toUpload)
|
.withDeviceInfo(toUpload)
|
||||||
.build()
|
.build()
|
||||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||||
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}.executeBy(taskExecutor)
|
}.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -608,81 +615,52 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUsersDeviceUpdate(users: List<String>) {
|
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
||||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users")
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
users.forEach { otherUserId ->
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
checkUserTrust(otherUserId).let {
|
// TODO if my keys have changes, i should recheck all devices of all users?
|
||||||
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
|
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
||||||
setUserKeysAsTrusted(otherUserId, it.isVerified())
|
devices?.forEach { device ->
|
||||||
}
|
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||||
|
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||||
|
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||||
|
}
|
||||||
|
|
||||||
// TODO if my keys have changes, i should recheck all devices of all users?
|
if (otherUserId == userId) {
|
||||||
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
// It's me, i should check if a newly trusted device is signing my master key
|
||||||
devices?.forEach { device ->
|
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
||||||
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
||||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
}
|
||||||
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherUserId == userId) {
|
eventBus.post(CryptoToSessionUserTrustChange(userIds))
|
||||||
// It's me, i should check if a newly trusted device is signing my master key
|
|
||||||
// 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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||||
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
||||||
|
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
||||||
// If it's me, recheck trust of all users and devices?
|
// If it's me, recheck trust of all users and devices?
|
||||||
val users = ArrayList<String>()
|
val users = ArrayList<String>()
|
||||||
if (otherUserId == userId && currentTrust != trusted) {
|
if (otherUserId == userId && currentTrust != trusted) {
|
||||||
cryptoStore.updateUsersTrust {
|
cryptoStore.updateUsersTrust {
|
||||||
users.add(it)
|
users.add(it)
|
||||||
checkUserTrust(it).isVerified()
|
checkUserTrust(it).isVerified()
|
||||||
}
|
|
||||||
|
|
||||||
users.forEach {
|
|
||||||
cryptoStore.getUserDeviceList(it)?.forEach { device ->
|
|
||||||
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
|
||||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
|
||||||
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
|
users.forEach {
|
||||||
val allTrusted = userIds
|
cryptoStore.getUserDeviceList(it)?.forEach { device ->
|
||||||
.filter { getUserCrossSigningKeys(it)?.isTrusted() == true }
|
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||||
|
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||||
val allUsersAreVerified = userIds.size == allTrusted.size
|
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||||
|
}
|
||||||
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.get
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.query.getById
|
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.crypto.store.db.query.getOrCreate
|
||||||
|
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
@ -70,11 +71,13 @@ import io.realm.kotlin.where
|
||||||
import org.matrix.olm.OlmAccount
|
import org.matrix.olm.OlmAccount
|
||||||
import org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class RealmCryptoStore(private val realmConfiguration: RealmConfiguration,
|
internal class RealmCryptoStore @Inject constructor(
|
||||||
private val credentials: Credentials) : IMXCryptoStore {
|
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
|
||||||
|
private val credentials: Credentials) : IMXCryptoStore {
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Memory cache, to correctly release JNI objects
|
* Memory cache, to correctly release JNI objects
|
||||||
|
@ -403,14 +406,14 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
||||||
{ realm: Realm ->
|
{ realm: Realm ->
|
||||||
realm
|
realm
|
||||||
.where<UserEntity>()
|
.where<UserEntity>()
|
||||||
.`in`(UserEntityFields.USER_ID, userIds.toTypedArray())
|
.`in`(UserEntityFields.USER_ID, userIds.distinct().toTypedArray())
|
||||||
},
|
},
|
||||||
{ entity ->
|
{ entity ->
|
||||||
entity.devices.map { CryptoMapper.mapToModel(it) }
|
entity.devices.map { CryptoMapper.mapToModel(it) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return Transformations.map(liveData) {
|
return Transformations.map(liveData) {
|
||||||
it.firstOrNull() ?: emptyList()
|
it.flatten()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,7 @@ internal object RoomMemberSummaryMapper {
|
||||||
userId = roomMemberSummaryEntity.userId,
|
userId = roomMemberSummaryEntity.userId,
|
||||||
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
||||||
displayName = roomMemberSummaryEntity.displayName,
|
displayName = roomMemberSummaryEntity.displayName,
|
||||||
membership = roomMemberSummaryEntity.membership,
|
membership = roomMemberSummaryEntity.membership
|
||||||
userEncryptionTrustLevel = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
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.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.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
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.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -49,7 +49,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
||||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
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(),
|
aliases = roomSummaryEntity.aliases.toList(),
|
||||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
|
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
|
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.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||||
|
@ -47,7 +48,8 @@ internal open class RoomSummaryEntity(
|
||||||
// this is required for querying
|
// this is required for querying
|
||||||
var flatAliases: String = "",
|
var flatAliases: String = "",
|
||||||
var isEncrypted: Boolean = false,
|
var isEncrypted: Boolean = false,
|
||||||
var typingUserIds: RealmList<String> = RealmList()
|
var typingUserIds: RealmList<String> = RealmList(),
|
||||||
|
var roomEncryptionTrustLevelStr: String? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
@ -68,5 +70,19 @@ internal open class RoomSummaryEntity(
|
||||||
versioningStateStr = value.name
|
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
|
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.api.session.user.UserService
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
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.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.di.SessionId
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||||
|
@ -89,7 +90,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
||||||
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
||||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
|
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
|
||||||
|
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||||
: Session,
|
: Session,
|
||||||
RoomService by roomService.get(),
|
RoomService by roomService.get(),
|
||||||
RoomDirectoryService by roomDirectoryService.get(),
|
RoomDirectoryService by roomDirectoryService.get(),
|
||||||
|
@ -119,6 +121,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
isOpen = true
|
isOpen = true
|
||||||
liveEntityObservers.forEach { it.start() }
|
liveEntityObservers.forEach { it.start() }
|
||||||
eventBus.register(this)
|
eventBus.register(this)
|
||||||
|
shieldTrustUpdater.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requireBackgroundSync() {
|
override fun requireBackgroundSync() {
|
||||||
|
@ -160,6 +163,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
isOpen = false
|
isOpen = false
|
||||||
eventBus.unregister(this)
|
eventBus.unregister(this)
|
||||||
syncTaskSequencer.close()
|
syncTaskSequencer.close()
|
||||||
|
shieldTrustUpdater.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSyncStateLive(): LiveData<SyncState> {
|
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.session.room
|
package im.vector.matrix.android.internal.session.room
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
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.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
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.RoomCanonicalAliasContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
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.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.mapper.ContentMapper
|
||||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
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.RoomSyncSummary
|
||||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
|
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomSummaryUpdater @Inject constructor(
|
internal class RoomSummaryUpdater @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
||||||
private val roomAvatarResolver: RoomAvatarResolver,
|
private val roomAvatarResolver: RoomAvatarResolver,
|
||||||
|
private val eventBus: EventBus,
|
||||||
private val monarchy: Monarchy) {
|
private val monarchy: Monarchy) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -139,6 +143,18 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
|
|
||||||
roomSummaryEntity.otherMemberIds.clear()
|
roomSummaryEntity.otherMemberIds.clear()
|
||||||
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
|
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
|
lateinit var title: String
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var subtitle: String? = null
|
var subtitle: String? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var iconRes: Int = 0
|
var iconRes: Int = 0
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var tintIcon: Boolean = true
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var editableRes: Int = R.drawable.ic_arrow_right
|
var editableRes: Int = R.drawable.ic_arrow_right
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var accessoryRes: Int = 0
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var editable: Boolean = true
|
var editable: Boolean = true
|
||||||
|
|
||||||
|
@ -70,12 +77,23 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
||||||
holder.subtitle.setTextOrHide(subtitle)
|
holder.subtitle.setTextOrHide(subtitle)
|
||||||
if (iconRes != 0) {
|
if (iconRes != 0) {
|
||||||
holder.icon.setImageResource(iconRes)
|
holder.icon.setImageResource(iconRes)
|
||||||
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(tintColor))
|
if (tintIcon) {
|
||||||
|
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(tintColor))
|
||||||
|
} else {
|
||||||
|
ImageViewCompat.setImageTintList(holder.icon, null)
|
||||||
|
}
|
||||||
holder.icon.isVisible = true
|
holder.icon.isVisible = true
|
||||||
} else {
|
} else {
|
||||||
holder.icon.isVisible = false
|
holder.icon.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (accessoryRes != 0) {
|
||||||
|
holder.secondaryAccessory.setImageResource(accessoryRes)
|
||||||
|
holder.secondaryAccessory.isVisible = true
|
||||||
|
} else {
|
||||||
|
holder.secondaryAccessory.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
if (editableRes != 0) {
|
if (editableRes != 0) {
|
||||||
val tintColorSecondary = if (destructive) {
|
val tintColorSecondary = if (destructive) {
|
||||||
tintColor
|
tintColor
|
||||||
|
@ -95,5 +113,6 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
||||||
val title by bind<TextView>(R.id.actionTitle)
|
val title by bind<TextView>(R.id.actionTitle)
|
||||||
val subtitle by bind<TextView>(R.id.actionSubtitle)
|
val subtitle by bind<TextView>(R.id.actionSubtitle)
|
||||||
val editable by bind<ImageView>(R.id.actionEditable)
|
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,
|
subtitle: String? = null,
|
||||||
editable: Boolean = true,
|
editable: Boolean = true,
|
||||||
@DrawableRes icon: Int = 0,
|
@DrawableRes icon: Int = 0,
|
||||||
|
tintIcon: Boolean = true,
|
||||||
@DrawableRes editableRes: Int? = null,
|
@DrawableRes editableRes: Int? = null,
|
||||||
destructive: Boolean = false,
|
destructive: Boolean = false,
|
||||||
divider: Boolean = true,
|
divider: Boolean = true,
|
||||||
action: ClickListener? = null
|
action: ClickListener? = null,
|
||||||
|
@DrawableRes accessory: Int = 0
|
||||||
) {
|
) {
|
||||||
profileActionItem {
|
profileActionItem {
|
||||||
iconRes(icon)
|
iconRes(icon)
|
||||||
|
tintIcon(tintIcon)
|
||||||
id("action_$id")
|
id("action_$id")
|
||||||
subtitle(subtitle)
|
subtitle(subtitle)
|
||||||
editable(editable)
|
editable(editable)
|
||||||
editableRes?.let { editableRes(editableRes) }
|
editableRes?.let { editableRes(editableRes) }
|
||||||
destructive(destructive)
|
destructive(destructive)
|
||||||
title(title)
|
title(title)
|
||||||
|
accessoryRes(accessory)
|
||||||
listener { _ ->
|
listener { _ ->
|
||||||
action?.invoke()
|
action?.invoke()
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,12 @@ class RxConfig @Inject constructor(
|
||||||
fun setupRxPlugin() {
|
fun setupRxPlugin() {
|
||||||
RxJavaPlugins.setErrorHandler { throwable ->
|
RxJavaPlugins.setErrorHandler { throwable ->
|
||||||
Timber.e(throwable, "RxError")
|
Timber.e(throwable, "RxError")
|
||||||
|
// is InterruptedException -> fine, some blocking code was interrupted by a dispose call
|
||||||
// Avoid crash in production, except if user wants it
|
if (throwable !is InterruptedException) {
|
||||||
if (vectorPreferences.failFast()) {
|
// Avoid crash in production, except if user wants it
|
||||||
throw throwable
|
if (vectorPreferences.failFast()) {
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,7 +159,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
observeMyRoomMember()
|
observeMyRoomMember()
|
||||||
room.getRoomSummaryLive()
|
room.getRoomSummaryLive()
|
||||||
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback())
|
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
|
// Inform the SDK that the room is displayed
|
||||||
session.onRoomDisplayed(initialState.roomId)
|
session.onRoomDisplayed(initialState.roomId)
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
val queryParams = roomMemberQueryParams {
|
val queryParams = roomMemberQueryParams {
|
||||||
this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE)
|
this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE)
|
||||||
}
|
}
|
||||||
room.rx(session)
|
room.rx()
|
||||||
.liveRoomMembers(queryParams)
|
.liveRoomMembers(queryParams)
|
||||||
.map {
|
.map {
|
||||||
it.firstOrNull().toOptional()
|
it.firstOrNull().toOptional()
|
||||||
|
@ -255,7 +255,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeDrafts() {
|
private fun observeDrafts() {
|
||||||
room.rx(session).liveDrafts()
|
room.rx().liveDrafts()
|
||||||
.subscribe {
|
.subscribe {
|
||||||
Timber.d("Draft update --> SetState")
|
Timber.d("Draft update --> SetState")
|
||||||
setState {
|
setState {
|
||||||
|
@ -896,7 +896,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room.rx(session).liveRoomSummary()
|
room.rx().liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
val typingRoomMembers =
|
val typingRoomMembers =
|
||||||
|
@ -914,7 +914,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
Observable
|
Observable
|
||||||
.combineLatest<List<TimelineEvent>, RoomSummary, UnreadState>(
|
.combineLatest<List<TimelineEvent>, RoomSummary, UnreadState>(
|
||||||
timelineEvents.observeOn(Schedulers.computation()),
|
timelineEvents.observeOn(Schedulers.computation()),
|
||||||
room.rx(session).liveRoomSummary().unwrap(),
|
room.rx().liveRoomSummary().unwrap(),
|
||||||
BiFunction { timelineEvents, roomSummary ->
|
BiFunction { timelineEvents, roomSummary ->
|
||||||
computeUnreadState(timelineEvents, roomSummary)
|
computeUnreadState(timelineEvents, roomSummary)
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
private fun observeEvent() {
|
private fun observeEvent() {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
room.rx(session)
|
room.rx()
|
||||||
.liveTimelineEvent(eventId)
|
.liveTimelineEvent(eventId)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute {
|
.execute {
|
||||||
|
@ -144,7 +144,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
private fun observeReactions() {
|
private fun observeReactions() {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
room.rx(session)
|
room.rx()
|
||||||
.liveAnnotationSummary(eventId)
|
.liveAnnotationSummary(eventId)
|
||||||
.map { annotations ->
|
.map { annotations ->
|
||||||
EmojiDataSource.quickEmojis.map { emoji ->
|
EmojiDataSource.quickEmojis.map { emoji ->
|
||||||
|
|
|
@ -86,7 +86,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeEventAnnotationSummaries() {
|
private fun observeEventAnnotationSummaries() {
|
||||||
RxRoom(room, session)
|
RxRoom(room)
|
||||||
.liveAnnotationSummary(eventId)
|
.liveAnnotationSummary(eventId)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.flatMapSingle { summaries ->
|
.flatMapSingle { summaries ->
|
||||||
|
|
|
@ -54,7 +54,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia
|
||||||
|
|
||||||
private fun observeNotificationState() {
|
private fun observeNotificationState() {
|
||||||
room
|
room
|
||||||
.rx(session)
|
.rx()
|
||||||
.liveNotificationState()
|
.liveNotificationState()
|
||||||
.execute {
|
.execute {
|
||||||
copy(roomNotificationState = it)
|
copy(roomNotificationState = it)
|
||||||
|
@ -63,7 +63,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room
|
room
|
||||||
.rx(session)
|
.rx()
|
||||||
.liveRoomSummary()
|
.liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute {
|
.execute {
|
||||||
|
|
|
@ -97,6 +97,7 @@ class RoomMemberProfileController @Inject constructor(
|
||||||
dividerColor = dividerColor,
|
dividerColor = dividerColor,
|
||||||
editable = true,
|
editable = true,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
|
tintIcon = false,
|
||||||
divider = false,
|
divider = false,
|
||||||
action = { callback?.onShowDeviceList() }
|
action = { callback?.onShowDeviceList() }
|
||||||
)
|
)
|
||||||
|
|
|
@ -170,7 +170,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||||
val queryParams = roomMemberQueryParams {
|
val queryParams = roomMemberQueryParams {
|
||||||
this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE)
|
this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE)
|
||||||
}
|
}
|
||||||
room.rx(session).liveRoomMembers(queryParams)
|
room.rx().liveRoomMembers(queryParams)
|
||||||
.map { it.firstOrNull()?.toMatrixItem().toOptional() }
|
.map { it.firstOrNull()?.toMatrixItem().toOptional() }
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute {
|
.execute {
|
||||||
|
@ -193,8 +193,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummaryAndPowerLevels(room: Room) {
|
private fun observeRoomSummaryAndPowerLevels(room: Room) {
|
||||||
val roomSummaryLive = room.rx(session).liveRoomSummary().unwrap()
|
val roomSummaryLive = room.rx().liveRoomSummary().unwrap()
|
||||||
val powerLevelsContentLive = room.rx(session).liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
val powerLevelsContentLive = room.rx().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||||
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package im.vector.riotx.features.roomprofile
|
package im.vector.riotx.features.roomprofile
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
||||||
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
||||||
|
@ -79,11 +80,13 @@ class RoomProfileController @Inject constructor(
|
||||||
action = { callback?.onNotificationsClicked() }
|
action = { callback?.onNotificationsClicked() }
|
||||||
)
|
)
|
||||||
val numberOfMembers = roomSummary.joinedMembersCount ?: 0
|
val numberOfMembers = roomSummary.joinedMembersCount ?: 0
|
||||||
|
val hasWarning = roomSummary.isEncrypted && roomSummary.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Warning
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
id = "member_list",
|
id = "member_list",
|
||||||
title = stringProvider.getQuantityString(R.plurals.room_profile_section_more_member_list, numberOfMembers, numberOfMembers),
|
title = stringProvider.getQuantityString(R.plurals.room_profile_section_more_member_list, numberOfMembers, numberOfMembers),
|
||||||
dividerColor = dividerColor,
|
dividerColor = dividerColor,
|
||||||
icon = R.drawable.ic_room_profile_member_list,
|
icon = R.drawable.ic_room_profile_member_list,
|
||||||
|
accessory = R.drawable.ic_shield_warning.takeIf { hasWarning } ?: 0,
|
||||||
action = { callback?.onMemberListClicked() }
|
action = { callback?.onMemberListClicked() }
|
||||||
)
|
)
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
|
|
|
@ -56,7 +56,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room.rx(session).liveRoomSummary()
|
room.rx().liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute {
|
.execute {
|
||||||
copy(roomSummary = it)
|
copy(roomSummary = it)
|
||||||
|
|
|
@ -62,7 +62,7 @@ class RoomMemberListController @Inject constructor(
|
||||||
id(roomMember.userId)
|
id(roomMember.userId)
|
||||||
matrixItem(roomMember.toMatrixItem())
|
matrixItem(roomMember.toMatrixItem())
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
userEncryptionTrustLevel(roomMember.userEncryptionTrustLevel)
|
userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId))
|
||||||
clickListener { _ ->
|
clickListener { _ ->
|
||||||
callback?.onRoomMemberClicked(roomMember)
|
callback?.onRoomMemberClicked(roomMember)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
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.query.QueryStringValue
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.model.RoomMemberSummary
|
||||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsConstants
|
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsConstants
|
||||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
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.mapOptional
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.matrix.rx.unwrap
|
import im.vector.matrix.rx.unwrap
|
||||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState,
|
class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState,
|
||||||
private val roomMemberSummaryComparator: RoomMemberSummaryComparator,
|
private val roomMemberSummaryComparator: RoomMemberSummaryComparator,
|
||||||
|
@ -70,10 +75,11 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
||||||
displayName = QueryStringValue.IsNotEmpty
|
displayName = QueryStringValue.IsNotEmpty
|
||||||
memberships = Membership.activeMemberships()
|
memberships = Membership.activeMemberships()
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable
|
Observable
|
||||||
.combineLatest<List<RoomMemberSummary>, PowerLevelsContent, RoomMemberSummaries>(
|
.combineLatest<List<RoomMemberSummary>, PowerLevelsContent, RoomMemberSummaries>(
|
||||||
room.rx(session).liveRoomMembers(roomMemberQueryParams),
|
room.rx().liveRoomMembers(roomMemberQueryParams),
|
||||||
room.rx(session)
|
room.rx()
|
||||||
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||||
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -84,10 +90,36 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
copy(roomMemberSummaries = 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() {
|
private fun observeRoomSummary() {
|
||||||
room.rx(session).liveRoomSummary()
|
room.rx().liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
copy(roomSummary = async)
|
copy(roomSummary = async)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import androidx.annotation.StringRes
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
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.RoomMemberSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
@ -28,7 +29,8 @@ import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
||||||
data class RoomMemberListViewState(
|
data class RoomMemberListViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val roomSummary: Async<RoomSummary> = Uninitialized,
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized
|
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized,
|
||||||
|
val trustLevelMap: Async<Map<String, RoomEncryptionTrustLevel?>> = Uninitialized
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||||
|
|
|
@ -52,7 +52,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room.rx(session).liveRoomSummary()
|
room.rx().liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
copy(roomSummary = async)
|
copy(roomSummary = async)
|
||||||
|
|
|
@ -426,6 +426,15 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
|
|
||||||
private fun refreshMyDevice() {
|
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...
|
// TODO Move to a ViewModel...
|
||||||
session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
|
session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
|
||||||
override fun onSuccess(data: 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.
|
* It can be any mobile devices, and any browsers.
|
||||||
*/
|
*/
|
||||||
private fun refreshDevicesList() {
|
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 {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
// Keep known list if we have it, and let refresh go in backgroung
|
// 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_height="wrap_content"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
android:tint="?riotx_text_primary"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -60,13 +59,26 @@
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
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_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toEndOf="@id/actionIcon"
|
app:layout_constraintStart_toEndOf="@id/actionIcon"
|
||||||
app:layout_constraintTop_toBottomOf="@id/actionTitle"
|
app:layout_constraintTop_toBottomOf="@id/actionTitle"
|
||||||
app:layout_goneMarginStart="0dp"
|
app:layout_goneMarginStart="0dp"
|
||||||
tools:text="@string/room_profile_encrypted_subtitle" />
|
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
|
<ImageView
|
||||||
android:id="@+id/actionEditable"
|
android:id="@+id/actionEditable"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
Loading…
Reference in a new issue