From 320dc4accd8f09d88e1b26e9103b8c6a4190eb59 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Tue, 4 Feb 2020 18:35:46 +0100 Subject: [PATCH] Refactor Room Shield / Profile shield --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 95 +------------ .../java/im/vector/matrix/rx/RxSession.kt | 28 +--- .../crosssigning/CrossSigningService.kt | 2 +- .../session/room/model/RoomMemberSummary.kt | 4 +- .../android/internal/crypto/CryptoModule.kt | 18 +-- .../crypto/crosssigning/ComputeTrustTask.kt | 76 ++++++++++ .../DefaultCrossSigningService.kt | 46 ++---- .../SessionToCryptoRoomMembersUpdate.kt | 25 ++++ .../crypto/crosssigning/ShieldTrustUpdater.kt | 133 ++++++++++++++++++ .../crypto/store/db/RealmCryptoStore.kt | 11 +- .../mapper/RoomMemberSummaryMapper.kt | 3 +- .../database/mapper/RoomSummaryMapper.kt | 3 +- .../database/model/RoomSummaryEntity.kt | 18 ++- .../internal/session/DefaultSession.kt | 6 +- .../session/room/RoomSummaryUpdater.kt | 16 +++ .../core/epoxy/profiles/ProfileActionItem.kt | 21 ++- .../epoxy/profiles/ProfileItemExtensions.kt | 6 +- .../home/room/detail/RoomDetailViewModel.kt | 10 +- .../action/MessageActionsViewModel.kt | 4 +- .../reactions/ViewReactionsViewModel.kt | 2 +- .../actions/RoomListQuickActionsViewModel.kt | 4 +- .../RoomMemberProfileController.kt | 1 + .../RoomMemberProfileViewModel.kt | 6 +- .../roomprofile/RoomProfileController.kt | 3 + .../roomprofile/RoomProfileViewModel.kt | 2 +- .../members/RoomMemberListController.kt | 2 +- .../members/RoomMemberListViewModel.kt | 38 ++++- .../members/RoomMemberListViewState.kt | 4 +- .../settings/RoomSettingsViewModel.kt | 2 +- .../main/res/layout/item_profile_action.xml | 16 ++- 30 files changed, 404 insertions(+), 201 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ComputeTrustTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/SessionToCryptoRoomMembersUpdate.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index d6c05fb0f7..02a72777fe 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -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}") } + .startWith(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) } diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 31e1834d2b..c43616f574 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -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>> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index a9d7a9e241..2b1c48174e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -64,5 +64,5 @@ interface CrossSigningService { otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult - fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel + suspend fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberSummary.kt index d3b7f3f026..62f632b557 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberSummary.kt @@ -25,7 +25,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 ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 35ad49f42f..c30c673198 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -23,7 +23,10 @@ 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.crosssigning.ShieldTrustUpdater 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.DefaultCreateKeysBackupVersionTask @@ -137,15 +140,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 +243,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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ComputeTrustTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ComputeTrustTask.kt new file mode 100644 index 0000000000..b7eba11103 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ComputeTrustTask.kt @@ -0,0 +1,76 @@ +/* + * 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 userList: List<String> + ) +} + +internal class DefaultComputeTrustTask @Inject constructor( + val cryptoStore: IMXCryptoStore +) : ComputeTrustTask { + + override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel { + val userIds = params.userList + 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 + } + } + } + } + + private fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { + return cryptoStore.getCrossSigningInfo(otherUserId) + } + + private fun getMyCrossSigningKeys(): MXCrossSigningInfo? { + return cryptoStore.getMyCrossSigningInfo() + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 4632a05552..a7ad76e163 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -20,7 +20,6 @@ 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 @@ -39,9 +38,8 @@ import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.TaskExecutor 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 org.greenrobot.eventbus.EventBus import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmUtility import timber.log.Timber @@ -56,9 +54,9 @@ internal class DefaultCrossSigningService @Inject constructor( private val deviceListManager: DeviceListManager, private val uploadSigningKeysTask: UploadSigningKeysTask, private val uploadSignaturesTask: UploadSignaturesTask, - private val cryptoCoroutineScope: CoroutineScope, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val taskExecutor: TaskExecutor) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener { + private val computeTrustTask: ComputeTrustTask, + private val taskExecutor: TaskExecutor, + val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener { private var olmUtility: OlmUtility? = null @@ -630,13 +628,14 @@ 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(users)) } } private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { 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) { @@ -655,36 +654,7 @@ 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 - } - } - } + override suspend fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel { + return computeTrustTask.execute(ComputeTrustTask.Params(userIds)) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/SessionToCryptoRoomMembersUpdate.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/SessionToCryptoRoomMembersUpdate.kt new file mode 100644 index 0000000000..0ecc9d30ba --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/SessionToCryptoRoomMembersUpdate.kt @@ -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 encryptedRoomMembersUpdate: String, + val userList: List<String> +) + +data class CryptoToSessionUserTrustChange( + val userList: List<String> +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt new file mode 100644 index 0000000000..313681592f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt @@ -0,0 +1,133 @@ +/* + * 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( + val eventBus: EventBus, + private val computeTrustTask: ComputeTrustTask, + val taskExecutor: TaskExecutor, + @CryptoDatabase val cryptoRealmConfiguration: RealmConfiguration, + @SessionDatabase val sessionRealmConfiguration: RealmConfiguration, + 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.userList)) + // We need to send that back to session base + + BACKGROUND_HANDLER.post { + backgroundSessionRealm.get().executeTransaction { realm -> + roomSummaryUpdater.updateShieldTrust(realm, update.encryptedRoomMembersUpdate, updatedTrust) + } + } + } + } + + @Subscribe + fun onTrustUpdate(update: CryptoToSessionUserTrustChange) { + onCryptoDevicesChange(update.userList) + } + + 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) + } + } + } + } + + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 51adc46900..00a496cae4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -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,11 +71,13 @@ 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, - private val credentials: Credentials) : IMXCryptoStore { +internal class RealmCryptoStore @Inject constructor( + @CryptoDatabase private val realmConfiguration: RealmConfiguration, + private val credentials: Credentials) : IMXCryptoStore { /* ========================================================================================== * Memory cache, to correctly release JNI objects @@ -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() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberSummaryMapper.kt index 5e8db4cf84..470772a40e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberSummaryMapper.kt @@ -26,8 +26,7 @@ internal object RoomMemberSummaryMapper { userId = roomMemberSummaryEntity.userId, avatarUrl = roomMemberSummaryEntity.avatarUrl, displayName = roomMemberSummaryEntity.displayName, - membership = roomMemberSummaryEntity.membership, - userEncryptionTrustLevel = null + membership = roomMemberSummaryEntity.membership ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index 896f994c99..1089e3b5c2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -75,7 +75,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 ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index c56e9ba10e..c4ebe3cbaa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 537bc63355..77cd3685d7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -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> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 6a26e6d282..5df7fb5be5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -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.map { it } + userId)) + } + } + } + + fun updateShieldTrust(realm: Realm, + roomId: String, + trust: RoomEncryptionTrustLevel?) { + val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) + if (roomSummaryEntity.isEncrypted) { + roomSummaryEntity.roomEncryptionTrustLevel = trust } } } diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt index 27586fcfde..3c31c09c52 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt @@ -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) - 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 } 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) } } diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt index beea3ca620..693efc5418 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index c58270aff2..2bee55bce3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -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) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index dcaddd3c08..114d80d9af 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -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 -> diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index e3d78c27b3..05cdbc0fd8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -86,7 +86,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted } private fun observeEventAnnotationSummaries() { - RxRoom(room, session) + RxRoom(room) .liveAnnotationSummary(eventId) .unwrap() .flatMapSingle { summaries -> diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt index 3108d3eafb..e5c0e743b8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt index c77d575f01..70c3e8add2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt @@ -97,6 +97,7 @@ class RoomMemberProfileController @Inject constructor( dividerColor = dividerColor, editable = true, icon = icon, + tintIcon = false, divider = false, action = { callback?.onShowDeviceList() } ) diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt index 75aa0f8e5e..e44693e4d4 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -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() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileController.kt index dcc0e678d1..ab1774233a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileController.kt @@ -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( diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index b0c942cbb9..6c66ac67b2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -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) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt index bd3dd06bd3..d0939e939e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt @@ -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) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt index 9bf8f88518..bf701c2be0 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt @@ -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,12 +33,14 @@ 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 class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, @@ -70,11 +74,12 @@ 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) - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "") + room.rx().liveRoomMembers(roomMemberQueryParams), + room.rx() + .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) .mapOptional { it.content.toModel<PowerLevelsContent>() } .unwrap(), BiFunction { roomMembers, powerLevelsContent -> @@ -84,10 +89,35 @@ 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() + .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) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt index 467f194597..4618a07cb0 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt @@ -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) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt index 58bd9f228a..f1dee87005 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -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) diff --git a/vector/src/main/res/layout/item_profile_action.xml b/vector/src/main/res/layout/item_profile_action.xml index 2f43bef0a4..dfaf2daf80 100644 --- a/vector/src/main/res/layout/item_profile_action.xml +++ b/vector/src/main/res/layout/item_profile_action.xml @@ -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"