From 562cfce9e2bd876d3f4b4addde099c1937d9e275 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Thu, 30 Jul 2020 12:08:30 +0200
Subject: [PATCH 1/3] Support HS admin option to disable E2EE for DMs

Fixes #1794
---
 .../matrix/android/api/auth/data/WellKnown.kt | 12 +++++++-
 .../homeserver/HomeServerCapabilities.kt      |  7 ++++-
 .../database/RealmSessionStoreMigration.kt    | 11 +++++++
 .../SessionRealmConfigurationFactory.kt       |  2 +-
 .../mapper/HomeServerCapabilitiesMapper.kt    |  3 +-
 .../model/HomeServerCapabilitiesEntity.kt     |  1 +
 .../DefaultGetHomeServerCapabilitiesTask.kt   |  4 ++-
 .../createdirect/CreateDirectRoomViewModel.kt | 10 ++++++-
 .../createdirect/CreateDirectRoomViewState.kt |  3 +-
 .../features/media/ImageContentRenderer.kt    |  2 +-
 .../createroom/CreateRoomController.kt        |  5 +++-
 .../createroom/CreateRoomViewModel.kt         | 16 +++++++++-
 .../createroom/CreateRoomViewState.kt         |  1 +
 .../features/settings/VectorPreferences.kt    |  2 ++
 .../VectorSettingsSecurityPrivacyFragment.kt  | 30 +++++++++++++++++++
 .../userdirectory/KnownUsersFragment.kt       | 11 ++++++-
 .../main/res/layout/fragment_known_users.xml  | 17 ++++++++++-
 vector/src/main/res/values/strings.xml        |  2 +-
 .../xml/vector_settings_security_privacy.xml  | 15 ++++++++--
 19 files changed, 138 insertions(+), 16 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt
index 93067d8ebb..7885a05e9b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt
@@ -53,5 +53,15 @@ data class WellKnown(
         val identityServer: WellKnownBaseConfig? = null,
 
         @Json(name = "m.integrations")
-        val integrations: JsonDict? = null
+        val integrations: JsonDict? = null,
+
+        @Json(name = "im.vector.riot.e2ee")
+        val e2eAdminSetting: E2EWellKnownConfig? = null
+
+)
+
+@JsonClass(generateAdapter = true)
+data class E2EWellKnownConfig(
+        @Json(name = "default")
+        val e2eDefault: Boolean = true
 )
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt
index 1c2b8de83b..148d75282e 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt
@@ -32,7 +32,12 @@ data class HomeServerCapabilities(
         /**
          * Default identity server url, provided in Wellknown
          */
-        val defaultIdentityServerUrl: String? = null
+        val defaultIdentityServerUrl: String? = null,
+        /**
+         * Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms
+         * (as it was before) for various environments where this is desired.
+         */
+        val adminE2EByDefault: Boolean = true
 ) {
     companion object {
         const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmSessionStoreMigration.kt
index 5204eef878..142d0d119f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmSessionStoreMigration.kt
@@ -16,6 +16,7 @@
 
 package im.vector.matrix.android.internal.database
 
+import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntityFields
 import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
 import io.realm.DynamicRealm
 import io.realm.RealmMigration
@@ -28,6 +29,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
         Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
 
         if (oldVersion <= 0) migrateTo1(realm)
+        if (oldVersion <= 1) migrateTo2(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -40,4 +42,13 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
                     obj.setBoolean(RoomSummaryEntityFields.HAS_FAILED_SENDING, false)
                 }
     }
+
+    private fun migrateTo2(realm: DynamicRealm) {
+        Timber.d("Step 1 -> 2")
+        realm.schema.get("HomeServerCapabilitiesEntity")
+                ?.addField(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, Boolean::class.java)
+                ?.transform { obj ->
+                    obj.setBoolean(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, true)
+                }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt
index f11f001e1c..416e169022 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt
@@ -46,7 +46,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
         context: Context) {
 
     companion object {
-        const val SESSION_STORE_SCHEMA_VERSION = 1L
+        const val SESSION_STORE_SCHEMA_VERSION = 2L
     }
 
     private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index a66f587cec..82a5ba79d1 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -29,7 +29,8 @@ internal object HomeServerCapabilitiesMapper {
                 canChangePassword = entity.canChangePassword,
                 maxUploadFileSize = entity.maxUploadFileSize,
                 lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
-                defaultIdentityServerUrl = entity.defaultIdentityServerUrl
+                defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
+                adminE2EByDefault = entity.adminE2EByDefault
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt
index a6b250b8fa..9dd150c6f0 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -24,6 +24,7 @@ internal open class HomeServerCapabilitiesEntity(
         var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
         var lastVersionIdentityServerSupported: Boolean = false,
         var defaultIdentityServerUrl: String? = null,
+        var adminE2EByDefault: Boolean = true,
         var lastUpdatedTimestamp: Long = 0L
 ) : RealmObject() {
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
index b26bbe7c5c..865b2513a8 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
@@ -108,13 +108,15 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
 
             if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
                 homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
-
+                homeServerCapabilitiesEntity.adminE2EByDefault = getWellknownResult.wellKnown.e2eAdminSetting?.e2eDefault ?: true
                 // We are also checking for integration manager configurations
                 val config = configExtractor.extract(getWellknownResult.wellKnown)
                 if (config != null) {
                     Timber.v("Extracted integration config : $config")
                     realm.insertOrUpdate(config)
                 }
+            } else {
+                homeServerCapabilitiesEntity.adminE2EByDefault = true
             }
             homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
         }
diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt
index 319671b230..b93993bc53 100644
--- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt
@@ -38,6 +38,14 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
         fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel
     }
 
+    init {
+        setState {
+            copy(
+                    hsAdminHasDisabledE2E = !session.getHomeServerCapabilities().adminE2EByDefault
+            )
+        }
+    }
+
     companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
 
         @JvmStatic
@@ -63,7 +71,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
                         }.exhaustive
                     }
                     setDirectMessage()
-                    enableEncryptionIfInvitedUsersSupportIt = true
+                    enableEncryptionIfInvitedUsersSupportIt = session.getHomeServerCapabilities().adminE2EByDefault
                 }
 
         session.rx()
diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt
index 8bb8c3ce58..ff2253cd66 100644
--- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt
+++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt
@@ -21,5 +21,6 @@ import com.airbnb.mvrx.MvRxState
 import com.airbnb.mvrx.Uninitialized
 
 data class CreateDirectRoomViewState(
-    val createAndInviteState: Async<String> = Uninitialized
+    val createAndInviteState: Async<String> = Uninitialized,
+    val hsAdminHasDisabledE2E: Boolean = false
 ) : MvRxState
diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt
index f9cb6ec3dc..926cef0668 100644
--- a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt
+++ b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt
@@ -209,7 +209,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
                 Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE)
             }
             // Fallback to base url
-                    ?: data.url
+                    ?: data.url.takeIf { it?.startsWith("content://") == true }
 
             GlideApp
                     .with(imageView)
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt
index 92e178c628..13649c113f 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt
@@ -103,7 +103,10 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
             id("encryption")
             enabled(enableFormElement)
             title(stringProvider.getString(R.string.create_room_encryption_title))
-            summary(stringProvider.getString(R.string.create_room_encryption_description))
+            summary(
+                    if (viewState.hsAdminHasDisabledE2E) stringProvider.getString(R.string.settings_hs_admin_e2e_disabled)
+                    else stringProvider.getString(R.string.create_room_encryption_description)
+            )
             switchChecked(viewState.isEncrypted)
 
             listener { value ->
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt
index b75e9444fe..e2d51dff83 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt
@@ -43,6 +43,15 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
         fun create(initialState: CreateRoomViewState): CreateRoomViewModel
     }
 
+    init {
+        setState {
+            copy(
+                    isEncrypted = !this.isPublic && session.getHomeServerCapabilities().adminE2EByDefault,
+                    hsAdminHasDisabledE2E = !session.getHomeServerCapabilities().adminE2EByDefault
+            )
+        }
+    }
+
     companion object : MvRxViewModelFactory<CreateRoomViewModel, CreateRoomViewState> {
 
         @JvmStatic
@@ -69,7 +78,12 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
 
     private fun setName(action: CreateRoomAction.SetName) = setState { copy(roomName = action.name) }
 
-    private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState { copy(isPublic = action.isPublic) }
+    private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState {
+        copy(
+                isPublic = action.isPublic,
+                isEncrypted = !action.isPublic && session.getHomeServerCapabilities().adminE2EByDefault
+        )
+    }
 
     private fun setIsInRoomDirectory(action: CreateRoomAction.SetIsInRoomDirectory) = setState { copy(isInRoomDirectory = action.isInRoomDirectory) }
 
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt
index 810319d54f..1758f77818 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt
@@ -25,5 +25,6 @@ data class CreateRoomViewState(
         val isPublic: Boolean = false,
         val isInRoomDirectory: Boolean = false,
         val isEncrypted: Boolean = false,
+        val hsAdminHasDisabledE2E: Boolean = false,
         val asyncCreateRoomRequest: Async<String> = Uninitialized
 ) : MvRxState
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
index c9284cf132..45558abe69 100755
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
@@ -72,6 +72,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
         const val SETTINGS_ALLOW_INTEGRATIONS_KEY = "SETTINGS_ALLOW_INTEGRATIONS_KEY"
         const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY"
         const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY"
+
+        const val SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT = "SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT"
 //        const val SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY = "SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY"
 
         // user
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
index 9d71c1712e..b60c2cb3a7 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
@@ -20,6 +20,9 @@ package im.vector.riotx.features.settings
 import android.annotation.SuppressLint
 import android.app.Activity
 import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup
 import android.widget.Button
 import android.widget.TextView
 import androidx.appcompat.app.AlertDialog
@@ -28,6 +31,7 @@ import androidx.core.view.isVisible
 import androidx.preference.Preference
 import androidx.preference.PreferenceCategory
 import androidx.preference.SwitchPreference
+import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.textfield.TextInputEditText
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
@@ -55,6 +59,7 @@ import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet
 import im.vector.riotx.features.themes.ThemeUtils
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.Disposable
+import me.gujun.android.span.span
 import javax.inject.Inject
 
 class VectorSettingsSecurityPrivacyFragment @Inject constructor(
@@ -96,6 +101,15 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
         findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY)!!
     }
 
+    override fun onCreateRecyclerView(inflater: LayoutInflater?, parent: ViewGroup?, savedInstanceState: Bundle?): RecyclerView {
+        return super.onCreateRecyclerView(inflater, parent, savedInstanceState).also {
+            // Insert animation are really annoying the first time the list is shown
+            // due to the way preference fragment is done, it's not trivial to disable it for first appearance only..
+            // And it's not that an issue that this list is not animated, it's pretty static
+            it.itemAnimator = null
+        }
+    }
+
     override fun onResume() {
         super.onResume()
         // My device name may have been updated
@@ -109,6 +123,9 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
                 }.also {
                     disposables.add(it)
                 }
+
+        val e2eByDefault = session.getHomeServerCapabilities().adminE2EByDefault
+        findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible = !e2eByDefault
     }
 
     private val secureBackupCategory by lazy {
@@ -220,6 +237,19 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
             ThemeUtils.tintDrawable(it,
                     ContextCompat.getDrawable(it, R.drawable.ic_secure_backup)!!, R.attr.vctr_settings_icon_tint_color)
         }
+
+        findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.let {
+            it.icon = ThemeUtils.tintDrawableWithColor(
+                    ContextCompat.getDrawable(requireContext(), R.drawable.ic_notification_privacy_warning)!!,
+                    ContextCompat.getColor(requireContext(), R.color.riotx_destructive_accent)
+            )
+            it.summary = span {
+                text = getString(R.string.settings_hs_admin_e2e_disabled)
+                textColor = ContextCompat.getColor(requireContext(), R.color.riotx_destructive_accent)
+            }
+
+            it.isVisible = session.getHomeServerCapabilities().adminE2EByDefault
+        }
     }
 
     // Todo this should be refactored and use same state as 4S section
diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt
index 671c0b0ee1..603b6e2b29 100644
--- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt
@@ -22,6 +22,7 @@ import android.view.MenuItem
 import android.view.View
 import android.widget.ScrollView
 import androidx.core.view.forEach
+import androidx.core.view.isVisible
 import com.airbnb.mvrx.activityViewModel
 import com.airbnb.mvrx.args
 import com.airbnb.mvrx.withState
@@ -35,6 +36,8 @@ import im.vector.riotx.core.extensions.hideKeyboard
 import im.vector.riotx.core.extensions.setupAsSearch
 import im.vector.riotx.core.platform.VectorBaseFragment
 import im.vector.riotx.core.utils.DimensionConverter
+import im.vector.riotx.features.createdirect.CreateDirectRoomViewModel
+import im.vector.riotx.features.createdirect.CreateDirectRoomViewState
 import kotlinx.android.synthetic.main.fragment_known_users.*
 import javax.inject.Inject
 
@@ -51,6 +54,8 @@ class KnownUsersFragment @Inject constructor(
     override fun getMenuRes() = args.menuResId
 
     private val viewModel: UserDirectoryViewModel by activityViewModel()
+    private val createDMViewModel: CreateDirectRoomViewModel by activityViewModel()
+
     private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -58,13 +63,17 @@ class KnownUsersFragment @Inject constructor(
         sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
 
         knownUsersTitle.text = args.title
-
         vectorBaseActivity.setSupportActionBar(knownUsersToolbar)
         setupRecyclerView()
         setupFilterView()
         setupAddByMatrixIdView()
         setupAddFromPhoneBookView()
         setupCloseView()
+
+        createDMViewModel.selectSubscribe(this, CreateDirectRoomViewState::hsAdminHasDisabledE2E) {
+            knownUsersE2EbyDefaultDisabled.isVisible = it
+        }
+
         viewModel.selectSubscribe(this, UserDirectoryViewState::pendingInvitees) {
             renderSelectedUsers(it)
         }
diff --git a/vector/src/main/res/layout/fragment_known_users.xml b/vector/src/main/res/layout/fragment_known_users.xml
index 82ddea5323..2b8ee68418 100644
--- a/vector/src/main/res/layout/fragment_known_users.xml
+++ b/vector/src/main/res/layout/fragment_known_users.xml
@@ -106,6 +106,21 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/knownUsersFilter" />
 
+
+        <TextView
+            android:id="@+id/knownUsersE2EbyDefaultDisabled"
+            android:visibility="gone"
+            tools:visibility="visible"
+            android:layout_margin="16dp"
+            app:layout_constraintTop_toBottomOf="@id/knownUsersFilterDivider"
+            android:text="@string/settings_hs_admin_e2e_disabled"
+            android:textColor="@color/riotx_destructive_accent"
+            android:drawableLeft="@drawable/ic_warning_badge"
+            android:drawablePadding="8dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+
         <com.google.android.material.button.MaterialButton
             android:id="@+id/addByMatrixId"
             style="@style/VectorButtonStyleText"
@@ -121,7 +136,7 @@
             app:iconPadding="13dp"
             app:iconTint="@color/riotx_accent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/knownUsersFilterDivider" />
+            app:layout_constraintTop_toBottomOf="@id/knownUsersE2EbyDefaultDisabled" />
 
         <com.google.android.material.button.MaterialButton
             android:id="@+id/addFromPhoneBook"
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 1411b59b9a..6dc4faab27 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -2201,7 +2201,7 @@ Not all features in Riot are implemented in Element yet. Main missing (and comin
     <string name="encryption_information_dg_xsigning_not_trusted">Cross-Signing is enabled.\nKeys are not trusted</string>
     <string name="encryption_information_dg_xsigning_disabled">Cross-Signing is not enabled</string>
 
-
+    <string name="settings_hs_admin_e2e_disabled">Your server admin has disabled end-to-end encryption by default in private rooms &amp; Direct Messages.</string>
     <string name="settings_active_sessions_list">Active Sessions</string>
     <string name="settings_active_sessions_show_all">Show All Sessions</string>
     <string name="settings_active_sessions_manage">Manage Sessions</string>
diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml
index 9bfe5e944b..5f249fb358 100644
--- a/vector/src/main/res/xml/vector_settings_security_privacy.xml
+++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml
@@ -3,6 +3,16 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools">
 
+    <im.vector.riotx.core.preference.VectorPreference
+        android:icon="@drawable/ic_notification_privacy_warning"
+        android:key="SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT"
+        android:persistent="false"
+        tools:summary="@string/settings_hs_admin_e2e_disabled"
+        app:isPreferenceVisible="false"
+        tools:isPreferenceVisible="true">
+
+    </im.vector.riotx.core.preference.VectorPreference>
+
     <!-- ************ Cryptography section ************ -->
     <im.vector.riotx.core.preference.VectorPreferenceCategory
         android:key="SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY"
@@ -53,9 +63,9 @@
         android:title="@string/settings_secure_backup_section_title">
 
         <im.vector.riotx.core.preference.VectorPreference
+            android:icon="@drawable/ic_secure_backup"
             android:key="SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY"
             android:persistent="false"
-            android:icon="@drawable/ic_secure_backup"
             android:title="@string/settings_secure_backup_setup" />
 
         <im.vector.riotx.core.preference.VectorPreference
@@ -99,8 +109,7 @@
 
     </im.vector.riotx.core.preference.VectorPreferenceCategory>
 
-    <im.vector.riotx.core.preference.VectorPreferenceCategory
-        android:title="@string/settings_other">
+    <im.vector.riotx.core.preference.VectorPreferenceCategory android:title="@string/settings_other">
 
         <im.vector.riotx.core.preference.VectorSwitchPreference
             android:defaultValue="false"

From 1204f6d9ce33011700b7d92612fbefcd3951d0a7 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Thu, 30 Jul 2020 15:25:10 +0200
Subject: [PATCH 2/3] UX feedback, reduce severity of message

---
 .../main/res/layout/fragment_known_users.xml   | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/res/layout/fragment_known_users.xml b/vector/src/main/res/layout/fragment_known_users.xml
index 2b8ee68418..760fd8eb5b 100644
--- a/vector/src/main/res/layout/fragment_known_users.xml
+++ b/vector/src/main/res/layout/fragment_known_users.xml
@@ -109,16 +109,16 @@
 
         <TextView
             android:id="@+id/knownUsersE2EbyDefaultDisabled"
-            android:visibility="gone"
-            tools:visibility="visible"
-            android:layout_margin="16dp"
-            app:layout_constraintTop_toBottomOf="@id/knownUsersFilterDivider"
-            android:text="@string/settings_hs_admin_e2e_disabled"
-            android:textColor="@color/riotx_destructive_accent"
-            android:drawableLeft="@drawable/ic_warning_badge"
-            android:drawablePadding="8dp"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:drawablePadding="8dp"
+            android:text="@string/settings_hs_admin_e2e_disabled"
+            android:textColor="?riotx_text_secondary"
+            android:textSize="14sp"
+            android:visibility="gone"
+            app:layout_constraintTop_toBottomOf="@id/knownUsersFilterDivider"
+            tools:visibility="visible" />
 
 
         <com.google.android.material.button.MaterialButton

From ccb466edbeed7606eac8c9afcf43fac1d1b673f5 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Thu, 30 Jul 2020 16:57:25 +0200
Subject: [PATCH 3/3] Update change log

---
 CHANGES.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGES.md b/CHANGES.md
index 6ae8eb3809..7419079881 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,7 +2,7 @@ Changes in Element 1.0.3 (2020-XX-XX)
 ===================================================
 
 Features ✨:
- -
+ - Support server admin option to disable E2EE for DMs / private rooms [users can still enable] (#1794)
 
 Improvements 🙌:
  -