diff --git a/CHANGES.md b/CHANGES.md
index 086b8f49e3..756b3b23fc 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,7 +7,6 @@ Features ✨:
  - Cross-Signing | Verify new session from existing session (#1134)
  - Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
 
-
 Improvements πŸ™Œ:
  - Verification DM / Handle concurrent .start after .ready (#794)
  - Reimplementation of multiple attachment picker
@@ -21,6 +20,7 @@ Improvements πŸ™Œ:
  - Cross-Sign | QR code scan confirmation screens design update (#1187)
  - Emoji Verification | It's not the same butterfly! (#1220)
  - Cross-Signing | Composer decoration: shields (#1077)
+ - Cross-Signing | Migrate existing keybackup to cross signing with 4S from mobile (#1197)
 
 Bugfix πŸ›:
  - Fix summary notification staying after "mark as read"
@@ -34,6 +34,7 @@ Bugfix πŸ›:
  - Local echo are not updated in timeline (for failed & encrypted states)
  - Render image event even if thumbnail_info does not have mimetype defined (#1209)
  - RiotX now uses as many threads as it needs to do work and send messages (#1221)
+ - Fix issue with media path (#1227)
 
 Translations πŸ—£:
  -
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
index 1a0723c725..cbd175f53f 100644
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
@@ -71,7 +71,7 @@ class QuadSTests : InstrumentedTest {
         val TEST_KEY_ID = "my.test.Key"
 
         mTestHelper.doSync<SsssKeyCreationInfo> {
-            quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
+            quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
         }
 
         // Assert Account data is updated
@@ -177,7 +177,7 @@ class QuadSTests : InstrumentedTest {
         val TEST_KEY_ID = "my.test.Key"
 
         mTestHelper.doSync<SsssKeyCreationInfo> {
-            quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
+            quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
         }
 
         // Test that we don't need to wait for an account data sync to access directly the keyid from DB
@@ -322,7 +322,7 @@ class QuadSTests : InstrumentedTest {
         val quadS = session.sharedSecretStorageService
 
         val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
-            quadS.generateKey(keyId, keyId, emptyKeySigner, it)
+            quadS.generateKey(keyId, null, keyId, emptyKeySigner, it)
         }
 
         assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt
index 9ba1631aec..6ed0b92e38 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt
@@ -26,6 +26,11 @@ interface ContentUrlResolver {
         SCALE("scale")
     }
 
+    /**
+     * URL to use to upload content
+     */
+    val uploadUrl: String
+
     /**
      * Get the actual URL for accessing the full-size image of a Matrix media content URI.
      *
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keysbackup/KeysBackupService.kt
index e9ed36ba23..9ad39d45fd 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keysbackup/KeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keysbackup/KeysBackupService.kt
@@ -217,4 +217,6 @@ interface KeysBackupService {
     // For gossiping
     fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
     fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
+
+    fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>)
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt
index 02ba9eae6e..4bbd821819 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt
@@ -29,7 +29,8 @@ data class MessageLocationContent(
         @Json(name = "msgtype") override val msgType: String,
 
         /**
-         * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'.
+         * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind
+         * of content description for accessibility e.g. 'location attachment'.
          */
         @Json(name = "body") override val body: String,
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt
index d32e459dd6..5fd0975f1a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt
@@ -35,12 +35,14 @@ interface SharedSecretStorageService {
      * Use the SsssKeyCreationInfo object returned by the callback to get more information about the created key (recovery key ...)
      *
      * @param keyId the ID of the key
+     * @param key keep null if you want to generate a random key
      * @param keyName a human readable name
      * @param keySigner Used to add a signature to the key (client should check key signature before storing secret)
      *
      * @param callback Get key creation info
      */
     fun generateKey(keyId: String,
+                    key: SsssKeySpec?,
                     keyName: String,
                     keySigner: KeySigner?,
                     callback: MatrixCallback<SsssKeyCreationInfo>)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt
index 75e37d27f6..9245f77317 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt
@@ -1100,6 +1100,16 @@ internal class DefaultKeysBackupService @Inject constructor(
         return true
     }
 
+    override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) {
+        val safeKeysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) }
+
+        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
+            isValidRecoveryKeyForKeysBackupVersion(recoveryKey, safeKeysBackupVersion).let {
+                callback.onSuccess(it)
+            }
+        }
+    }
+
     /**
      * Enable backing up of keys.
      * This method will update the state and will start sending keys in nominal case
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt
index 3b267280e5..f0c0ada207 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt
@@ -29,7 +29,8 @@ data class CreateKeysBackupVersionBody(
         override val algorithm: String? = null,
 
         /**
-         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
+         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2"
+         * see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
          */
         @Json(name = "auth_data")
         override val authData: JsonDict? = null
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt
index 0addd1491e..ba5cb2a379 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt
@@ -29,7 +29,8 @@ data class KeysVersionResult(
         override val algorithm: String? = null,
 
         /**
-         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
+         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2"
+         * see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
          */
         @Json(name = "auth_data")
         override val authData: JsonDict? = null,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt
index 9d88af20ef..bb12911e42 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt
@@ -29,7 +29,8 @@ data class UpdateKeysBackupVersionBody(
         override val algorithm: String? = null,
 
         /**
-         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
+         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2"
+         * see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
          */
         @Json(name = "auth_data")
         override val authData: JsonDict? = null,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
index 62bc4774c6..649a5a118f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
@@ -65,14 +65,16 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
 ) : SharedSecretStorageService {
 
     override fun generateKey(keyId: String,
+                             key: SsssKeySpec?,
                              keyName: String,
                              keySigner: KeySigner?,
                              callback: MatrixCallback<SsssKeyCreationInfo>) {
         cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-            val key = try {
-                ByteArray(32).also {
-                    SecureRandom().nextBytes(it)
-                }
+            val bytes = try {
+                (key as? RawBytesKeySpec)?.privateKey
+                        ?: ByteArray(32).also {
+                            SecureRandom().nextBytes(it)
+                        }
             } catch (failure: Throwable) {
                 callback.onFailure(failure)
                 return@launch
@@ -102,8 +104,8 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                             callback.onSuccess(SsssKeyCreationInfo(
                                     keyId = keyId,
                                     content = storageKeyContent,
-                                    recoveryKey = computeRecoveryKey(key),
-                                    keySpec = RawBytesKeySpec(key)
+                                    recoveryKey = computeRecoveryKey(bytes),
+                                    keySpec = RawBytesKeySpec(bytes)
                             ))
                         }
                     }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt
index 77dcc483bd..04a3560223 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt
@@ -82,6 +82,7 @@ import im.vector.matrix.android.internal.di.DeviceId
 import im.vector.matrix.android.internal.di.UserId
 import im.vector.matrix.android.internal.session.SessionScope
 import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import timber.log.Timber
@@ -102,7 +103,8 @@ internal class DefaultVerificationService @Inject constructor(
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory,
         private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
-        private val crossSigningService: CrossSigningService
+        private val crossSigningService: CrossSigningService,
+        private val cryptoCoroutineScope: CoroutineScope
 ) : DefaultVerificationTransaction.Listener, VerificationService {
 
     private val uiHandler = Handler(Looper.getMainLooper())
@@ -125,7 +127,7 @@ internal class DefaultVerificationService @Inject constructor(
 
     // Event received from the sync
     fun onToDeviceEvent(event: Event) {
-        GlobalScope.launch(coroutineDispatchers.crypto) {
+        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
             when (event.getClearType()) {
                 EventType.KEY_VERIFICATION_START         -> {
                     onStartRequestReceived(event)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt
index 2b049e0061..95376fb0cc 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt
@@ -30,6 +30,10 @@ import im.vector.matrix.android.internal.worker.getSessionComponent
 import timber.log.Timber
 import javax.inject.Inject
 
+/**
+ * Possible previous worker: None
+ * Possible next worker    : None
+ */
 internal class SendVerificationMessageWorker(context: Context,
                                              params: WorkerParameters)
     : CoroutineWorker(context, params) {
@@ -48,7 +52,7 @@ internal class SendVerificationMessageWorker(context: Context,
     lateinit var cryptoService: CryptoService
 
     override suspend fun doWork(): Result {
-        val errorOutputData = Data.Builder().putBoolean("failed", true).build()
+        val errorOutputData = Data.Builder().putBoolean(OUTPUT_KEY_FAILED, true).build()
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.success(errorOutputData)
 
@@ -76,4 +80,12 @@ internal class SendVerificationMessageWorker(context: Context,
             }
         }
     }
+
+    companion object {
+        private const val OUTPUT_KEY_FAILED = "failed"
+
+        fun hasFailed(outputData: Data): Boolean {
+            return outputData.getBoolean(SendVerificationMessageWorker.OUTPUT_KEY_FAILED, false)
+        }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt
index 75ffa5e082..c5c4772c51 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt
@@ -34,6 +34,9 @@ internal interface VerificationTransport {
                         onErrorReason: CancelCode,
                         onDone: (() -> Unit)?)
 
+    /**
+     * @param callback will be called with eventId and ValidVerificationInfoRequest in case of success
+     */
     fun sendVerificationRequest(supportedMethods: List<String>,
                                 localId: String,
                                 otherUserId: String,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt
index b7b7335011..77234e82f4 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt
@@ -115,7 +115,7 @@ internal class VerificationTransportRoomMessage(
                         ?.filter { it.state == WorkInfo.State.SUCCEEDED }
                         ?.firstOrNull { it.id == enqueueInfo.second }
                         ?.let { wInfo ->
-                            if (wInfo.outputData.getBoolean("failed", false)) {
+                            if (SendVerificationMessageWorker.hasFailed(wInfo.outputData)) {
                                 Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
                                 tx?.cancel(onErrorReason)
                             } else {
@@ -196,12 +196,15 @@ internal class VerificationTransportRoomMessage(
                         ?.filter { it.state == WorkInfo.State.SUCCEEDED }
                         ?.firstOrNull { it.id == workRequest.id }
                         ?.let { wInfo ->
-                            if (wInfo.outputData.getBoolean("failed", false)) {
+                            if (SendVerificationMessageWorker.hasFailed(wInfo.outputData)) {
                                 callback(null, null)
-                            } else if (wInfo.outputData.getString(localId) != null) {
-                                callback(wInfo.outputData.getString(localId), validInfo)
                             } else {
-                                callback(null, null)
+                                val eventId = wInfo.outputData.getString(localId)
+                                if (eventId != null) {
+                                    callback(eventId, validInfo)
+                                } else {
+                                    callback(null, null)
+                                }
                             }
                             workLiveData.removeObserver(this)
                         }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/account/AccountAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/account/AccountAPI.kt
index 8acfb3abb8..23d8210e89 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/account/AccountAPI.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/account/AccountAPI.kt
@@ -20,7 +20,6 @@ import im.vector.matrix.android.api.session.account.model.ChangePasswordParams
 import im.vector.matrix.android.internal.network.NetworkConstants
 import retrofit2.Call
 import retrofit2.http.Body
-import retrofit2.http.Headers
 import retrofit2.http.POST
 
 internal interface AccountAPI {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt
index f288f949cd..df12ad6131 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt
@@ -18,46 +18,46 @@ package im.vector.matrix.android.internal.session.content
 
 import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
 import im.vector.matrix.android.api.session.content.ContentUrlResolver
+import im.vector.matrix.android.internal.network.NetworkConstants
 import javax.inject.Inject
 
 private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
-private const val URI_PREFIX_CONTENT_API = "_matrix/media/v1/"
 
-internal class DefaultContentUrlResolver @Inject constructor(private val homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
+internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
 
-    companion object {
-        fun getUploadUrl(homeServerConnectionConfig: HomeServerConnectionConfig): String {
-            val baseUrl = homeServerConnectionConfig.homeServerUri.toString()
-            val sep = if (baseUrl.endsWith("/")) "" else "/"
+    private val baseUrl = homeServerConnectionConfig.homeServerUri.toString()
+    private val sep = if (baseUrl.endsWith("/")) "" else "/"
 
-            return baseUrl + sep + URI_PREFIX_CONTENT_API + "upload"
-        }
-    }
+    override val uploadUrl = baseUrl + sep + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
 
     override fun resolveFullSize(contentUrl: String?): String? {
-        if (contentUrl?.isValidMatrixContentUrl() == true) {
-            val baseUrl = homeServerConnectionConfig.homeServerUri.toString()
-            val prefix = URI_PREFIX_CONTENT_API + "download/"
-            return resolve(baseUrl, contentUrl, prefix)
-        }
-        return null
+        return contentUrl
+                // do not allow non-mxc content URLs
+                ?.takeIf { it.isValidMatrixContentUrl() }
+                ?.let {
+                    resolve(
+                            contentUrl = it,
+                            prefix = NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "download/"
+                    )
+                }
     }
 
     override fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ContentUrlResolver.ThumbnailMethod): String? {
-        if (contentUrl?.isValidMatrixContentUrl() == true) {
-            val baseUrl = homeServerConnectionConfig.homeServerUri.toString()
-            val prefix = URI_PREFIX_CONTENT_API + "thumbnail/"
-            val params = "?width=$width&height=$height&method=${method.value}"
-            return resolve(baseUrl, contentUrl, prefix, params)
-        }
-        // do not allow non-mxc content URLs
-        return null
+        return contentUrl
+                // do not allow non-mxc content URLs
+                ?.takeIf { it.isValidMatrixContentUrl() }
+                ?.let {
+                    resolve(
+                            contentUrl = it,
+                            prefix = NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "thumbnail/",
+                            params = "?width=$width&height=$height&method=${method.value}"
+                    )
+                }
     }
 
-    private fun resolve(baseUrl: String,
-                        contentUrl: String,
+    private fun resolve(contentUrl: String,
                         prefix: String,
-                        params: String? = null): String? {
+                        params: String = ""): String? {
         var serverAndMediaId = contentUrl.removePrefix(MATRIX_CONTENT_URI_SCHEME)
         val fragmentOffset = serverAndMediaId.indexOf("#")
         var fragment = ""
@@ -66,9 +66,7 @@ internal class DefaultContentUrlResolver @Inject constructor(private val homeSer
             serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
         }
 
-        val sep = if (baseUrl.endsWith("/")) "" else "/"
-
-        return baseUrl + sep + prefix + serverAndMediaId + (params ?: "") + fragment
+        return baseUrl + sep + prefix + serverAndMediaId + params + fragment
     }
 
     private fun String.isValidMatrixContentUrl(): Boolean {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
index 4fa0cb5013..1153b39b0a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
@@ -17,7 +17,7 @@
 package im.vector.matrix.android.internal.session.content
 
 import com.squareup.moshi.Moshi
-import im.vector.matrix.android.api.auth.data.SessionParams
+import im.vector.matrix.android.api.session.content.ContentUrlResolver
 import im.vector.matrix.android.internal.di.Authenticated
 import im.vector.matrix.android.internal.network.ProgressRequestBody
 import im.vector.matrix.android.internal.network.awaitResponse
@@ -37,10 +37,10 @@ import javax.inject.Inject
 internal class FileUploader @Inject constructor(@Authenticated
                                                 private val okHttpClient: OkHttpClient,
                                                 private val eventBus: EventBus,
-                                                sessionParams: SessionParams,
+                                                contentUrlResolver: ContentUrlResolver,
                                                 moshi: Moshi) {
 
-    private val uploadUrl = DefaultContentUrlResolver.getUploadUrl(sessionParams.homeServerConnectionConfig)
+    private val uploadUrl = contentUrlResolver.uploadUrl
     private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java)
 
     suspend fun uploadFile(file: File,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
index 1b736d349f..03ae366ed5 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
@@ -46,6 +46,10 @@ private data class NewImageAttributes(
         val newFileSize: Int
 )
 
+/**
+ * Possible previous worker: None
+ * Possible next worker    : Always [MultipleEventSendingDispatcherWorker]
+ */
 internal class UploadContentWorker(val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
 
     @JsonClass(generateAdapter = true)
@@ -64,12 +68,14 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
     override suspend fun doWork(): Result {
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.success()
+                        .also { Timber.e("Unable to parse work parameters") }
+
         Timber.v("Starting upload media work with params $params")
 
         if (params.lastFailureMessage != null) {
             // Transmit the error
-            Timber.v("Stop upload media work due to input failure")
             return Result.success(inputData)
+                    .also { Timber.e("Work cancelled due to input error from parent") }
         }
 
         // Just defensive code to ensure that we never have an uncaught exception that could break the queue
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
index 93705774e6..bb33212f9c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
@@ -23,8 +23,13 @@ import com.squareup.moshi.JsonClass
 import im.vector.matrix.android.internal.worker.SessionWorkerParams
 import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 import im.vector.matrix.android.internal.worker.getSessionComponent
+import timber.log.Timber
 import javax.inject.Inject
 
+/**
+ * Possible previous worker: None
+ * Possible next worker    : None
+ */
 internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
 
     @JsonClass(generateAdapter = true)
@@ -39,6 +44,7 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
     override suspend fun doWork(): Result {
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.failure()
+                        .also { Timber.e("Unable to parse work parameters") }
 
         val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
         sessionComponent.inject(this)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt
index 3ca5a03822..9808b584aa 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt
@@ -69,13 +69,13 @@ internal class GroupSummaryUpdater @Inject constructor(
 
         val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)
 
-        val sendWork = workManagerProvider.matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>()
+        val getGroupWork = workManagerProvider.matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>()
                 .setInputData(workData)
                 .setConstraints(WorkManagerProvider.workConstraints)
                 .build()
 
         workManagerProvider.workManager
-                .beginUniqueWork(GET_GROUP_DATA_WORKER, ExistingWorkPolicy.APPEND, sendWork)
+                .beginUniqueWork(GET_GROUP_DATA_WORKER, ExistingWorkPolicy.APPEND, getGroupWork)
                 .enqueue()
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt
index adb4bf32c2..ecc39cfdc2 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt
@@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.worker.SessionWorkerParams
 import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 import im.vector.matrix.android.internal.worker.getSessionComponent
 import org.greenrobot.eventbus.EventBus
+import timber.log.Timber
 import javax.inject.Inject
 
 internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
@@ -50,6 +51,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
     override suspend fun doWork(): Result {
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.failure()
+                        .also { Timber.e("Unable to parse work parameters") }
 
         val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
         sessionComponent.inject(this)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt
index 5857eaa89b..9a8f1a7dc6 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt
@@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.worker.SessionWorkerParams
 import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 import im.vector.matrix.android.internal.worker.getSessionComponent
 import org.greenrobot.eventbus.EventBus
+import timber.log.Timber
 import javax.inject.Inject
 
 // TODO This is not used. Delete?
@@ -51,10 +52,12 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
     override suspend fun doWork(): Result {
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.failure()
+                        .also { Timber.e("Unable to parse work parameters") }
 
         if (params.lastFailureMessage != null) {
             // Transmit the error
             return Result.success(inputData)
+                    .also { Timber.e("Work cancelled due to input error from parent") }
         }
 
         val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
index a99337695a..1037b7c79c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
@@ -228,7 +228,7 @@ internal class DefaultSendService @AssistedInject constructor(
                     keys.forEach { isRoomEncrypted ->
                         // Should never be empty
                         val localEchoes = get(isRoomEncrypted).orEmpty()
-                        val uploadWork = createUploadMediaWork(localEchoes, attachment, isRoomEncrypted, compressBeforeSending, startChain = true)
+                        val uploadWork = createUploadMediaWork(localEchoes, attachment, isRoomEncrypted, compressBeforeSending)
 
                         val dispatcherWork = createMultipleEventDispatcherWork(isRoomEncrypted)
 
@@ -293,14 +293,13 @@ internal class DefaultSendService @AssistedInject constructor(
     private fun createUploadMediaWork(allLocalEchos: List<Event>,
                                       attachment: ContentAttachmentData,
                                       isRoomEncrypted: Boolean,
-                                      compressBeforeSending: Boolean,
-                                      startChain: Boolean): OneTimeWorkRequest {
+                                      compressBeforeSending: Boolean): OneTimeWorkRequest {
         val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, allLocalEchos, attachment, isRoomEncrypted, compressBeforeSending)
         val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
 
         return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
                 .setConstraints(WorkManagerProvider.workConstraints)
-                .startChain(startChain)
+                .startChain(true)
                 .setInputData(uploadWorkData)
                 .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
                 .build()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
index e4424f1cb3..6af2f8dab6 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
@@ -35,6 +35,10 @@ import im.vector.matrix.android.internal.worker.getSessionComponent
 import timber.log.Timber
 import javax.inject.Inject
 
+/**
+ * Possible previous worker: None
+ * Possible next worker    : Always [SendEventWorker]
+ */
 internal class EncryptEventWorker(context: Context, params: WorkerParameters)
     : CoroutineWorker(context, params) {
 
@@ -53,14 +57,14 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
     override suspend fun doWork(): Result {
         Timber.v("Start Encrypt work")
         val params = WorkerParamsFactory.fromData<Params>(inputData)
-                ?: return Result.success().also {
-                    Timber.e("Work cancelled due to input error from parent")
-                }
+                ?: return Result.success()
+                        .also { Timber.e("Unable to parse work parameters") }
 
         Timber.v("Start Encrypt work for event ${params.event.eventId}")
         if (params.lastFailureMessage != null) {
             // Transmit the error
             return Result.success(inputData)
+                    .also { Timber.e("Work cancelled due to input error from parent") }
         }
 
         val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
index 8c31dd1682..aec7cb3c5c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
@@ -25,6 +25,7 @@ import com.squareup.moshi.JsonClass
 import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.room.send.SendState
 import im.vector.matrix.android.internal.di.WorkManagerProvider
+import im.vector.matrix.android.internal.session.content.UploadContentWorker
 import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
 import im.vector.matrix.android.internal.worker.SessionWorkerParams
 import im.vector.matrix.android.internal.worker.WorkerParamsFactory
@@ -36,6 +37,9 @@ import javax.inject.Inject
 
 /**
  * This worker creates a new work for each events passed in parameter
+ *
+ * Possible previous worker: Always [UploadContentWorker]
+ * Possible next worker    : None, but it will post new work to send events, encrypted or not
  */
 internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters)
     : CoroutineWorker(context, params) {
@@ -55,9 +59,8 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
     override suspend fun doWork(): Result {
         Timber.v("Start dispatch sending multiple event work")
         val params = WorkerParamsFactory.fromData<Params>(inputData)
-                ?: return Result.success().also {
-                    Timber.e("Work cancelled due to input error from parent")
-                }
+                ?: return Result.success()
+                        .also { Timber.e("Unable to parse work parameters") }
 
         val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
         sessionComponent.inject(this)
@@ -68,6 +71,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
             }
             // Transmit the error if needed?
             return Result.success(inputData)
+                    .also { Timber.e("Work cancelled due to input error from parent") }
         }
 
         // Create a work for every event
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt
index 3ff318aa8a..7e0b665d63 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt
@@ -26,8 +26,13 @@ import im.vector.matrix.android.internal.worker.SessionWorkerParams
 import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 import im.vector.matrix.android.internal.worker.getSessionComponent
 import org.greenrobot.eventbus.EventBus
+import timber.log.Timber
 import javax.inject.Inject
 
+/**
+ * Possible previous worker: None
+ * Possible next worker    : None
+ */
 internal class RedactEventWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
 
     @JsonClass(generateAdapter = true)
@@ -46,10 +51,12 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
     override suspend fun doWork(): Result {
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.failure()
+                        .also { Timber.e("Unable to parse work parameters") }
 
         if (params.lastFailureMessage != null) {
             // Transmit the error
             return Result.success(inputData)
+                    .also { Timber.e("Work cancelled due to input error from parent") }
         }
 
         val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
index 69d288a932..d55b9665f6 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
@@ -32,6 +32,10 @@ import org.greenrobot.eventbus.EventBus
 import timber.log.Timber
 import javax.inject.Inject
 
+/**
+ * Possible previous worker: [EncryptEventWorker] or first worker
+ * Possible next worker    : None
+ */
 internal class SendEventWorker(context: Context,
                                params: WorkerParameters)
     : CoroutineWorker(context, params) {
@@ -49,9 +53,8 @@ internal class SendEventWorker(context: Context,
 
     override suspend fun doWork(): Result {
         val params = WorkerParamsFactory.fromData<Params>(inputData)
-                ?: return Result.success().also {
-                    Timber.e("Work cancelled due to input error from parent")
-                }
+                ?: return Result.success()
+                        .also { Timber.e("Unable to parse work parameters") }
 
         val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
         sessionComponent.inject(this)
@@ -65,6 +68,7 @@ internal class SendEventWorker(context: Context,
             localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED)
             // Transmit the error
             return Result.success(inputData)
+                    .also { Timber.e("Work cancelled due to input error from parent") }
         }
         return try {
             sendEvent(event)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt
index c844db8d33..ab4e1938ce 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt
@@ -35,6 +35,10 @@ import javax.inject.Inject
 
 private const val DEFAULT_LONG_POOL_TIMEOUT = 0L
 
+/**
+ * Possible previous worker: None
+ * Possible next worker    : None
+ */
 internal class SyncWorker(context: Context,
                           workerParameters: WorkerParameters
 ) : CoroutineWorker(context, workerParameters) {
@@ -53,7 +57,10 @@ internal class SyncWorker(context: Context,
 
     override suspend fun doWork(): Result {
         Timber.i("Sync work starting")
-        val params = WorkerParamsFactory.fromData<Params>(inputData) ?: return Result.success()
+        val params = WorkerParamsFactory.fromData<Params>(inputData)
+                ?: return Result.success()
+                        .also { Timber.e("Unable to parse work parameters") }
+
         val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
         sessionComponent.inject(this)
         return runCatching {
@@ -76,7 +83,6 @@ internal class SyncWorker(context: Context,
     }
 
     companion object {
-
         private const val BG_SYNC_WORK_NAME = "BG_SYNCP"
 
         fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt
index c05367cf10..3cbe6c7866 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt
@@ -16,9 +16,16 @@
 
 package im.vector.matrix.android.internal.worker
 
+/**
+ * Note about the Worker usage:
+ * The workers we chain, or when using the append strategy, should never return Result.Failure(), else the chain will be broken forever
+ */
 interface SessionWorkerParams {
     val sessionId: String
 
-    // Null is no error occurs. When chaining Workers, first step is to check that there is no lastFailureMessage from the previous workers
+    /**
+     * Null when no error occurs. When chaining Workers, first step is to check that there is no lastFailureMessage from the previous workers
+     * If it is the case, the worker should just transmit the error and shouldn't do anything else
+     */
     val lastFailureMessage: String?
 }
diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index a0bd725118..3c0337ac99 100644
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -60,7 +60,7 @@ private short
 final short
 
 ### Line length is limited to 160 chars. Please split long lines
-.{161}
+[^─]{161}
 
 ### "DO NOT COMMIT" has been committed
 DO NOT COMMIT
diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
index c68972cdd4..c2f2959bd7 100644
--- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
+++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
@@ -30,6 +30,7 @@ import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment
 import im.vector.riotx.features.crypto.recover.BootstrapConclusionFragment
 import im.vector.riotx.features.crypto.recover.BootstrapConfirmPassphraseFragment
 import im.vector.riotx.features.crypto.recover.BootstrapEnterPassphraseFragment
+import im.vector.riotx.features.crypto.recover.BootstrapMigrateBackupFragment
 import im.vector.riotx.features.crypto.recover.BootstrapSaveRecoveryKeyFragment
 import im.vector.riotx.features.crypto.recover.BootstrapWaitingFragment
 import im.vector.riotx.features.crypto.verification.cancel.VerificationCancelFragment
@@ -444,4 +445,8 @@ interface FragmentModule {
     @IntoMap
     @FragmentKey(BootstrapAccountPasswordFragment::class)
     fun bindBootstrapAccountPasswordFragment(fragment: BootstrapAccountPasswordFragment): Fragment
+    @Binds
+    @IntoMap
+    @FragmentKey(BootstrapMigrateBackupFragment::class)
+    fun bindBootstrapMigrateBackupFragment(fragment: BootstrapMigrateBackupFragment): Fragment
 }
diff --git a/vector/src/main/java/im/vector/riotx/core/platform/ViewModelTask.kt b/vector/src/main/java/im/vector/riotx/core/platform/ViewModelTask.kt
new file mode 100644
index 0000000000..abe5cc9095
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/core/platform/ViewModelTask.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 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.riotx.core.platform
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+
+interface ViewModelTask<Params, Result> {
+    operator fun invoke(
+            scope: CoroutineScope,
+            params: Params,
+            onResult: (Result) -> Unit = {}
+    ) {
+        val backgroundJob = scope.async { execute(params) }
+        scope.launch { onResult(backgroundJob.await()) }
+    }
+
+    suspend fun execute(params: Params): Result
+}
diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt
index 83a4e88ad5..817575d91a 100755
--- a/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt
+++ b/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt
@@ -159,7 +159,7 @@ class KeysBackupBanner @JvmOverloads constructor(
         render(state, true)
     }
 
-    // PRIVATE METHODS *****************************************************************************************************************************************
+    // PRIVATE METHODS ****************************************************************************************************************************************
 
     private fun setupView() {
         inflate(context, R.layout.view_keys_backup_banner, this)
diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/NotificationAreaView.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/NotificationAreaView.kt
index 145a26aed2..1c1f3fae1a 100644
--- a/vector/src/main/java/im/vector/riotx/core/ui/views/NotificationAreaView.kt
+++ b/vector/src/main/java/im/vector/riotx/core/ui/views/NotificationAreaView.kt
@@ -87,7 +87,7 @@ class NotificationAreaView @JvmOverloads constructor(
         }
     }
 
-    // PRIVATE METHODS *****************************************************************************************************************************************
+    // PRIVATE METHODS ****************************************************************************************************************************************
 
     private fun setupView() {
         inflate(context, R.layout.view_notification_area, this)
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
index fbc69505fa..c7d3da30ea 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
@@ -128,7 +128,10 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
     }
 
     private fun exportKeysManually() {
-        if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_EXPORT_KEYS, R.string.permissions_rationale_msg_keys_backup_export)) {
+        if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES,
+                        this,
+                        PERMISSION_REQUEST_CODE_EXPORT_KEYS,
+                        R.string.permissions_rationale_msg_keys_backup_export)) {
             ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
                 override fun onPassphrase(passphrase: String) {
                     showWaitingView()
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt
new file mode 100644
index 0000000000..e29d0d636d
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 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.riotx.features.crypto.recover
+
+import im.vector.matrix.android.api.NoOpMatrixCallback
+import im.vector.matrix.android.api.listeners.ProgressListener
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
+import im.vector.matrix.android.api.session.securestorage.EmptyKeySigner
+import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
+import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
+import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
+import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
+import im.vector.matrix.android.internal.crypto.keysbackup.deriveKey
+import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
+import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
+import im.vector.matrix.android.internal.util.awaitCallback
+import im.vector.riotx.R
+import im.vector.riotx.core.platform.ViewModelTask
+import im.vector.riotx.core.platform.WaitingViewData
+import im.vector.riotx.core.resources.StringProvider
+import timber.log.Timber
+import java.util.UUID
+import javax.inject.Inject
+
+class BackupToQuadSMigrationTask @Inject constructor(
+        val session: Session,
+        val stringProvider: StringProvider
+) : ViewModelTask<BackupToQuadSMigrationTask.Params, BackupToQuadSMigrationTask.Result> {
+
+    sealed class Result {
+        object Success : Result()
+        abstract class Failure(val error: String?) : Result()
+        object InvalidRecoverySecret : Failure(null)
+        object NoKeyBackupVersion : Failure(null)
+        object IllegalParams : Failure(null)
+        class ErrorFailure(throwable: Throwable) : Failure(throwable.localizedMessage)
+    }
+
+    data class Params(
+            val passphrase: String?,
+            val recoveryKey: String?,
+            val progressListener: BootstrapProgressListener? = null
+    )
+
+    override suspend fun execute(params: Params): Result {
+        try {
+            // We need to use the current secret for keybackup and use it as the new master key for SSSS
+            // Then we need to put back the backup key in sss
+            val keysBackupService = session.cryptoService().keysBackupService()
+            val quadS = session.sharedSecretStorageService
+
+            val version = keysBackupService.keysBackupVersion ?: return Result.NoKeyBackupVersion
+
+            reportProgress(params, R.string.bootstrap_progress_checking_backup)
+            val curveKey =
+                    (if (params.recoveryKey != null) {
+                        extractCurveKeyFromRecoveryKey(params.recoveryKey)
+                    } else if (!params.passphrase.isNullOrEmpty() && version.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null) {
+                        version.getAuthDataAsMegolmBackupAuthData()?.let { authData ->
+                            deriveKey(params.passphrase, authData.privateKeySalt!!, authData.privateKeyIterations!!, object : ProgressListener {
+                                override fun onProgress(progress: Int, total: Int) {
+                                    params.progressListener?.onProgress(WaitingViewData(
+                                            stringProvider.getString(R.string.bootstrap_progress_checking_backup_with_info,
+                                                    "$progress/$total")
+                                    ))
+                                }
+                            })
+                        }
+                    } else null)
+                            ?: return Result.IllegalParams
+
+            reportProgress(params, R.string.bootstrap_progress_compute_curve_key)
+            val recoveryKey = computeRecoveryKey(curveKey)
+
+            val isValid = awaitCallback<Boolean> {
+                keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey, it)
+            }
+
+            if (!isValid) return Result.InvalidRecoverySecret
+
+            val info: SsssKeyCreationInfo =
+                    when {
+                        params.passphrase?.isNotEmpty() == true -> {
+                            reportProgress(params, R.string.bootstrap_progress_generating_ssss)
+                            awaitCallback {
+                                quadS.generateKeyWithPassphrase(
+                                        UUID.randomUUID().toString(),
+                                        "ssss_key",
+                                        params.passphrase,
+                                        EmptyKeySigner(),
+                                        object : ProgressListener {
+                                            override fun onProgress(progress: Int, total: Int) {
+                                                params.progressListener?.onProgress(
+                                                        WaitingViewData(
+                                                                stringProvider.getString(
+                                                                        R.string.bootstrap_progress_generating_ssss_with_info,
+                                                                        "$progress/$total")
+                                                        ))
+                                            }
+                                        },
+                                        it
+                                )
+                            }
+                        }
+                        params.recoveryKey != null              -> {
+                            reportProgress(params, R.string.bootstrap_progress_generating_ssss_recovery)
+                            awaitCallback {
+                                quadS.generateKey(
+                                        UUID.randomUUID().toString(),
+                                        extractCurveKeyFromRecoveryKey(params.recoveryKey)?.let { RawBytesKeySpec(it) },
+                                        "ssss_key",
+                                        EmptyKeySigner(),
+                                        it
+                                )
+                            }
+                        }
+                        else                                    -> {
+                            return Result.IllegalParams
+                        }
+                    }
+
+            // Ok, so now we have migrated the old keybackup secret as the quadS key
+            // Now we need to store the keybackup key in SSSS in a compatible way
+            reportProgress(params, R.string.bootstrap_progress_storing_in_sss)
+            awaitCallback<Unit> {
+                quadS.storeSecret(
+                        KEYBACKUP_SECRET_SSSS_NAME,
+                        curveKey.toBase64NoPadding(),
+                        listOf(SharedSecretStorageService.KeyRef(info.keyId, info.keySpec)),
+                        it
+                )
+            }
+
+            // save for gossiping
+            keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version)
+
+            // while we are there let's restore, but do not block
+            session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
+                    version,
+                    recoveryKey,
+                    null,
+                    null,
+                    null,
+                    NoOpMatrixCallback()
+            )
+
+            return Result.Success
+        } catch (failure: Throwable) {
+            Timber.e(failure, "## BackupToQuadSMigrationTask - Failed to migrate backup")
+            return Result.ErrorFailure(failure)
+        }
+    }
+
+    private fun reportProgress(params: Params, stringRes: Int) {
+        params.progressListener?.onProgress(WaitingViewData(stringProvider.getString(stringRes)))
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt
index 7c0f2c1c46..2d9440a77d 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt
@@ -40,4 +40,8 @@ sealed class BootstrapActions : VectorViewModelAction {
     object SaveReqQueryStarted : BootstrapActions()
     data class SaveKeyToUri(val os: OutputStream) : BootstrapActions()
     object SaveReqFailed : BootstrapActions()
+
+    object HandleForgotBackupPassphrase : BootstrapActions()
+    data class DoMigrateWithPassphrase(val passphrase: String) : BootstrapActions()
+    data class DoMigrateWithRecoveryKey(val recoveryKey: String) : BootstrapActions()
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt
index 6305f161e3..e48c674159 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt
@@ -18,6 +18,7 @@ package im.vector.riotx.features.crypto.recover
 
 import android.app.Dialog
 import android.os.Bundle
+import android.os.Parcelable
 import android.view.KeyEvent
 import android.view.LayoutInflater
 import android.view.View
@@ -26,18 +27,26 @@ import android.view.WindowManager
 import androidx.appcompat.app.AlertDialog
 import androidx.core.content.ContextCompat
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
 import im.vector.riotx.R
 import im.vector.riotx.core.di.ScreenComponent
 import im.vector.riotx.core.extensions.commitTransaction
+import im.vector.riotx.core.extensions.exhaustive
 import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
+import kotlinx.android.parcel.Parcelize
 import kotlinx.android.synthetic.main.bottom_sheet_bootstrap.*
 import javax.inject.Inject
 import kotlin.reflect.KClass
 
 class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
 
+    @Parcelize
+    data class Args(
+            val isNewAccount: Boolean
+    ) : Parcelable
+
     override val showExpanded = true
 
     @Inject
@@ -113,40 +122,70 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
     override fun invalidate() = withState(viewModel) { state ->
 
         when (state.step) {
-            is BootstrapStep.SetupPassphrase   -> {
+            is BootstrapStep.CheckingMigration           -> {
+                bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password))
+                bootstrapTitleText.text = getString(R.string.upgrade_security)
+                showFragment(BootstrapWaitingFragment::class, Bundle())
+            }
+            is BootstrapStep.SetupPassphrase             -> {
                 bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password))
                 bootstrapTitleText.text = getString(R.string.set_recovery_passphrase, getString(R.string.recovery_passphrase))
                 showFragment(BootstrapEnterPassphraseFragment::class, Bundle())
             }
-            is BootstrapStep.ConfirmPassphrase -> {
+            is BootstrapStep.ConfirmPassphrase           -> {
                 bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password))
                 bootstrapTitleText.text = getString(R.string.confirm_recovery_passphrase, getString(R.string.recovery_passphrase))
                 showFragment(BootstrapConfirmPassphraseFragment::class, Bundle())
             }
-            is BootstrapStep.AccountPassword   -> {
+            is BootstrapStep.AccountPassword             -> {
                 bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user))
                 bootstrapTitleText.text = getString(R.string.account_password)
                 showFragment(BootstrapAccountPasswordFragment::class, Bundle())
             }
-            is BootstrapStep.Initializing      -> {
+            is BootstrapStep.Initializing                -> {
                 bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key))
                 bootstrapTitleText.text = getString(R.string.bootstrap_loading_title)
                 showFragment(BootstrapWaitingFragment::class, Bundle())
             }
-            is BootstrapStep.SaveRecoveryKey   -> {
+            is BootstrapStep.SaveRecoveryKey             -> {
                 bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key))
                 bootstrapTitleText.text = getString(R.string.keys_backup_setup_step3_please_make_copy)
                 showFragment(BootstrapSaveRecoveryKeyFragment::class, Bundle())
             }
-            is BootstrapStep.DoneSuccess       -> {
+            is BootstrapStep.DoneSuccess                 -> {
                 bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key))
                 bootstrapTitleText.text = getString(R.string.bootstrap_finish_title)
                 showFragment(BootstrapConclusionFragment::class, Bundle())
             }
-        }
+            is BootstrapStep.GetBackupSecretForMigration -> {
+                val isKey = when (state.step) {
+                    is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey
+                    else                                             -> true
+                }
+                val drawableRes = if (isKey) R.drawable.ic_message_key else R.drawable.ic_message_password
+                bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(
+                        requireContext(),
+                        drawableRes)
+                )
+                bootstrapTitleText.text = getString(R.string.upgrade_security)
+                showFragment(BootstrapMigrateBackupFragment::class, Bundle())
+            }
+        }.exhaustive
         super.invalidate()
     }
 
+    companion object {
+
+        const val EXTRA_ARGS = "EXTRA_ARGS"
+
+        fun show(fragmentManager: FragmentManager, isAccountCreation: Boolean) {
+            BootstrapBottomSheet().apply {
+                isCancelable = false
+                arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(isAccountCreation)) }
+            }.show(fragmentManager, "BootstrapBottomSheet")
+        }
+    }
+
     private fun showFragment(fragmentClass: KClass<out Fragment>, bundle: Bundle) {
         if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
             childFragmentManager.commitTransaction {
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt
index a19604d78e..c2e0afbe3b 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt
@@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY
 import im.vector.matrix.android.api.session.securestorage.EmptyKeySigner
 import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
 import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
+import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
 import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
 import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
@@ -33,11 +34,9 @@ import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
 import im.vector.matrix.android.internal.di.MoshiProvider
 import im.vector.matrix.android.internal.util.awaitCallback
 import im.vector.riotx.R
+import im.vector.riotx.core.platform.ViewModelTask
 import im.vector.riotx.core.platform.WaitingViewData
 import im.vector.riotx.core.resources.StringProvider
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
 import timber.log.Timber
 import java.util.UUID
 import javax.inject.Inject
@@ -67,24 +66,16 @@ interface BootstrapProgressListener {
 data class Params(
         val userPasswordAuth: UserPasswordAuth? = null,
         val progressListener: BootstrapProgressListener? = null,
-        val passphrase: String?
+        val passphrase: String?,
+        val keySpec: SsssKeySpec? = null
 )
 
 class BootstrapCrossSigningTask @Inject constructor(
         private val session: Session,
         private val stringProvider: StringProvider
-) {
+) : ViewModelTask<Params, BootstrapResult> {
 
-    operator fun invoke(
-            scope: CoroutineScope,
-            params: Params,
-            onResult: (BootstrapResult) -> Unit = {}
-    ) {
-        val backgroundJob = scope.async { execute(params) }
-        scope.launch { onResult(backgroundJob.await()) }
-    }
-
-    suspend fun execute(params: Params): BootstrapResult {
+    override suspend fun execute(params: Params): BootstrapResult {
         params.progressListener?.onProgress(
                 WaitingViewData(
                         stringProvider.getString(R.string.bootstrap_crosssigning_progress_initializing),
@@ -124,6 +115,7 @@ class BootstrapCrossSigningTask @Inject constructor(
                 } ?: kotlin.run {
                     ssssService.generateKey(
                             UUID.randomUUID().toString(),
+                            params.keySpec,
                             "ssss_key",
                             EmptyKeySigner(),
                             it
@@ -205,14 +197,16 @@ class BootstrapCrossSigningTask @Inject constructor(
                 )
         )
         try {
-            val creationInfo = awaitCallback<MegolmBackupCreationInfo> {
-                session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
+            if (session.cryptoService().keysBackupService().keysBackupVersion == null) {
+                val creationInfo = awaitCallback<MegolmBackupCreationInfo> {
+                    session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
+                }
+                val version = awaitCallback<KeysVersion> {
+                    session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
+                }
+                // Save it for gossiping
+                session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
             }
-            val version = awaitCallback<KeysVersion> {
-                session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
-            }
-            // Save it for gossiping
-            session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
         } catch (failure: Throwable) {
             Timber.e("## BootstrapCrossSigningTask: Failed to init keybackup")
         }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt
new file mode 100644
index 0000000000..f1847e5ab5
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 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.riotx.features.crypto.recover
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.text.InputType.TYPE_CLASS_TEXT
+import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
+import android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import androidx.core.text.toSpannable
+import androidx.core.view.isVisible
+import com.airbnb.mvrx.parentFragmentViewModel
+import com.airbnb.mvrx.withState
+import com.jakewharton.rxbinding3.view.clicks
+import com.jakewharton.rxbinding3.widget.editorActionEvents
+import com.jakewharton.rxbinding3.widget.textChanges
+import im.vector.matrix.android.api.extensions.tryThis
+import im.vector.matrix.android.internal.crypto.keysbackup.util.isValidRecoveryKey
+import im.vector.riotx.R
+import im.vector.riotx.core.extensions.hideKeyboard
+import im.vector.riotx.core.extensions.showPassword
+import im.vector.riotx.core.platform.VectorBaseFragment
+import im.vector.riotx.core.resources.ColorProvider
+import im.vector.riotx.core.utils.colorizeMatchingText
+import im.vector.riotx.core.utils.startImportTextFromFileIntent
+import io.reactivex.android.schedulers.AndroidSchedulers
+import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText
+import kotlinx.android.synthetic.main.fragment_bootstrap_migrate_backup.*
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+class BootstrapMigrateBackupFragment @Inject constructor(
+        private val colorProvider: ColorProvider
+) : VectorBaseFragment() {
+
+    override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup
+
+    val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        withState(sharedViewModel) {
+            // set initial value (usefull when coming back)
+            bootstrapMigrateEditText.setText(it.passphrase ?: "")
+        }
+        bootstrapMigrateEditText.editorActionEvents()
+                .debounce(300, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe {
+                    if (it.actionId == EditorInfo.IME_ACTION_DONE) {
+                        submit()
+                    }
+                }
+                .disposeOnDestroyView()
+
+        bootstrapMigrateEditText.textChanges()
+                .skipInitialValue()
+                .subscribe {
+                    bootstrapRecoveryKeyEnterTil.error = null
+                    // sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
+                }
+                .disposeOnDestroyView()
+
+        // sharedViewModel.observeViewEvents {}
+        bootstrapMigrateContinueButton.clicks()
+                .debounce(300, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe {
+                    submit()
+                }
+                .disposeOnDestroyView()
+
+        bootstrapMigrateShowPassword.clicks()
+                .debounce(300, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe {
+                    sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility)
+                }
+                .disposeOnDestroyView()
+
+        bootstrapMigrateForgotPassphrase.clicks()
+                .debounce(300, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe {
+                    sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase)
+                }
+                .disposeOnDestroyView()
+
+        bootstrapMigrateUseFile.clicks()
+                .debounce(300, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe {
+                    startImportTextFromFileIntent(this, IMPORT_FILE_REQ)
+                }
+                .disposeOnDestroyView()
+    }
+
+    private fun submit() = withState(sharedViewModel) { state ->
+        if (state.step !is BootstrapStep.GetBackupSecretForMigration) {
+            return@withState
+        }
+        val isEnteringKey =
+                when (state.step) {
+                    is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey
+                    else                                             -> true
+                }
+
+        val secret = bootstrapMigrateEditText.text?.toString()
+        if (secret.isNullOrBlank()) {
+            val errRes = if (isEnteringKey) R.string.recovery_key_empty_error_message else R.string.passphrase_empty_error_message
+            bootstrapRecoveryKeyEnterTil.error = getString(errRes)
+        } else if (isEnteringKey && !isValidRecoveryKey(secret)) {
+            bootstrapRecoveryKeyEnterTil.error = getString(R.string.bootstrap_invalid_recovery_key)
+        } else {
+            view?.hideKeyboard()
+            if (isEnteringKey) {
+                sharedViewModel.handle(BootstrapActions.DoMigrateWithRecoveryKey(secret))
+            } else {
+                sharedViewModel.handle(BootstrapActions.DoMigrateWithPassphrase(secret))
+            }
+        }
+    }
+
+    override fun invalidate() = withState(sharedViewModel) { state ->
+        if (state.step !is BootstrapStep.GetBackupSecretForMigration) {
+            return@withState
+        }
+
+        val isEnteringKey =
+                when (state.step) {
+                    is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey
+                    else                                             -> true
+                }
+
+        if (isEnteringKey) {
+            bootstrapMigrateShowPassword.isVisible = false
+            bootstrapMigrateEditText.inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or TYPE_TEXT_FLAG_MULTI_LINE
+
+            val recKey = getString(R.string.bootstrap_migration_backup_recovery_key)
+            bootstrapDescriptionText.text = getString(R.string.enter_account_password, recKey)
+                    .toSpannable()
+                    .colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
+
+            bootstrapMigrateEditText.hint = recKey
+
+            bootstrapMigrateEditText.hint = recKey
+            bootstrapMigrateForgotPassphrase.isVisible = false
+            bootstrapMigrateUseFile.isVisible = true
+        } else {
+            bootstrapMigrateShowPassword.isVisible = true
+
+            if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
+                val isPasswordVisible = state.step.isPasswordVisible
+                bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false)
+                bootstrapMigrateShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
+            }
+
+            bootstrapDescriptionText.text = getString(R.string.bootstrap_migration_enter_backup_password)
+
+            bootstrapMigrateEditText.hint = getString(R.string.passphrase_enter_passphrase)
+
+            bootstrapMigrateForgotPassphrase.isVisible = true
+
+            val recKey = getString(R.string.bootstrap_migration_use_recovery_key)
+            bootstrapMigrateForgotPassphrase.text = getString(R.string.bootstrap_migration_with_passphrase_helper_with_link, recKey)
+                    .toSpannable()
+                    .colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
+
+            bootstrapMigrateUseFile.isVisible = false
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
+            data?.data?.let { dataURI ->
+                tryThis {
+                    activity?.contentResolver?.openInputStream(dataURI)
+                            ?.bufferedReader()
+                            ?.use { it.readText() }
+                            ?.let {
+                                bootstrapMigrateEditText.setText(it)
+                            }
+                }
+            }
+            return
+        }
+        super.onActivityResult(requestCode, resultCode, data)
+    }
+
+    companion object {
+        private const val IMPORT_FILE_REQ = 0
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt
index 998899374c..55481b1f5b 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt
@@ -31,20 +31,26 @@ import com.nulabinc.zxcvbn.Zxcvbn
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
 import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
 import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
+import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
+import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
 import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
+import im.vector.matrix.android.internal.util.awaitCallback
 import im.vector.riotx.R
 import im.vector.riotx.core.extensions.exhaustive
 import im.vector.riotx.core.platform.VectorViewModel
 import im.vector.riotx.core.platform.WaitingViewData
 import im.vector.riotx.core.resources.StringProvider
 import im.vector.riotx.features.login.ReAuthHelper
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import java.io.OutputStream
 
 data class BootstrapViewState(
         val step: BootstrapStep = BootstrapStep.SetupPassphrase(false),
         val passphrase: String? = null,
+        val migrationRecoveryKey: String? = null,
         val passphraseRepeat: String? = null,
         val crossSigningInitialization: Async<Unit> = Uninitialized,
         val passphraseStrength: Async<Strength> = Uninitialized,
@@ -55,20 +61,13 @@ data class BootstrapViewState(
         val recoverySaveFileProcess: Async<Unit> = Uninitialized
 ) : MvRxState
 
-sealed class BootstrapStep {
-    data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
-    data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
-    data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep()
-    object Initializing : BootstrapStep()
-    data class SaveRecoveryKey(val isSaved: Boolean) : BootstrapStep()
-    object DoneSuccess : BootstrapStep()
-}
-
 class BootstrapSharedViewModel @AssistedInject constructor(
         @Assisted initialState: BootstrapViewState,
+        @Assisted val args: BootstrapBottomSheet.Args,
         private val stringProvider: StringProvider,
         private val session: Session,
         private val bootstrapTask: BootstrapCrossSigningTask,
+        private val migrationTask: BackupToQuadSMigrationTask,
         private val reAuthHelper: ReAuthHelper
 ) : VectorViewModel<BootstrapViewState, BootstrapActions, BootstrapViewEvents>(initialState) {
 
@@ -76,7 +75,53 @@ class BootstrapSharedViewModel @AssistedInject constructor(
 
     @AssistedInject.Factory
     interface Factory {
-        fun create(initialState: BootstrapViewState): BootstrapSharedViewModel
+        fun create(initialState: BootstrapViewState, args: BootstrapBottomSheet.Args): BootstrapSharedViewModel
+    }
+
+    init {
+        // need to check if user have an existing keybackup
+        if (args.isNewAccount) {
+            setState {
+                copy(step = BootstrapStep.SetupPassphrase(false))
+            }
+        } else {
+            setState {
+                copy(step = BootstrapStep.CheckingMigration)
+            }
+
+            // We need to check if there is an existing backup
+            viewModelScope.launch(Dispatchers.IO) {
+                val version = awaitCallback<KeysVersionResult?> {
+                    session.cryptoService().keysBackupService().getCurrentVersion(it)
+                }
+                if (version == null) {
+                    // we just resume plain bootstrap
+                    setState {
+                        copy(step = BootstrapStep.SetupPassphrase(false))
+                    }
+                } else {
+                    // we need to get existing backup passphrase/key and convert to SSSS
+                    val keyVersion = awaitCallback<KeysVersionResult?> {
+                        session.cryptoService().keysBackupService().getVersion(version.version ?: "", it)
+                    }
+                    if (keyVersion == null) {
+                        // strange case... just finish?
+                        _viewEvents.post(BootstrapViewEvents.Dismiss)
+                    } else {
+                        val isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null
+                        if (isBackupCreatedFromPassphrase) {
+                            setState {
+                                copy(step = BootstrapStep.GetBackupSecretPassForMigration(isPasswordVisible = false, useKey = false))
+                            }
+                        } else {
+                            setState {
+                                copy(step = BootstrapStep.GetBackupSecretKeyForMigration)
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
 
     override fun handle(action: BootstrapActions) = withState { state ->
@@ -84,23 +129,27 @@ class BootstrapSharedViewModel @AssistedInject constructor(
             is BootstrapActions.GoBack                           -> queryBack()
             BootstrapActions.TogglePasswordVisibility            -> {
                 when (state.step) {
-                    is BootstrapStep.SetupPassphrase   -> {
+                    is BootstrapStep.SetupPassphrase                 -> {
                         setState {
                             copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
                         }
                     }
-                    is BootstrapStep.ConfirmPassphrase -> {
+                    is BootstrapStep.ConfirmPassphrase               -> {
                         setState {
                             copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
                         }
                     }
-
-                    is BootstrapStep.AccountPassword   -> {
+                    is BootstrapStep.AccountPassword                 -> {
                         setState {
                             copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
                         }
                     }
-                    else                               -> {
+                    is BootstrapStep.GetBackupSecretPassForMigration -> {
+                        setState {
+                            copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
+                        }
+                    }
+                    else                                             -> {
                     }
                 }
             }
@@ -197,12 +246,25 @@ class BootstrapSharedViewModel @AssistedInject constructor(
                     copy(step = BootstrapStep.AccountPassword(false))
                 }
             }
+            BootstrapActions.HandleForgotBackupPassphrase        -> {
+                if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
+                    setState {
+                        copy(step = BootstrapStep.GetBackupSecretPassForMigration(state.step.isPasswordVisible, true))
+                    }
+                } else return@withState
+            }
             is BootstrapActions.ReAuth                           -> {
                 startInitializeFlow(
                         state.currentReAuth?.copy(password = action.pass)
                                 ?: UserPasswordAuth(user = session.myUserId, password = action.pass)
                 )
             }
+            is BootstrapActions.DoMigrateWithPassphrase          -> {
+                startMigrationFlow(state.step, action.passphrase, null)
+            }
+            is BootstrapActions.DoMigrateWithRecoveryKey         -> {
+                startMigrationFlow(state.step, null, action.recoveryKey)
+            }
         }.exhaustive
     }
 
@@ -210,7 +272,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
     // Business Logic
     // =======================================
     private fun saveRecoveryKeyToUri(os: OutputStream) = withState { state ->
-        viewModelScope.launch {
+        viewModelScope.launch(Dispatchers.IO) {
             kotlin.runCatching {
                 os.use {
                     os.write((state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey() ?: "").toByteArray())
@@ -231,6 +293,57 @@ class BootstrapSharedViewModel @AssistedInject constructor(
         }
     }
 
+    private fun startMigrationFlow(prevState: BootstrapStep, passphrase: String?, recoveryKey: String?) {
+        setState {
+            copy(step = BootstrapStep.Initializing)
+        }
+        viewModelScope.launch(Dispatchers.IO) {
+            val progressListener = object : BootstrapProgressListener {
+                override fun onProgress(data: WaitingViewData) {
+                    setState {
+                        copy(
+                                initializationWaitingViewData = data
+                        )
+                    }
+                }
+            }
+            migrationTask.invoke(this, BackupToQuadSMigrationTask.Params(passphrase, recoveryKey, progressListener)) {
+                if (it is BackupToQuadSMigrationTask.Result.Success) {
+                    setState {
+                        copy(
+                                passphrase = passphrase,
+                                passphraseRepeat = passphrase,
+                                migrationRecoveryKey = recoveryKey
+                        )
+                    }
+                    val auth = reAuthHelper.rememberedAuth()
+                    if (auth == null) {
+                        setState {
+                            copy(
+                                    step = BootstrapStep.AccountPassword(false)
+                            )
+                        }
+                    } else {
+                        startInitializeFlow(auth)
+                    }
+                } else {
+                    _viewEvents.post(
+                            BootstrapViewEvents.ModalError(
+                                    (it as? BackupToQuadSMigrationTask.Result.Failure)?.error
+                                            ?: stringProvider.getString(R.string.matrix_error
+                                            )
+                            )
+                    )
+                    setState {
+                        copy(
+                                step = prevState
+                        )
+                    }
+                }
+            }
+        }
+    }
+
     private fun startInitializeFlow(auth: UserPasswordAuth?) {
         setState {
             copy(step = BootstrapStep.Initializing)
@@ -247,11 +360,12 @@ class BootstrapSharedViewModel @AssistedInject constructor(
         }
 
         withState { state ->
-            viewModelScope.launch {
+            viewModelScope.launch(Dispatchers.IO) {
                 bootstrapTask.invoke(this, Params(
                         userPasswordAuth = auth ?: reAuthHelper.rememberedAuth(),
                         progressListener = progressListener,
-                        passphrase = state.passphrase
+                        passphrase = state.passphrase,
+                        keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } }
                 )) {
                     when (it) {
                         is BootstrapResult.Success                 -> {
@@ -309,11 +423,30 @@ class BootstrapSharedViewModel @AssistedInject constructor(
 
     private fun queryBack() = withState { state ->
         when (state.step) {
-            is BootstrapStep.SetupPassphrase   -> {
+            is BootstrapStep.GetBackupSecretPassForMigration -> {
+                if (state.step.useKey) {
+                    // go back to passphrase
+                    setState {
+                        copy(
+                                step = BootstrapStep.GetBackupSecretPassForMigration(
+                                        isPasswordVisible = state.step.isPasswordVisible,
+                                        useKey = false
+                                )
+                        )
+                    }
+                } else {
+                    _viewEvents.post(BootstrapViewEvents.SkipBootstrap())
+                }
+            }
+            is BootstrapStep.GetBackupSecretKeyForMigration -> {
                 // do we let you cancel from here?
                 _viewEvents.post(BootstrapViewEvents.SkipBootstrap())
             }
-            is BootstrapStep.ConfirmPassphrase -> {
+            is BootstrapStep.SetupPassphrase                 -> {
+                // do we let you cancel from here?
+                _viewEvents.post(BootstrapViewEvents.SkipBootstrap())
+            }
+            is BootstrapStep.ConfirmPassphrase               -> {
                 setState {
                     copy(
                             step = BootstrapStep.SetupPassphrase(
@@ -322,15 +455,15 @@ class BootstrapSharedViewModel @AssistedInject constructor(
                     )
                 }
             }
-            is BootstrapStep.AccountPassword   -> {
+            is BootstrapStep.AccountPassword                 -> {
                 _viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
             }
-            BootstrapStep.Initializing         -> {
+            BootstrapStep.Initializing                       -> {
                 // do we let you cancel from here?
                 _viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
             }
             is BootstrapStep.SaveRecoveryKey,
-            BootstrapStep.DoneSuccess          -> {
+            BootstrapStep.DoneSuccess                        -> {
                 // nop
             }
         }
@@ -344,7 +477,9 @@ class BootstrapSharedViewModel @AssistedInject constructor(
 
         override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? {
             val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
-            return fragment.bootstrapViewModelFactory.create(state)
+            val args: BootstrapBottomSheet.Args = fragment.arguments?.getParcelable(BootstrapBottomSheet.EXTRA_ARGS)
+                    ?: BootstrapBottomSheet.Args(true)
+            return fragment.bootstrapViewModelFactory.create(state, args)
         }
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt
new file mode 100644
index 0000000000..2c649ae99c
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 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.riotx.features.crypto.recover
+
+/**
+ *                             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+ *                             β”‚ User has signing keys?  │──────────── Account
+ *                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            Creation ?
+ *                                          β”‚                              β”‚
+ *                                          No                             β”‚
+ *                                          β”‚                              β”‚
+ *                                          β”‚                              β”‚
+ *                                          β–Ό                              β”‚
+ *                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
+ *                        β”‚  BootstrapStep.CheckingMigration  β”‚            β”‚
+ *                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
+ *                                          β”‚                              β”‚
+ *                                          β”‚                              β”‚
+ *                           Existing       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€No ───────┐          β”‚
+ *                     β”Œβ”€β”€β”€β”€Keybackupβ”€β”€β”€β”€β”€β”€β”€β”˜     KeyBackup     β”‚          β”‚
+ *                     β”‚                                        β”‚          β”‚
+ *                     β”‚                                        β–Ό          β–Ό
+ *                     β–Ό                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+ *     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚   BootstrapStep.SetupPassphrase    │◀─┐
+ *     β”‚BootstrapStep.GetBackupSecretForMigrationβ”‚          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
+ *     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                             β”‚                    β”‚
+ *                             β”‚                                               β”‚                 β”ŒBack
+ *                             β”‚                                               β–Ό                 β”‚
+ *                             β”‚                            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
+ *                             β”‚                            β”‚  BootstrapStep.ConfirmPassphrase   │──┐
+ *                             β”‚                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
+ *                             β”‚                                               β”‚                    β”‚
+ *                             β”‚                                      is password needed?           β”‚
+ *                             β”‚                                               β”‚                    β”‚
+ *                             β”‚                                               β–Ό                    β”‚
+ *                             β”‚                            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
+ *                             β”‚                            β”‚   BootstrapStep.AccountPassword    β”‚  β”‚
+ *                             β”‚                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
+ *                             β”‚                                               β”‚                    β”‚
+ *                             β”‚                                               β”‚                    β”‚
+ *                             β”‚                            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         password not needed (in
+ *                             β”‚                            β”‚                                    memory)
+ *                             β”‚                            β”‚                                       β”‚
+ *                             β”‚                            β–Ό                                       β”‚
+ *                             β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                     β”‚
+ *                             └────────▢│     BootstrapStep.Initializing     β”‚β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+ *                                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+ *                                                          β”‚
+ *                                                          β”‚
+ *                                                          β”‚
+ *                                                          β–Ό
+ *                                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+ *                                        β”‚   BootstrapStep.SaveRecoveryKey    β”‚
+ *                                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+ *                                                          β”‚
+ *                                                          β”‚
+ *                                                          β”‚
+ *                                                          β–Ό
+ *                                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+ *                                       β”‚       BootstrapStep.DoneSuccess        β”‚
+ *                                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+ *
+ */
+
+sealed class BootstrapStep {
+    data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
+    data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
+    data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep()
+    object CheckingMigration : BootstrapStep()
+
+    abstract class GetBackupSecretForMigration : BootstrapStep()
+    data class GetBackupSecretPassForMigration(val isPasswordVisible: Boolean, val useKey: Boolean) : GetBackupSecretForMigration()
+    object GetBackupSecretKeyForMigration : GetBackupSecretForMigration()
+
+    object Initializing : BootstrapStep()
+    data class SaveRecoveryKey(val isSaved: Boolean) : BootstrapStep()
+    object DoneSuccess : BootstrapStep()
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapWaitingFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapWaitingFragment.kt
index ff79fa6a4b..1c6c9e6d9b 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapWaitingFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapWaitingFragment.kt
@@ -16,8 +16,7 @@
 
 package im.vector.riotx.features.crypto.recover
 
-import android.os.Bundle
-import android.view.View
+import androidx.core.view.isVisible
 import com.airbnb.mvrx.parentFragmentViewModel
 import com.airbnb.mvrx.withState
 import im.vector.riotx.R
@@ -31,12 +30,22 @@ class BootstrapWaitingFragment @Inject constructor() : VectorBaseFragment() {
 
     val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
 
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-    }
-
     override fun invalidate() = withState(sharedViewModel) { state ->
-        if (state.step !is BootstrapStep.Initializing) return@withState
-        bootstrapLoadingStatusText.text = state.initializationWaitingViewData?.message
+        when (state.step) {
+            is BootstrapStep.Initializing -> {
+                bootstrapLoadingStatusText.isVisible = true
+                bootstrapDescriptionText.isVisible = true
+                bootstrapLoadingStatusText.text = state.initializationWaitingViewData?.message
+            }
+//            is BootstrapStep.CheckingMigration -> {
+//                bootstrapLoadingStatusText.isVisible = false
+//                bootstrapDescriptionText.isVisible = false
+//            }
+            else                          -> {
+                // just show the spinner
+                bootstrapLoadingStatusText.isVisible = false
+                bootstrapDescriptionText.isVisible = false
+            }
+        }
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt
index e286c82532..2449c635e4 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt
@@ -91,13 +91,13 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
                 .observe()
                 .subscribe { sharedAction ->
                     when (sharedAction) {
-                        is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
-                        is HomeActivitySharedAction.OpenGroup  -> {
+                        is HomeActivitySharedAction.OpenDrawer                 -> drawerLayout.openDrawer(GravityCompat.START)
+                        is HomeActivitySharedAction.OpenGroup                  -> {
                             drawerLayout.closeDrawer(GravityCompat.START)
                             replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java)
                         }
                         is HomeActivitySharedAction.PromptForSecurityBootstrap -> {
-                            BootstrapBottomSheet().apply { isCancelable = false }.show(supportFragmentManager, "BootstrapBottomSheet")
+                            BootstrapBottomSheet.show(supportFragmentManager, true)
                         }
                     }
                 }
@@ -109,6 +109,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
         }
         if (intent.getBooleanExtra(EXTRA_ACCOUNT_CREATION, false)) {
             sharedActionViewModel.post(HomeActivitySharedAction.PromptForSecurityBootstrap)
+            sharedActionViewModel.isAccountCreation = true
             intent.removeExtra(EXTRA_ACCOUNT_CREATION)
         }
 
@@ -163,29 +164,48 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
                 .getMyCrossSigningKeys()
         val crossSigningEnabledOnAccount = myCrossSigningKeys != null
 
-        if (crossSigningEnabledOnAccount && myCrossSigningKeys?.isTrusted() == false) {
+        if (!crossSigningEnabledOnAccount && !sharedActionViewModel.isAccountCreation) {
             // We need to ask
-            sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true
-            popupAlertManager.postVectorAlert(
-                    VerificationVectorAlert(
-                            uid = "completeSecurity",
-                            title = getString(R.string.complete_security),
-                            description = getString(R.string.crosssigning_verify_this_session),
-                            iconId = R.drawable.ic_shield_warning
-                    ).apply {
-                        matrixItem = session.getUser(session.myUserId)?.toMatrixItem()
-                        colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent)
-                        contentAction = Runnable {
-                            (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
-                                it.navigator.waitSessionVerification(it)
-                            }
-                        }
-                        dismissedAction = Runnable {}
-                    }
-            )
+            promptSecurityEvent(
+                    session,
+                    R.string.upgrade_security,
+                    R.string.security_prompt_text
+            ) {
+                it.navigator.upgradeSessionSecurity(it)
+            }
+        } else if (myCrossSigningKeys?.isTrusted() == false) {
+            // We need to ask
+            promptSecurityEvent(
+                    session,
+                    R.string.complete_security,
+                    R.string.crosssigning_verify_this_session
+            ) {
+                it.navigator.waitSessionVerification(it)
+            }
         }
     }
 
+    private fun promptSecurityEvent(session: Session, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) {
+        sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true
+        popupAlertManager.postVectorAlert(
+                VerificationVectorAlert(
+                        uid = "upgradeSecurity",
+                        title = getString(titleRes),
+                        description = getString(descRes),
+                        iconId = R.drawable.ic_shield_warning
+                ).apply {
+                    matrixItem = session.getUser(session.myUserId)?.toMatrixItem()
+                    colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent)
+                    contentAction = Runnable {
+                        (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
+                            action(it)
+                        }
+                    }
+                    dismissedAction = Runnable {}
+                }
+        )
+    }
+
     override fun onNewIntent(intent: Intent?) {
         super.onNewIntent(intent)
         if (intent?.hasExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION) == true) {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
index 47338f6335..564b030081 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
@@ -102,7 +102,7 @@ class HomeDetailFragment @Inject constructor(
                             VerificationVectorAlert(
                                     uid = uid,
                                     title = getString(R.string.new_session),
-                                    description = getString(R.string.new_session_review),
+                                    description = getString(R.string.new_session_review_with_info, newest.displayName ?: "", newest.deviceId ?: ""),
                                     iconId = R.drawable.ic_shield_warning
                             ).apply {
                                 matrixItem = user
diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt
index ecbe460b90..4af9262d05 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt
@@ -21,4 +21,5 @@ import javax.inject.Inject
 
 class HomeSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<HomeActivitySharedAction>() {
     var hasDisplayedCompleteSecurityPrompt : Boolean = false
+    var isAccountCreation : Boolean = false
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt
index ef43605b04..12ef5c3577 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt
@@ -81,7 +81,8 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
             }
             VerificationState.CANCELED_BY_OTHER -> {
                 holder.buttonBar.isVisible = false
-                holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_other_cancelled, attributes.informationData.memberName)
+                holder.statusTextView.text = holder.view.context
+                        .getString(R.string.verification_request_other_cancelled, attributes.informationData.memberName)
                 holder.statusTextView.isVisible = true
             }
             VerificationState.CANCELED_BY_ME    -> {
diff --git a/vector/src/main/java/im/vector/riotx/features/media/VideoMediaViewerActivity.kt b/vector/src/main/java/im/vector/riotx/features/media/VideoMediaViewerActivity.kt
index e92b58c681..4c98472f6e 100644
--- a/vector/src/main/java/im/vector/riotx/features/media/VideoMediaViewerActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/media/VideoMediaViewerActivity.kt
@@ -43,7 +43,11 @@ class VideoMediaViewerActivity : VectorBaseActivity() {
 
             configureToolbar(videoMediaViewerToolbar, mediaData)
             imageContentRenderer.render(mediaData.thumbnailMediaData, ImageContentRenderer.Mode.FULL_SIZE, videoMediaViewerThumbnailView)
-            videoContentRenderer.render(mediaData, videoMediaViewerThumbnailView, videoMediaViewerLoading, videoMediaViewerVideoView, videoMediaViewerErrorView)
+            videoContentRenderer.render(mediaData,
+                    videoMediaViewerThumbnailView,
+                    videoMediaViewerLoading,
+                    videoMediaViewerVideoView,
+                    videoMediaViewerErrorView)
         }
     }
 
diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt
index 2e91090ec4..0f19a1292a 100644
--- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt
@@ -34,6 +34,7 @@ import im.vector.riotx.core.utils.toast
 import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
 import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
 import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity
+import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet
 import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
 import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
 import im.vector.riotx.features.debug.DebugMenuActivity
@@ -107,6 +108,12 @@ class DefaultNavigator @Inject constructor(
         }
     }
 
+    override fun upgradeSessionSecurity(context: Context) {
+        if (context is VectorBaseActivity) {
+            BootstrapBottomSheet.show(context.supportFragmentManager, false)
+        }
+    }
+
     override fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String?, buildTask: Boolean) {
         if (context is VectorBaseActivity) {
             context.notImplemented("Open not joined room")
diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
index 65ef08dd05..bf99643912 100644
--- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
@@ -34,6 +34,8 @@ interface Navigator {
 
     fun waitSessionVerification(context: Context)
 
+    fun upgradeSessionSecurity(context: Context)
+
     fun openRoomForSharingAndFinish(activity: Activity, roomId: String, sharedData: SharedData)
 
     fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String? = null, buildTask: Boolean = false)
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 d357c6ae82..bb83658ae7 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
@@ -58,23 +58,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
     override var titleRes = R.string.settings_security_and_privacy
     override val preferenceXmlRes = R.xml.vector_settings_security_privacy
 
-    // devices: device IDs and device names
-    private val mDevicesNameList: MutableList<DeviceInfo> = mutableListOf()
-
-    private var mMyDeviceInfo: DeviceInfo? = null
-
     // cryptography
     private val mCryptographyCategory by lazy {
         findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY)!!
     }
-    // cryptography manage
-    private val mCryptographyManageCategory by lazy {
-        findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY)!!
-    }
-    // displayed pushers
-    private val mPushersSettingsCategory by lazy {
-        findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY)!!
-    }
 
     private val mCrossSigningStatePreference by lazy {
         findPreference<VectorPreference>(VectorPreferences.SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY)!!
@@ -106,7 +93,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
         // My device name may have been updated
         refreshMyDevice()
         refreshXSigningStatus()
-        mCryptographyCategory.isVisible = vectorPreferences.developerMode()
     }
 
     override fun bindPref() {
@@ -133,7 +119,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
     }
 
     private fun refreshXSigningStatus() {
-        if (vectorPreferences.developerMode()) {
             val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
             val xSigningIsEnableInAccount = crossSigningKeys != null
             val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
@@ -154,9 +139,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
             }
 
             mCrossSigningStatePreference.isVisible = true
-        } else {
-            mCrossSigningStatePreference.isVisible = false
-        }
     }
 
     override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
@@ -203,7 +185,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
      */
     private fun exportKeys() {
         // We need WRITE_EXTERNAL permission
-        if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_EXPORT_KEYS, R.string.permissions_rationale_msg_keys_backup_export)) {
+        if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES,
+                        this,
+                        PERMISSION_REQUEST_CODE_EXPORT_KEYS,
+                        R.string.permissions_rationale_msg_keys_backup_export)) {
             activity?.let { activity ->
                 ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener {
                     override fun onPassphrase(passphrase: String) {
@@ -346,15 +331,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
     // Cryptography
     // ==============================================================================================================
 
-    private fun removeCryptographyPreference() {
-        preferenceScreen.let {
-            it.removePreference(mCryptographyCategory)
-
-            // Also remove keys management section
-            it.removePreference(mCryptographyManageCategory)
-        }
-    }
-
     /**
      * Build the cryptography preference section.
      *
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt
index e33b12d19a..dd050c29ac 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt
@@ -24,13 +24,15 @@ import im.vector.riotx.core.ui.list.genericItem
 import im.vector.riotx.core.ui.list.genericItemWithValue
 import im.vector.riotx.core.utils.DimensionConverter
 import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
+import im.vector.riotx.features.settings.VectorPreferences
 import me.gujun.android.span.span
 import javax.inject.Inject
 
 class CrossSigningEpoxyController @Inject constructor(
         private val stringProvider: StringProvider,
         private val colorProvider: ColorProvider,
-        private val dimensionConverter: DimensionConverter
+        private val dimensionConverter: DimensionConverter,
+        private val vectorPreferences: VectorPreferences
 ) : TypedEpoxyController<CrossSigningSettingsViewState>() {
 
     interface InteractionListener {
@@ -49,7 +51,7 @@ class CrossSigningEpoxyController @Inject constructor(
                 titleIconResourceId(R.drawable.ic_shield_trusted)
                 title(stringProvider.getString(R.string.encryption_information_dg_xsigning_complete))
             }
-            if (!data.isUploadingKeys) {
+            if (vectorPreferences.developerMode() && !data.isUploadingKeys) {
                 bottomSheetVerificationActionItem {
                     id("resetkeys")
                     title("Reset keys")
@@ -68,14 +70,16 @@ class CrossSigningEpoxyController @Inject constructor(
                 title(stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted))
             }
             if (!data.isUploadingKeys) {
-                bottomSheetVerificationActionItem {
-                    id("resetkeys")
-                    title("Reset keys")
-                    titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
-                    iconRes(R.drawable.ic_arrow_right)
-                    iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
-                    listener {
-                        interactionListener?.onResetCrossSigningKeys()
+                if (vectorPreferences.developerMode()) {
+                    bottomSheetVerificationActionItem {
+                        id("resetkeys")
+                        title("Reset keys")
+                        titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+                        iconRes(R.drawable.ic_arrow_right)
+                        iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+                        listener {
+                            interactionListener?.onResetCrossSigningKeys()
+                        }
                     }
                 }
 
@@ -106,14 +110,16 @@ class CrossSigningEpoxyController @Inject constructor(
                     interactionListener?.verifySession()
                 }
             }
-            bottomSheetVerificationActionItem {
-                id("resetkeys")
-                title("Reset keys")
-                titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
-                iconRes(R.drawable.ic_arrow_right)
-                iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
-                listener {
-                    interactionListener?.onResetCrossSigningKeys()
+            if (vectorPreferences.developerMode()) {
+                bottomSheetVerificationActionItem {
+                    id("resetkeys")
+                    title("Reset keys")
+                    titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+                    iconRes(R.drawable.ic_arrow_right)
+                    iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+                    listener {
+                        interactionListener?.onResetCrossSigningKeys()
+                    }
                 }
             }
         } else {
@@ -121,7 +127,7 @@ class CrossSigningEpoxyController @Inject constructor(
                 id("not")
                 title(stringProvider.getString(R.string.encryption_information_dg_xsigning_disabled))
             }
-            if (!data.isUploadingKeys) {
+            if (vectorPreferences.developerMode() && !data.isUploadingKeys) {
                 bottomSheetVerificationActionItem {
                     id("initKeys")
                     title("Initialize keys")
diff --git a/vector/src/main/res/drawable/ic_file.xml b/vector/src/main/res/drawable/ic_file.xml
new file mode 100644
index 0000000000..2b74a19a94
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_file.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M13,2H6C4.8954,2 4,2.8954 4,4V20C4,21.1046 4.8954,22 6,22H18C19.1046,22 20,21.1046 20,20V9L13,2Z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#2E2F32"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M13,2V9H20"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#2E2F32"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/vector/src/main/res/drawable/ic_message_key.xml b/vector/src/main/res/drawable/ic_message_key.xml
index c4a415477b..9c5e53571d 100644
--- a/vector/src/main/res/drawable/ic_message_key.xml
+++ b/vector/src/main/res/drawable/ic_message_key.xml
@@ -7,13 +7,10 @@
     <clip-path
         android:pathData="M0,0h22v6h-22zM0,17h22v7h-22z"/>
     <path
-        android:pathData="M21,11.4445C21.0038,12.911 20.6612,14.3577 20,15.6667C18.401,18.8659 15.1321,20.8875 11.5555,20.8889C10.089,20.8927 8.6423,20.5501 7.3333,19.8889L1,22L3.1111,15.6667C2.4499,14.3577 2.1073,12.911 2.1111,11.4445C2.1125,7.8679 4.1341,4.599 7.3333,3C8.6423,2.3388 10.089,1.9962 11.5555,2H12.1111C16.9064,2.2646 20.7354,6.0936 21,10.8889V11.4445V11.4445Z"
-        android:strokeLineJoin="round"
+        android:pathData="M19,14C19,16.0333 17.7458,17.9018 16.043,19.4808C14.3615,21.0401 12.4,22.1689 11.3349,22.7219C11.1216,22.8327 10.8784,22.8327 10.6651,22.7219C9.6,22.1689 7.6385,21.0401 5.957,19.4808C4.2542,17.9018 3,16.0333 3,14V3.6043C3,3.1356 3.3255,2.7298 3.7831,2.6282L10.7831,1.0726C10.9259,1.0409 11.0741,1.0409 11.2169,1.0726L18.2169,2.6282C18.6745,2.7298 19,3.1356 19,3.6043V14Z"
         android:strokeWidth="2"
         android:fillColor="#00000000"
-        android:fillType="evenOdd"
-        android:strokeColor="#2E2F32"
-        android:strokeLineCap="round"/>
+        android:strokeColor="#2E2F32"/>
   </group>
   <path
       android:pathData="M2,8C0.8954,8 0,8.8954 0,10V13C0,14.1046 0.8954,15 2,15H20C21.1046,15 22,14.1046 22,13V10C22,8.8954 21.1046,8 20,8H2ZM4.25,9.5C3.8358,9.5 3.5,9.8358 3.5,10.25C3.5,10.6642 3.8358,11 4.25,11H6.75C7.1642,11 7.5,10.6642 7.5,10.25C7.5,9.8358 7.1642,9.5 6.75,9.5H4.25ZM8.5,10.25C8.5,9.8358 8.8358,9.5 9.25,9.5H9.75C10.1642,9.5 10.5,9.8358 10.5,10.25C10.5,10.6642 10.1642,11 9.75,11H9.25C8.8358,11 8.5,10.6642 8.5,10.25ZM12.25,9.5C11.8358,9.5 11.5,9.8358 11.5,10.25C11.5,10.6642 11.8358,11 12.25,11H14.75C15.1642,11 15.5,10.6642 15.5,10.25C15.5,9.8358 15.1642,9.5 14.75,9.5H12.25ZM16.5,10.25C16.5,9.8358 16.8358,9.5 17.25,9.5H17.75C18.1642,9.5 18.5,9.8358 18.5,10.25C18.5,10.6642 18.1642,11 17.75,11H17.25C16.8358,11 16.5,10.6642 16.5,10.25ZM4.25,12C3.8358,12 3.5,12.3358 3.5,12.75C3.5,13.1642 3.8358,13.5 4.25,13.5H4.75C5.1642,13.5 5.5,13.1642 5.5,12.75C5.5,12.3358 5.1642,12 4.75,12H4.25ZM6.5,12.75C6.5,12.3358 6.8358,12 7.25,12H9.75C10.1642,12 10.5,12.3358 10.5,12.75C10.5,13.1642 10.1642,13.5 9.75,13.5H7.25C6.8358,13.5 6.5,13.1642 6.5,12.75ZM12.25,12C11.8358,12 11.5,12.3358 11.5,12.75C11.5,13.1642 11.8358,13.5 12.25,13.5H12.75C13.1642,13.5 13.5,13.1642 13.5,12.75C13.5,12.3358 13.1642,12 12.75,12H12.25Z"
diff --git a/vector/src/main/res/drawable/ic_message_password.xml b/vector/src/main/res/drawable/ic_message_password.xml
index 311be14cc6..f82603a320 100644
--- a/vector/src/main/res/drawable/ic_message_password.xml
+++ b/vector/src/main/res/drawable/ic_message_password.xml
@@ -7,13 +7,10 @@
     <clip-path
         android:pathData="M0,0h22v6h-22zM0,17h22v7h-22z"/>
     <path
-        android:pathData="M21,11.4445C21.0038,12.911 20.6612,14.3577 20,15.6667C18.401,18.8659 15.1321,20.8875 11.5555,20.8889C10.089,20.8927 8.6423,20.5501 7.3333,19.8889L1,22L3.1111,15.6667C2.4499,14.3577 2.1073,12.911 2.1111,11.4445C2.1125,7.8679 4.1341,4.599 7.3333,3C8.6423,2.3388 10.089,1.9962 11.5555,2H12.1111C16.9064,2.2646 20.7354,6.0936 21,10.8889V11.4445V11.4445Z"
-        android:strokeLineJoin="round"
+        android:pathData="M11.3349,22.7219C11.1216,22.8327 10.8784,22.8327 10.6651,22.7219C9.6,22.1689 7.6385,21.0401 5.957,19.4808C4.2542,17.9018 3,16.0333 3,14V3.6043C3,3.1356 3.3255,2.7298 3.7831,2.6282L10.7831,1.0726C10.9259,1.0409 11.0741,1.0409 11.2169,1.0726L18.2169,2.6282C18.6745,2.7298 19,3.1356 19,3.6043V14C19,16.0333 17.7458,17.9018 16.043,19.4808C14.3615,21.0401 12.4,22.1689 11.3349,22.7219Z"
         android:strokeWidth="2"
         android:fillColor="#00000000"
-        android:fillType="evenOdd"
-        android:strokeColor="#2E2F32"
-        android:strokeLineCap="round"/>
+        android:strokeColor="#2E2F32"/>
   </group>
   <path
       android:pathData="M0,10C0,8.8954 0.8954,8 2,8H20C21.1046,8 22,8.8954 22,10V13C22,14.1046 21.1046,15 20,15H2C0.8954,15 0,14.1046 0,13V10ZM5,11.5C5,12.3284 4.3284,13 3.5,13C2.6716,13 2,12.3284 2,11.5C2,10.6716 2.6716,10 3.5,10C4.3284,10 5,10.6716 5,11.5ZM8.5,13C9.3284,13 10,12.3284 10,11.5C10,10.6716 9.3284,10 8.5,10C7.6716,10 7,10.6716 7,11.5C7,12.3284 7.6716,13 8.5,13ZM15,11.5C15,12.3284 14.3284,13 13.5,13C12.6716,13 12,12.3284 12,11.5C12,10.6716 12.6716,10 13.5,10C14.3284,10 15,10.6716 15,11.5ZM18.5,13C19.3284,13 20,12.3284 20,11.5C20,10.6716 19.3284,10 18.5,10C17.6716,10 17,10.6716 17,11.5C17,12.3284 17.6716,13 18.5,13Z"
diff --git a/vector/src/main/res/layout/fragment_bootstrap_migrate_backup.xml b/vector/src/main/res/layout/fragment_bootstrap_migrate_backup.xml
new file mode 100644
index 0000000000..68417044f9
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_bootstrap_migrate_backup.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="16dp">
+
+    <TextView
+        android:id="@+id/bootstrapDescriptionText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:textColor="?riotx_text_primary"
+        android:textSize="14sp"
+        app:layout_constraintBottom_toTopOf="@id/bootstrapRecoveryKeyEnterTil"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="@string/bootstrap_enter_recovery" />
+
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/bootstrapRecoveryKeyEnterTil"
+        style="@style/VectorTextInputLayout"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="8dp"
+        app:errorEnabled="true"
+        app:layout_constraintEnd_toStartOf="@id/bootstrapMigrateShowPassword"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/bootstrapDescriptionText">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/bootstrapMigrateEditText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:imeOptions="actionDone"
+            android:maxLines="3"
+            android:singleLine="false"
+            android:textColor="?android:textColorPrimary"
+            tools:hint="@string/keys_backup_restore_key_enter_hint"
+            tools:inputType="textPassword" />
+
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/bootstrapMigrateUseFile"
+            style="@style/Widget.MaterialComponents.Button.TextButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/use_file"
+            android:textAllCaps="false"
+            app:icon="@drawable/ic_file"
+            app:iconTint="@color/button_positive_text_color_selector"
+            tools:visibility="visible" />
+
+        <TextView
+            android:id="@+id/bootstrapMigrateForgotPassphrase"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            tools:text="@string/keys_backup_restore_with_passphrase_helper_with_link"
+            tools:visibility="visible" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <ImageView
+        android:id="@+id/bootstrapMigrateShowPassword"
+        android:layout_width="@dimen/layout_touch_size"
+        android:layout_height="@dimen/layout_touch_size"
+        android:layout_marginTop="8dp"
+        android:background="?attr/selectableItemBackground"
+        android:scaleType="center"
+        android:src="@drawable/ic_eye_black"
+        android:tint="?colorAccent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/bootstrapRecoveryKeyEnterTil"
+        app:layout_constraintTop_toTopOf="@+id/bootstrapRecoveryKeyEnterTil" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/bootstrapMigrateContinueButton"
+        style="@style/VectorButtonStyleText"
+        android:layout_gravity="end"
+        android:layout_marginTop="@dimen/layout_vertical_margin"
+        android:text="@string/_continue"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/bootstrapRecoveryKeyEnterTil" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 6b0591595a..66638e5d62 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -2202,7 +2202,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
 
     <string name="refresh">Refresh</string>
 
-    <string name="new_session">New Session</string>
+    <string name="new_session">Unverified login. Was this you?</string>
     <string name="new_session_review">Tap to review &amp; verify</string>
     <string name="verify_new_session_notice">Use this session to verify your new one, granting it access to encrypted messages.</string>
     <string name="verify_new_session_was_not_me">This wasn’t me</string>
@@ -2219,7 +2219,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
     <string name="verify_cancelled_notice">Verify your devices from Settings.</string>
     <string name="verification_cancelled">Verification Cancelled</string>
 
-    <string name="recovery_passphrase">Message Password</string>
+    <string name="recovery_passphrase">Recovery Passphrase</string>
     <string name="message_key">Message Key</string>
     <string name="account_password">Account Password</string>
 
@@ -2243,7 +2243,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
     <string name="bootstrap_loading_text">This might take several seconds, please be patient.</string>
     <string name="bootstrap_loading_title">Setting up recovery.</string>
     <string name="your_recovery_key">Your recovery key</string>
-    <string name="bootstrap_finish_title">Youβ€˜re done!</string>
+    <string name="bootstrap_finish_title">"You're done!"</string>
     <string name="keep_it_safe">Keep it safe</string>
     <string name="finish">Finish</string>
 
@@ -2269,8 +2269,8 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
 
     <string name="auth_flow_not_supported">You cannot do that from mobile</string>
 
-    <string name="bootstrap_skip_text">Setting a Message Password lets you secure &amp; unlock encrypted messages and trust.\n\nIf you don’t want to set a Message Password, generate a Message Key instead.</string>
-    <string name="bootstrap_skip_text_no_gen_key">Setting a Message Password lets you secure &amp; unlock encrypted messages and trust.</string>
+    <string name="bootstrap_skip_text">Setting a Recovery Passphrase lets you secure &amp; unlock encrypted messages and trust.\n\nIf you don’t want to set a Message Password, generate a Message Key instead.</string>
+    <string name="bootstrap_skip_text_no_gen_key">Setting a Recovery Passphrase lets you secure &amp; unlock encrypted messages and trust.</string>
 
 
     <string name="encryption_enabled">Encryption enabled</string>
diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
index b7b65ad311..2cd97098af 100644
--- a/vector/src/main/res/values/strings_riotX.xml
+++ b/vector/src/main/res/values/strings_riotX.xml
@@ -7,6 +7,37 @@
 
     <!-- BEGIN Strings added by Valere -->
     <string name="room_message_placeholder">Message…</string>
+
+    <string name="upgrade_security">Encryption upgrade available</string>
+    <string name="security_prompt_text">Verify yourself &amp; others to keep your chats safe</string>
+
+    <!-- %s will be replaced by recovery_key -->
+    <string name="bootstrap_enter_recovery">Enter your %s to continue</string>
+    <string name="use_file">Use File</string>
+
+    <!-- %s will be replaced by recovery_passphrase -->
+    <!--    <string name="upgrade_account_desc">Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.</string>-->
+    <string name="enter_backup_passphrase">Enter %s</string>
+    <string name="backup_recovery_passphrase">Recovery Passphrase</string>
+    <string name="bootstrap_invalid_recovery_key">"It's not a valid recovery key"</string>
+    <string name="recovery_key_empty_error_message">Please enter a recovery key</string>
+
+    <string name="bootstrap_progress_checking_backup">Checking backup Key</string>
+    <string name="bootstrap_progress_checking_backup_with_info">Checking backup Key (%s)</string>
+    <string name="bootstrap_progress_compute_curve_key">Getting curve key</string>
+    <string name="bootstrap_progress_generating_ssss">Generating SSSS key from passphrase</string>
+    <string name="bootstrap_progress_generating_ssss_with_info">Generating SSSS key from passphrase (%s)</string>
+    <string name="bootstrap_progress_generating_ssss_recovery">Generating SSSS key from recovery key</string>
+    <string name="bootstrap_progress_storing_in_sss">Storing keybackup secret in SSSS</string>
+    <!-- To produce things like 'RiotX Android (IQDHUVJTTV)' -->
+    <string name="new_session_review_with_info">%1$s (%2$s)</string>
+
+    <string name="bootstrap_migration_enter_backup_password">Enter your Key Backup Passphrase to continue.</string>
+    <string name="bootstrap_migration_use_recovery_key">use your Key Backup recovery key</string>
+    <!-- %s will be replaced by the value of bootstrap_migration_use_recovery_key  -->
+    <string name="bootstrap_migration_with_passphrase_helper_with_link">Don’t know your Key Backup Passphrase, you can %s.</string>
+    <string name="bootstrap_migration_backup_recovery_key">Key Backup recovery key</string>
+
     <!-- END Strings added by Valere -->
 
 
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 1002c16782..f394e319c7 100644
--- a/vector/src/main/res/xml/vector_settings_security_privacy.xml
+++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml
@@ -6,9 +6,7 @@
     <!-- ************ Cryptography section ************ -->
     <im.vector.riotx.core.preference.VectorPreferenceCategory
         android:key="SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY"
-        android:title="@string/settings_cryptography"
-        app:isPreferenceVisible="false"
-        tools:isPreferenceVisible="true">
+        android:title="@string/settings_cryptography">
         <im.vector.riotx.core.preference.VectorPreference
             android:key="SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY"
             android:persistent="false"