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"