diff --git a/CHANGES.md b/CHANGES.md
index 6f8b8ead15..ea0e51561b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,12 +3,16 @@ Changes in RiotX 0.19.0 (2020-XX-XX)
 
 Features ✨:
  - Cross-Signing | Support SSSS secret sharing (#944)
+ - Cross-Signing | Verify new session from existing session (#1134)
 
 Improvements 🙌:
  - Verification DM / Handle concurrent .start after .ready (#794)
+ - Reimplementation of multiple attachment picker
 
 Bugfix 🐛:
  - Missing avatar/displayname after verification request message (#841)
+ - Crypto | RiotX sometimes rotate the current device keys (#1170)
+ - RiotX can't restore cross signing keys saved by web in SSSS (#1174)
 
 Translations 🗣:
  -
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 60f22a4bdf..c10aaf3545 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -119,7 +119,7 @@ dependencies {
     implementation "ru.noties.markwon:core:$markwon_version"
 
     // Image
-    implementation 'androidx.exifinterface:exifinterface:1.1.0'
+    implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
     implementation 'id.zelory:compressor:3.0.0'
 
     // Database
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt
index 08c81f56c0..3cf03fff53 100644
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt
@@ -266,8 +266,8 @@ class CommonTestHelper(context: Context) {
      * @param latch
      * @throws InterruptedException
      */
-    fun await(latch: CountDownLatch) {
-        assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
+    fun await(latch: CountDownLatch, timout: Long? = TestConstants.timeOutMillis) {
+        assertTrue(latch.await(timout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
     }
 
     fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
@@ -282,10 +282,10 @@ class CommonTestHelper(context: Context) {
         }
     }
 
-    fun waitWithLatch(block: (CountDownLatch) -> Unit) {
+    fun waitWithLatch(timout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
         val latch = CountDownLatch(1)
         block(latch)
-        await(latch)
+        await(latch, timout)
     }
 
     // Transform a method with a MatrixCallback to a synchronous method
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt
index c8d2df38ce..aec5e6c423 100644
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt
@@ -19,6 +19,12 @@ package im.vector.matrix.android.internal.crypto.gossiping
 import android.util.Log
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import im.vector.matrix.android.InstrumentedTest
+import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
+import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
+import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
+import im.vector.matrix.android.api.session.crypto.verification.VerificationService
+import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
+import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
 import im.vector.matrix.android.api.session.events.model.toModel
 import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
 import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
@@ -28,7 +34,11 @@ import im.vector.matrix.android.common.TestConstants
 import im.vector.matrix.android.internal.crypto.GossipingRequestState
 import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState
 import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
+import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
+import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
 import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
+import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
+import junit.framework.TestCase.assertEquals
 import junit.framework.TestCase.assertNotNull
 import junit.framework.TestCase.assertTrue
 import junit.framework.TestCase.fail
@@ -174,4 +184,85 @@ class KeyShareTests : InstrumentedTest {
         mTestHelper.signOutAndClose(aliceSession)
         mTestHelper.signOutAndClose(aliceSession2)
     }
+
+    @Test
+    fun test_ShareSSSSSecret() {
+        val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+
+        mTestHelper.doSync<Unit> {
+            aliceSession1.cryptoService().crossSigningService()
+                    .initializeCrossSigning(UserPasswordAuth(
+                            user = aliceSession1.myUserId,
+                            password = TestConstants.PASSWORD
+                    ), it)
+        }
+
+        val aliceSession2 = mTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
+
+        val aliceVerificationService1 = aliceSession1.cryptoService().verificationService()
+        val aliceVerificationService2 = aliceSession2.cryptoService().verificationService()
+
+        // force keys download
+        mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
+            aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it)
+        }
+        mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
+            aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it)
+        }
+
+        var session1ShortCode: String? = null
+        var session2ShortCode: String? = null
+
+        aliceVerificationService1.addListener(object : VerificationService.Listener {
+            override fun transactionUpdated(tx: VerificationTransaction) {
+                Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
+                if (tx is SasVerificationTransaction) {
+                    if (tx.state == VerificationTxState.OnStarted) {
+                        (tx as IncomingSasVerificationTransaction).performAccept()
+                    }
+                    if (tx.state == VerificationTxState.ShortCodeReady) {
+                        session1ShortCode = tx.getDecimalCodeRepresentation()
+                        tx.userHasVerifiedShortCode()
+                    }
+                }
+            }
+        })
+
+        aliceVerificationService2.addListener(object : VerificationService.Listener {
+            override fun transactionUpdated(tx: VerificationTransaction) {
+                Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
+                if (tx is SasVerificationTransaction) {
+                    if (tx.state == VerificationTxState.ShortCodeReady) {
+                        session2ShortCode = tx.getDecimalCodeRepresentation()
+                        tx.userHasVerifiedShortCode()
+                    }
+                }
+            }
+        })
+
+        val txId: String = "m.testVerif12"
+        aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.credentials.deviceId
+                ?: "", txId)
+
+        mTestHelper.waitWithLatch { latch ->
+            mTestHelper.retryPeriodicallyWithLatch(latch) {
+                aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.credentials.deviceId ?: "")?.isVerified == true
+            }
+        }
+
+        assertNotNull(session1ShortCode)
+        Log.d("#TEST", "session1ShortCode: $session1ShortCode")
+        assertNotNull(session2ShortCode)
+        Log.d("#TEST", "session2ShortCode: $session2ShortCode")
+        assertEquals(session1ShortCode, session2ShortCode)
+
+        // SSK and USK private keys should have been shared
+
+        mTestHelper.waitWithLatch(60_000) { latch ->
+            mTestHelper.retryPeriodicallyWithLatch(latch) {
+                Log.d("#TEST", "CAN XS :${ aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}")
+                aliceSession2.cryptoService().crossSigningService().canCrossSign()
+            }
+        }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt
index e32bb9f21f..b80a17b017 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt
@@ -16,6 +16,7 @@
 
 package im.vector.matrix.android.api.session.content
 
+import android.net.Uri
 import android.os.Parcelable
 import androidx.exifinterface.media.ExifInterface
 import kotlinx.android.parcel.Parcelize
@@ -29,8 +30,7 @@ data class ContentAttachmentData(
         val width: Long? = 0,
         val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
         val name: String? = null,
-        val queryUri: String,
-        val path: String,
+        val queryUri: Uri,
         private val mimeType: String?,
         val type: Type
 ) : Parcelable {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt
index 75033082d6..4482101434 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt
@@ -60,6 +60,8 @@ interface VerificationService {
                                     roomId: String,
                                     localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
 
+    fun cancelVerificationRequest(request: PendingVerificationRequest)
+
     /**
      * Request a key verification from another user using toDevice events.
      */
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt
index 9e702ee9ac..d0a08b17ab 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt
@@ -140,7 +140,7 @@ internal class DefaultCryptoService @Inject constructor(
 
         private val crossSigningService: DefaultCrossSigningService,
         //
-        private val incomingRoomKeyRequestManager: IncomingRoomKeyRequestManager,
+        private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
         //
         private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
         // Actions
@@ -239,7 +239,7 @@ internal class DefaultCryptoService @Inject constructor(
     override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
         getDevicesTask
                 .configureWith {
-                    this.executionThread = TaskThread.CRYPTO
+//                    this.executionThread = TaskThread.CRYPTO
                     this.callback = callback
                 }
                 .executeBy(taskExecutor)
@@ -317,7 +317,7 @@ internal class DefaultCryptoService @Inject constructor(
                 deviceListManager.invalidateAllDeviceLists()
                 deviceListManager.refreshOutdatedDeviceLists()
             } else {
-                incomingRoomKeyRequestManager.processReceivedGossipingRequests()
+                incomingGossipingRequestManager.processReceivedGossipingRequests()
             }
         }.fold(
                 {
@@ -376,7 +376,7 @@ internal class DefaultCryptoService @Inject constructor(
                     // Make sure we process to-device messages before generating new one-time-keys #2782
                     deviceListManager.refreshOutdatedDeviceLists()
                     oneTimeKeysUploader.maybeUploadOneTimeKeys()
-                    incomingRoomKeyRequestManager.processReceivedGossipingRequests()
+                    incomingGossipingRequestManager.processReceivedGossipingRequests()
                 }
             }
         }
@@ -709,7 +709,7 @@ internal class DefaultCryptoService @Inject constructor(
                     // save audit trail
                     cryptoStore.saveGossipingEvent(event)
                     // Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
-                    incomingRoomKeyRequestManager.onGossipingRequestEvent(event)
+                    incomingGossipingRequestManager.onGossipingRequestEvent(event)
                 }
                 EventType.SEND_SECRET                            -> {
                     cryptoStore.saveGossipingEvent(event)
@@ -729,30 +729,30 @@ internal class DefaultCryptoService @Inject constructor(
      */
     private fun onRoomKeyEvent(event: Event) {
         val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
-        Timber.v("## onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>")
+        Timber.v("## GOSSIP onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>")
         if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
-            Timber.e("## onRoomKeyEvent() : missing fields")
+            Timber.e("## GOSSIP onRoomKeyEvent() : missing fields")
             return
         }
         val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)
         if (alg == null) {
-            Timber.e("## onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
+            Timber.e("## GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
             return
         }
         alg.onRoomKeyEvent(event, keysBackupService)
     }
 
     private fun onSecretSendReceived(event: Event) {
-        Timber.i("## onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}")
+        Timber.i("## GOSSIP onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}")
         if (!event.isEncrypted()) {
             // secret send messages must be encrypted
-            Timber.e("## onSecretSend() :Received unencrypted secret send event")
+            Timber.e("## GOSSIP onSecretSend() :Received unencrypted secret send event")
             return
         }
 
         // Was that sent by us?
         if (event.senderId != credentials.userId) {
-            Timber.e("## onSecretSend() : Ignore secret from other user ${event.senderId}")
+            Timber.e("## GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
             return
         }
 
@@ -762,7 +762,7 @@ internal class DefaultCryptoService @Inject constructor(
                 .getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId }
 
         if (existingRequest == null) {
-            Timber.i("## onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
+            Timber.i("## GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
             return
         }
 
@@ -1111,7 +1111,7 @@ internal class DefaultCryptoService @Inject constructor(
      * @param listener listener
      */
     override fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
-        incomingRoomKeyRequestManager.addRoomKeysRequestListener(listener)
+        incomingGossipingRequestManager.addRoomKeysRequestListener(listener)
     }
 
     /**
@@ -1120,7 +1120,7 @@ internal class DefaultCryptoService @Inject constructor(
      * @param listener listener
      */
     override fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
-        incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener)
+        incomingGossipingRequestManager.removeRoomKeysRequestListener(listener)
     }
 
     /**
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt
index b218a2e387..f2e45ef109 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt
@@ -25,7 +25,9 @@ enum class GossipingRequestState {
     NONE,
     PENDING,
     REJECTED,
+    ACCEPTING,
     ACCEPTED,
+    FAILED_TO_ACCEPTED,
    // USER_REJECTED,
     UNABLE_TO_PROCESS,
     CANCELLED_BY_REQUESTER,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingWorkManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingWorkManager.kt
new file mode 100644
index 0000000000..b7c782c5b4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingWorkManager.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.matrix.android.internal.crypto
+
+import androidx.work.BackoffPolicy
+import androidx.work.Data
+import androidx.work.ExistingWorkPolicy
+import androidx.work.ListenableWorker
+import androidx.work.OneTimeWorkRequest
+import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.internal.di.WorkManagerProvider
+import im.vector.matrix.android.internal.session.SessionScope
+import im.vector.matrix.android.internal.util.CancelableWork
+import im.vector.matrix.android.internal.worker.startChain
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+@SessionScope
+internal class GossipingWorkManager @Inject constructor(
+        private val workManagerProvider: WorkManagerProvider
+) {
+
+    inline fun <reified W : ListenableWorker> createWork(data: Data, startChain: Boolean): OneTimeWorkRequest {
+        return workManagerProvider.matrixOneTimeWorkRequestBuilder<W>()
+                .setConstraints(WorkManagerProvider.workConstraints)
+                .startChain(startChain)
+                .setInputData(data)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS)
+                .build()
+    }
+
+    // Prevent sending queue to stay broken after app restart
+    // The unique queue id will stay the same as long as this object is instanciated
+    val queueSuffixApp = System.currentTimeMillis()
+
+    fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
+        workManagerProvider.workManager
+                .beginUniqueWork(this::class.java.name + "_$queueSuffixApp", policy, workRequest)
+                .enqueue()
+
+        return CancelableWork(workManagerProvider.workManager, workRequest.id)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt
similarity index 72%
rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt
index 0bb89154f1..8bec87b341 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt
@@ -17,6 +17,7 @@
 package im.vector.matrix.android.internal.crypto
 
 import im.vector.matrix.android.api.auth.data.Credentials
+import im.vector.matrix.android.api.auth.data.sessionId
 import im.vector.matrix.android.api.crypto.MXCryptoConfig
 import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
 import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
@@ -27,16 +28,19 @@ import im.vector.matrix.android.api.session.events.model.toModel
 import im.vector.matrix.android.internal.crypto.model.rest.GossipingDefaultContent
 import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject
 import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
+import im.vector.matrix.android.internal.di.SessionId
 import im.vector.matrix.android.internal.session.SessionScope
+import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 import timber.log.Timber
 import javax.inject.Inject
 
 @SessionScope
-internal class IncomingRoomKeyRequestManager @Inject constructor(
+internal class IncomingGossipingRequestManager @Inject constructor(
+        @SessionId private val sessionId: String,
         private val credentials: Credentials,
         private val cryptoStore: IMXCryptoStore,
         private val cryptoConfig: MXCryptoConfig,
-        private val secretSecretCryptoProvider: ShareSecretCryptoProvider,
+        private val gossipingWorkManager: GossipingWorkManager,
         private val roomDecryptorProvider: RoomDecryptorProvider) {
 
     // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
@@ -51,6 +55,32 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
         receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests())
     }
 
+    // Recently verified devices (map of deviceId and timestamp)
+    private val recentlyVerifiedDevices = HashMap<String, Long>()
+
+    /**
+     * Called when a session has been verified.
+     * This information can be used by the manager to decide whether or not to fullfil gossiping requests
+     */
+    fun onVerificationCompleteForDevice(deviceId: String) {
+        // For now we just keep an in memory cache
+        synchronized(recentlyVerifiedDevices) {
+            recentlyVerifiedDevices[deviceId] = System.currentTimeMillis()
+        }
+    }
+
+    private fun hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId: String): Boolean {
+        val verifTimestamp: Long?
+        synchronized(recentlyVerifiedDevices) {
+            verifTimestamp = recentlyVerifiedDevices[deviceId]
+        }
+        if (verifTimestamp == null) return false
+
+        val age = System.currentTimeMillis() - verifTimestamp
+
+        return age < FIVE_MINUTES_IN_MILLIS
+    }
+
     /**
      * Called when we get an m.room_key_request event
      * It must be called on CryptoThread
@@ -58,7 +88,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
      * @param event the announcement event.
      */
     fun onGossipingRequestEvent(event: Event) {
-        Timber.v("## onGossipingRequestEvent type ${event.type} from user ${event.senderId}")
+        Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}")
         val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>()
         val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
         when (roomKeyShare?.action) {
@@ -67,7 +97,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
                     IncomingSecretShareRequest.fromEvent(event)?.let {
                         if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
                             // ignore, it was sent by me as *
-                            Timber.v("## onGossipingRequestEvent type ${event.type} ignore remote echo")
+                            Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
                         } else {
                             // save in DB
                             cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
@@ -78,7 +108,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
                     IncomingRoomKeyRequest.fromEvent(event)?.let {
                         if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
                             // ignore, it was sent by me as *
-                            Timber.v("## onGossipingRequestEvent type ${event.type} ignore remote echo")
+                            Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
                         } else {
                             cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
                             receivedGossipingRequests.add(it)
@@ -92,7 +122,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
                 }
             }
             else                                              -> {
-                Timber.e("## onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}")
+                Timber.e("## GOSSIP onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}")
             }
         }
     }
@@ -103,8 +133,6 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
      * It must be called on CryptoThread
      */
     fun processReceivedGossipingRequests() {
-        Timber.v("## processReceivedGossipingRequests()")
-
         val roomKeyRequestsToProcess = receivedGossipingRequests.toList()
         receivedGossipingRequests.clear()
         for (request in roomKeyRequestsToProcess) {
@@ -125,7 +153,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
         }
 
         receivedRequestCancellations?.forEach { request ->
-            Timber.v("## processReceivedGossipingRequests() : m.room_key_request cancellation $request")
+            Timber.v("## GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request")
             // we should probably only notify the app of cancellations we told it
             // about, but we don't currently have a record of that, so we just pass
             // everything through.
@@ -154,10 +182,10 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
         val roomId = body!!.roomId
         val alg = body.algorithm
 
-        Timber.v("## processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
+        Timber.v("## GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
         if (userId == null || credentials.userId != userId) {
             // TODO: determine if we sent this device the keys already: in
-            Timber.w("## processReceivedGossipingRequests() : Ignoring room key request from other user for now")
+            Timber.w("## GOSSIP processReceivedGossipingRequests() : Ignoring room key request from other user for now")
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
             return
         }
@@ -166,18 +194,18 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
         // the keys for the requested events, and can drop the requests.
         val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
         if (null == decryptor) {
-            Timber.w("## processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId")
+            Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId")
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
             return
         }
         if (!decryptor.hasKeysForKeyRequest(request)) {
-            Timber.w("## processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}")
+            Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}")
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
             return
         }
 
         if (credentials.deviceId == deviceId && credentials.userId == userId) {
-            Timber.v("## processReceivedGossipingRequests() : oneself device - ignored")
+            Timber.v("## GOSSIP processReceivedGossipingRequests() : oneself device - ignored")
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
             return
         }
@@ -192,13 +220,13 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
         val device = cryptoStore.getUserDevice(userId, deviceId!!)
         if (device != null) {
             if (device.isVerified) {
-                Timber.v("## processReceivedGossipingRequests() : device is already verified: sharing keys")
+                Timber.v("## GOSSIP processReceivedGossipingRequests() : device is already verified: sharing keys")
                 request.share?.run()
                 return
             }
 
             if (device.isBlocked) {
-                Timber.v("## processReceivedGossipingRequests() : device is blocked -> ignored")
+                Timber.v("## GOSSIP processReceivedGossipingRequests() : device is blocked -> ignored")
                 cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
                 return
             }
@@ -219,30 +247,30 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
     private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) {
         val secretName = request.secretName ?: return Unit.also {
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            Timber.v("## processIncomingSecretShareRequest() : Missing secret name")
+            Timber.v("## GOSSIP processIncomingSecretShareRequest() : Missing secret name")
         }
 
         val userId = request.userId
         if (userId == null || credentials.userId != userId) {
-            Timber.e("## processIncomingSecretShareRequest() : Ignoring secret share request from other users")
+            Timber.e("## GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from other users")
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
             return
         }
 
         val deviceId = request.deviceId
                 ?: return Unit.also {
-                    Timber.e("## processIncomingSecretShareRequest() : Malformed request, no ")
+                    Timber.e("## GOSSIP processIncomingSecretShareRequest() : Malformed request, no ")
                     cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
                 }
 
         val device = cryptoStore.getUserDevice(userId, deviceId)
                 ?: return Unit.also {
-                    Timber.e("## processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}")
+                    Timber.e("## GOSSIP processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}")
                     cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
                 }
 
         if (!device.isVerified || device.isBlocked) {
-            Timber.v("## processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device")
+            Timber.v("## GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device")
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
             return
         }
@@ -255,11 +283,20 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
             USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
             else                       -> null
         }?.let { secretValue ->
-            // TODO check if locally trusted and not outdated
-            Timber.i("## processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted")
-            if (isDeviceLocallyVerified == true) {
-                secretSecretCryptoProvider.shareSecretWithDevice(request, secretValue)
-                cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
+            Timber.i("## GOSSIP processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted")
+            if (isDeviceLocallyVerified == true && hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId)) {
+                val params = SendGossipWorker.Params(
+                        sessionId = sessionId,
+                        secretValue = secretValue,
+                        request = request
+                )
+
+                cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
+                val workRequest = gossipingWorkManager.createWork<SendGossipWorker>(WorkerParamsFactory.toData(params), true)
+                gossipingWorkManager.postWork(workRequest)
+            } else {
+                Timber.v("## GOSSIP processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old")
+                cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
             }
             return
         }
@@ -269,7 +306,16 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
         }
 
         request.share = { secretValue ->
-            secretSecretCryptoProvider.shareSecretWithDevice(request, secretValue)
+
+            val params = SendGossipWorker.Params(
+                    sessionId = userId,
+                    secretValue = secretValue,
+                    request = request
+            )
+
+            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
+            val workRequest = gossipingWorkManager.createWork<SendGossipWorker>(WorkerParamsFactory.toData(params), true)
+            gossipingWorkManager.postWork(workRequest)
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
         }
 
@@ -304,7 +350,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
                         return
                     }
                 } catch (e: Exception) {
-                    Timber.e(e, "## onRoomKeyRequest() failed")
+                    Timber.e(e, "## GOSSIP onRoomKeyRequest() failed")
                 }
             }
         }
@@ -323,7 +369,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
                 try {
                     listener.onRoomKeyRequestCancellation(request)
                 } catch (e: Exception) {
-                    Timber.e(e, "## onRoomKeyRequestCancellation() failed")
+                    Timber.e(e, "## GOSSIP onRoomKeyRequestCancellation() failed")
                 }
             }
         }
@@ -340,4 +386,8 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
             gossipingRequestListeners.remove(listener)
         }
     }
+
+    companion object {
+        private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt
index 54f34c6c9d..0f48b5ecfb 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt
@@ -59,9 +59,6 @@ internal class MXOlmDevice @Inject constructor(
     var deviceEd25519Key: String? = null
         private set
 
-    // The OLM lib account instance.
-    private var olmAccount: OlmAccount? = null
-
     // The OLM lib utility instance.
     private var olmUtility: OlmUtility? = null
 
@@ -86,19 +83,10 @@ internal class MXOlmDevice @Inject constructor(
 
     init {
         // Retrieve the account from the store
-        olmAccount = store.getAccount()
-
-        if (null == olmAccount) {
-            Timber.v("MXOlmDevice : create a new olm account")
-            // Else, create it
-            try {
-                olmAccount = OlmAccount()
-                store.storeAccount(olmAccount!!)
-            } catch (e: Exception) {
-                Timber.e(e, "MXOlmDevice : cannot initialize olmAccount")
-            }
-        } else {
-            Timber.v("MXOlmDevice : use an existing account")
+        try {
+            store.getOrCreateOlmAccount()
+        } catch (e: Exception) {
+            Timber.e(e, "MXOlmDevice : cannot initialize olmAccount")
         }
 
         try {
@@ -109,13 +97,13 @@ internal class MXOlmDevice @Inject constructor(
         }
 
         try {
-            deviceCurve25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY]
+            deviceCurve25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY]
         } catch (e: Exception) {
             Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error")
         }
 
         try {
-            deviceEd25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
+            deviceEd25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
         } catch (e: Exception) {
             Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error")
         }
@@ -126,7 +114,7 @@ internal class MXOlmDevice @Inject constructor(
      */
     fun getOneTimeKeys(): Map<String, Map<String, String>>? {
         try {
-            return olmAccount!!.oneTimeKeys()
+            return store.getOlmAccount().oneTimeKeys()
         } catch (e: Exception) {
             Timber.e(e, "## getOneTimeKeys() : failed")
         }
@@ -138,14 +126,13 @@ internal class MXOlmDevice @Inject constructor(
      * @return The maximum number of one-time keys the olm account can store.
      */
     fun getMaxNumberOfOneTimeKeys(): Long {
-        return olmAccount?.maxOneTimeKeys() ?: -1
+        return store.getOlmAccount().maxOneTimeKeys()
     }
 
     /**
      * Release the instance
      */
     fun release() {
-        olmAccount?.releaseAccount()
         olmUtility?.releaseUtility()
     }
 
@@ -157,7 +144,7 @@ internal class MXOlmDevice @Inject constructor(
      */
     fun signMessage(message: String): String? {
         try {
-            return olmAccount!!.signMessage(message)
+            return store.getOlmAccount().signMessage(message)
         } catch (e: Exception) {
             Timber.e(e, "## signMessage() : failed")
         }
@@ -170,8 +157,8 @@ internal class MXOlmDevice @Inject constructor(
      */
     fun markKeysAsPublished() {
         try {
-            olmAccount!!.markOneTimeKeysAsPublished()
-            store.storeAccount(olmAccount!!)
+            store.getOlmAccount().markOneTimeKeysAsPublished()
+            store.saveOlmAccount()
         } catch (e: Exception) {
             Timber.e(e, "## markKeysAsPublished() : failed")
         }
@@ -184,8 +171,8 @@ internal class MXOlmDevice @Inject constructor(
      */
     fun generateOneTimeKeys(numKeys: Int) {
         try {
-            olmAccount!!.generateOneTimeKeys(numKeys)
-            store.storeAccount(olmAccount!!)
+            store.getOlmAccount().generateOneTimeKeys(numKeys)
+            store.saveOlmAccount()
         } catch (e: Exception) {
             Timber.e(e, "## generateOneTimeKeys() : failed")
         }
@@ -205,7 +192,7 @@ internal class MXOlmDevice @Inject constructor(
 
         try {
             olmSession = OlmSession()
-            olmSession.initOutboundSession(olmAccount!!, theirIdentityKey, theirOneTimeKey)
+            olmSession.initOutboundSession(store.getOlmAccount(), theirIdentityKey, theirOneTimeKey)
 
             val olmSessionWrapper = OlmSessionWrapper(olmSession, 0)
 
@@ -245,7 +232,7 @@ internal class MXOlmDevice @Inject constructor(
         try {
             try {
                 olmSession = OlmSession()
-                olmSession.initInboundSessionFrom(olmAccount!!, theirDeviceIdentityKey, ciphertext)
+                olmSession.initInboundSessionFrom(store.getOlmAccount(), theirDeviceIdentityKey, ciphertext)
             } catch (e: Exception) {
                 Timber.e(e, "## createInboundSession() : the session creation failed")
                 return null
@@ -254,8 +241,8 @@ internal class MXOlmDevice @Inject constructor(
             Timber.v("## createInboundSession() : sessionId: ${olmSession.sessionIdentifier()}")
 
             try {
-                olmAccount!!.removeOneTimeKeys(olmSession)
-                store.storeAccount(olmAccount!!)
+                store.getOlmAccount().removeOneTimeKeys(olmSession)
+                store.saveOlmAccount()
             } catch (e: Exception) {
                 Timber.e(e, "## createInboundSession() : removeOneTimeKeys failed")
             }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt
index 7c83ccc9bf..c06f10b106 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt
@@ -17,27 +17,17 @@
 
 package im.vector.matrix.android.internal.crypto
 
-import androidx.work.BackoffPolicy
-import androidx.work.Data
-import androidx.work.ExistingWorkPolicy
-import androidx.work.ListenableWorker
-import androidx.work.OneTimeWorkRequest
 import im.vector.matrix.android.api.session.events.model.LocalEcho
-import im.vector.matrix.android.api.util.Cancelable
 import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
 import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
 import im.vector.matrix.android.internal.di.SessionId
-import im.vector.matrix.android.internal.di.WorkManagerProvider
 import im.vector.matrix.android.internal.session.SessionScope
-import im.vector.matrix.android.internal.util.CancelableWork
 import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
 import im.vector.matrix.android.internal.worker.WorkerParamsFactory
-import im.vector.matrix.android.internal.worker.startChain
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import timber.log.Timber
-import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
 @SessionScope
@@ -46,7 +36,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
         private val cryptoStore: IMXCryptoStore,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val cryptoCoroutineScope: CoroutineScope,
-        private val workManagerProvider: WorkManagerProvider) {
+        private val gossipingWorkManager: GossipingWorkManager) {
 
     /**
      * Send off a room key request, if we haven't already done so.
@@ -65,7 +55,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
             cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients)?.let {
                 // Don't resend if it's already done, you need to cancel first (reRequest)
                 if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
-                    Timber.v("## sendOutgoingRoomKeyRequest() : we already request for that session: $it")
+                    Timber.v("## GOSSIP sendOutgoingRoomKeyRequest() : we already request for that session: $it")
                     return@launch
                 }
 
@@ -82,7 +72,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
             cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let {
                 // TODO check if there is already one that is being sent?
                 if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
-                    Timber.v("## sendOutgoingRoomKeyRequest() : we already request for that session: $it")
+                    Timber.v("## GOSSIP sendSecretShareRequest() : we already request for that session: $it")
                     return@launch
                 }
 
@@ -123,7 +113,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
         val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody)
                 ?: // no request was made for this key
                 return Unit.also {
-                    Timber.v("## cancelRoomKeyRequest() Unknown request")
+                    Timber.v("## GOSSIP cancelRoomKeyRequest() Unknown request")
                 }
 
         sendOutgoingRoomKeyRequestCancellation(req, andResend)
@@ -135,7 +125,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
      * @param request the request
      */
     private fun sendOutgoingGossipingRequest(request: OutgoingGossipingRequest) {
-        Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys $request")
+        Timber.v("## GOSSIP sendOutgoingRoomKeyRequest() : Requesting keys $request")
 
         val params = SendGossipRequestWorker.Params(
                 sessionId = sessionId,
@@ -143,8 +133,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
                 secretShareRequest = request as? OutgoingSecretRequest
         )
         cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING)
-        val workRequest = createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(params), true)
-        postWork(workRequest)
+        val workRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(params), true)
+        gossipingWorkManager.postWork(workRequest)
     }
 
     /**
@@ -157,33 +147,16 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
         val params = CancelGossipRequestWorker.Params.fromRequest(sessionId, request)
         cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.CANCELLING)
 
-        val workRequest = createWork<CancelGossipRequestWorker>(WorkerParamsFactory.toData(params), true)
-        postWork(workRequest)
+        val workRequest = gossipingWorkManager.createWork<CancelGossipRequestWorker>(WorkerParamsFactory.toData(params), true)
+        gossipingWorkManager.postWork(workRequest)
 
         if (resend) {
             val reSendParams = SendGossipRequestWorker.Params(
                     sessionId = sessionId,
                     keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId())
             )
-            val reSendWorkRequest = createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(reSendParams), true)
-            postWork(reSendWorkRequest)
+            val reSendWorkRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(reSendParams), true)
+            gossipingWorkManager.postWork(reSendWorkRequest)
         }
     }
-
-    private inline fun <reified W : ListenableWorker> createWork(data: Data, startChain: Boolean): OneTimeWorkRequest {
-        return workManagerProvider.matrixOneTimeWorkRequestBuilder<W>()
-                .setConstraints(WorkManagerProvider.workConstraints)
-                .startChain(startChain)
-                .setInputData(data)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS)
-                .build()
-    }
-
-    private fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
-        workManagerProvider.workManager
-                .beginUniqueWork(this::class.java.name, policy, workRequest)
-                .enqueue()
-
-        return CancelableWork(workManagerProvider.workManager, workRequest.id)
-    }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt
new file mode 100644
index 0000000000..8a273da338
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.matrix.android.internal.crypto
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.Data
+import androidx.work.WorkerParameters
+import com.squareup.moshi.JsonClass
+import im.vector.matrix.android.api.auth.data.Credentials
+import im.vector.matrix.android.api.failure.shouldBeRetried
+import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.api.session.events.model.LocalEcho
+import im.vector.matrix.android.api.session.events.model.toContent
+import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
+import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
+import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
+import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
+import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
+import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
+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 SendGossipWorker(context: Context,
+                                params: WorkerParameters)
+    : CoroutineWorker(context, params) {
+
+    @JsonClass(generateAdapter = true)
+    internal data class Params(
+            val sessionId: String,
+            val secretValue: String,
+            val request: IncomingSecretShareRequest
+    )
+
+    @Inject lateinit var sendToDeviceTask: SendToDeviceTask
+    @Inject lateinit var cryptoStore: IMXCryptoStore
+    @Inject lateinit var eventBus: EventBus
+    @Inject lateinit var credentials: Credentials
+    @Inject lateinit var messageEncrypter: MessageEncrypter
+    @Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
+
+    override suspend fun doWork(): Result {
+        val errorOutputData = Data.Builder().putBoolean("failed", true).build()
+        val params = WorkerParamsFactory.fromData<Params>(inputData)
+                ?: return Result.success(errorOutputData)
+
+        val sessionComponent = getSessionComponent(params.sessionId)
+                ?: return Result.success(errorOutputData).also {
+                    // TODO, can this happen? should I update local echo?
+                    Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
+                }
+        sessionComponent.inject(this)
+
+        val localId = LocalEcho.createLocalEchoId()
+        val eventType: String = EventType.SEND_SECRET
+
+        val toDeviceContent = SecretSendEventContent(
+                requestId = params.request.requestId ?: "",
+                secretValue = params.secretValue
+        )
+
+        val requestingUserId = params.request.userId ?: ""
+        val requestingDeviceId = params.request.deviceId ?: ""
+        val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId)
+                ?: return Result.success(errorOutputData).also {
+                    cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
+                    Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}")
+                }
+
+        val sendToDeviceMap = MXUsersDevicesMap<Any>()
+
+        val devicesByUser = mapOf(requestingUserId to listOf(deviceInfo))
+        val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+        val olmSessionResult = usersDeviceMap.getObject(requestingUserId, requestingDeviceId)
+        if (olmSessionResult?.sessionId == null) {
+            // no session with this device, probably because there
+            // were no one-time keys.
+            return Result.success(errorOutputData).also {
+                cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
+                Timber.e("no session with this device, probably because there were no one-time keys.")
+            }
+        }
+
+        val payloadJson = mapOf(
+                "type" to EventType.SEND_SECRET,
+                "content" to toDeviceContent.toContent()
+        )
+
+        try {
+            val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
+            sendToDeviceMap.setObject(requestingUserId, requestingDeviceId, encodedPayload)
+        } catch (failure: Throwable) {
+            Timber.e("## Fail to encrypt gossip + ${failure.localizedMessage}")
+        }
+
+        cryptoStore.saveGossipingEvent(Event(
+                type = eventType,
+                content = toDeviceContent.toContent(),
+                senderId = credentials.userId
+        ).also {
+            it.ageLocalTs = System.currentTimeMillis()
+        })
+
+        try {
+            sendToDeviceTask.execute(
+                    SendToDeviceTask.Params(
+                            eventType = EventType.ENCRYPTED,
+                            contentMap = sendToDeviceMap,
+                            transactionId = localId
+                    )
+            )
+            cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.ACCEPTED)
+            return Result.success()
+        } catch (exception: Throwable) {
+            return if (exception.shouldBeRetried()) {
+                Result.retry()
+            } else {
+                cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
+                Result.success(errorOutputData)
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt
deleted file mode 100644
index f4457f3a7f..0000000000
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.matrix.android.internal.crypto
-
-import im.vector.matrix.android.api.session.events.model.Event
-import im.vector.matrix.android.api.session.events.model.EventType
-import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
-import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryptionFactory
-import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
-import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
-import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
-import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
-import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import timber.log.Timber
-import javax.inject.Inject
-
-internal class ShareSecretCryptoProvider @Inject constructor(
-        val messageEncrypter: MessageEncrypter,
-        val sendToDeviceTask: SendToDeviceTask,
-        val deviceListManager: DeviceListManager,
-        private val olmDecryptionFactory: MXOlmDecryptionFactory,
-        val cryptoCoroutineScope: CoroutineScope,
-        val cryptoStore: IMXCryptoStore,
-        val coroutineDispatchers: MatrixCoroutineDispatchers
-) {
-    fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue: String) {
-        val userId = request.userId ?: return
-        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-            runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
-                    .mapCatching {
-                        val deviceId = request.deviceId
-                        val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "") ?: throw RuntimeException()
-
-                        Timber.i("## shareSecretWithDevice() : sharing secret ${request.secretName} with device $userId:$deviceId")
-
-                        val payloadJson = mutableMapOf<String, Any>("type" to EventType.SEND_SECRET)
-                        payloadJson["content"] = SecretSendEventContent(
-                                requestId = request.requestId ?: "",
-                                secretValue = secretValue
-                        )
-
-                        val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
-                        val sendToDeviceMap = MXUsersDevicesMap<Any>()
-                        sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
-                        Timber.i("## shareSecretWithDevice() : sending to $userId:$deviceId")
-                        val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
-                        sendToDeviceTask.execute(sendToDeviceParams)
-                    }
-        }
-    }
-
-    fun decryptEvent(event: Event): MXEventDecryptionResult {
-        return runBlocking(coroutineDispatchers.crypto) {
-            olmDecryptionFactory.create().decryptEvent(event, ShareSecretCryptoProvider::class.java.name)
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index d5b3b3b034..1d7a2765fa 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -149,7 +149,7 @@ internal class MXMegolmDecryption(private val userId: String,
         val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
         val senderDevice = encryptedEventContent?.deviceId ?: return
 
-        val recipients = if (event.senderId != userId) {
+        val recipients = if (event.senderId == userId) {
             mapOf(
                     userId to listOf("*")
             )
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt
index 74a4c6bee8..389fa1ea50 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt
@@ -25,7 +25,6 @@ import im.vector.matrix.android.api.util.Optional
 import im.vector.matrix.android.internal.crypto.DeviceListManager
 import im.vector.matrix.android.internal.crypto.MXOlmDevice
 import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
-import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
 import im.vector.matrix.android.internal.crypto.model.KeyUsage
 import im.vector.matrix.android.internal.crypto.model.rest.UploadSignatureQueryBuilder
@@ -62,7 +61,6 @@ internal class DefaultCrossSigningService @Inject constructor(
         private val taskExecutor: TaskExecutor,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val cryptoCoroutineScope: CoroutineScope,
-        private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
         private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
 
     private var olmUtility: OlmUtility? = null
@@ -599,6 +597,7 @@ internal class DefaultCrossSigningService @Inject constructor(
 
     override fun canCrossSign(): Boolean {
         return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null
+                && cryptoStore.getCrossSigningPrivateKeys()?.user != null
     }
 
     override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
@@ -770,7 +769,12 @@ internal class DefaultCrossSigningService @Inject constructor(
                     Timber.v("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
                     setUserKeysAsTrusted(otherUserId, it.isVerified())
                 }
+            }
+        }
 
+        // now check device trust
+        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
+            userIds.forEach { otherUserId ->
                 // TODO if my keys have changes, i should recheck all devices of all users?
                 val devices = cryptoStore.getUserDeviceList(otherUserId)
                 devices?.forEach { device ->
@@ -791,24 +795,22 @@ internal class DefaultCrossSigningService @Inject constructor(
     }
 
     private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-            val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
-            cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
-            // If it's me, recheck trust of all users and devices?
-            val users = ArrayList<String>()
-            if (otherUserId == userId && currentTrust != trusted) {
+        val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
+        cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
+        // If it's me, recheck trust of all users and devices?
+        val users = ArrayList<String>()
+        if (otherUserId == userId && currentTrust != trusted) {
 //                reRequestAllPendingRoomKeyRequest()
-                cryptoStore.updateUsersTrust {
-                    users.add(it)
-                    checkUserTrust(it).isVerified()
-                }
+            cryptoStore.updateUsersTrust {
+                users.add(it)
+                checkUserTrust(it).isVerified()
+            }
 
-                users.forEach {
-                    cryptoStore.getUserDeviceList(it)?.forEach { device ->
-                        val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
-                        Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
-                        cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
-                    }
+            users.forEach {
+                cryptoStore.getUserDeviceList(it)?.forEach { device ->
+                    val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
+                    Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
+                    cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
                 }
             }
         }
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 1bd55dd35d..42f72a0a33 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
@@ -272,7 +272,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         val ivParameterSpec = IvParameterSpec(iv)
         cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
         // secret are not that big, just do Final
-        val cipherBytes = cipher.doFinal(clearDataBase64.fromBase64())
+        val cipherBytes = cipher.doFinal(clearDataBase64.toByteArray())
         require(cipherBytes.isNotEmpty())
 
         val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
@@ -303,6 +303,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
 
         val cipherRawBytes = cipherContent.ciphertext?.fromBase64() ?: throw SharedSecretStorageError.BadCipherText
 
+        // Check Signature
+        val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
+        val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) }
+        val digest = mac.doFinal(cipherRawBytes)
+
+        if (!cipherContent.mac?.fromBase64()?.contentEquals(digest).orFalse()) {
+            throw SharedSecretStorageError.BadMac
+        }
+
         val cipher = Cipher.getInstance("AES/CTR/NoPadding")
 
         val secretKeySpec = SecretKeySpec(aesKey, "AES")
@@ -313,17 +322,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
 
         require(decryptedSecret.isNotEmpty())
 
-        // Check Signature
-        val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
-        val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) }
-        val digest = mac.doFinal(cipherRawBytes)
-
-        if (!cipherContent.mac?.fromBase64()?.contentEquals(digest).orFalse()) {
-            throw SharedSecretStorageError.BadMac
-        } else {
-            // we are good
-            return decryptedSecret.toBase64NoPadding()
-        }
+        return String(decryptedSecret, Charsets.UTF_8)
     }
 
     override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt
index 5594cbdf17..726d56a2f7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt
@@ -49,7 +49,9 @@ internal interface IMXCryptoStore {
     /**
      * @return the olm account
      */
-    fun getAccount(): OlmAccount?
+    fun getOlmAccount(): OlmAccount
+
+    fun getOrCreateOlmAccount(): OlmAccount
 
     /**
      * Retrieve the known inbound group sessions.
@@ -159,7 +161,7 @@ internal interface IMXCryptoStore {
      *
      * @param account the account to save
      */
-    fun storeAccount(account: OlmAccount)
+    fun saveOlmAccount()
 
     /**
      * Store a device for a user.
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt
index 20cc327b3d..7f136df54d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt
@@ -122,27 +122,7 @@ internal class RealmCryptoStore @Inject constructor(
             .setRealmConfiguration(realmConfiguration)
             .build()
 
-    /* ==========================================================================================
-     * Other data
-     * ========================================================================================== */
-
-    override fun hasData(): Boolean {
-        return doWithRealm(realmConfiguration) {
-            !it.isEmpty
-                    // Check if there is a MetaData object
-                    && it.where<CryptoMetadataEntity>().count() > 0
-        }
-    }
-
-    override fun deleteStore() {
-        doRealmTransaction(realmConfiguration) {
-            it.deleteAll()
-        }
-    }
-
-    override fun open() {
-        realmLocker = Realm.getInstance(realmConfiguration)
-
+    init {
         // Ensure CryptoMetadataEntity is inserted in DB
         doRealmTransaction(realmConfiguration) { realm ->
             var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
@@ -173,6 +153,27 @@ internal class RealmCryptoStore @Inject constructor(
             }
         }
     }
+    /* ==========================================================================================
+     * Other data
+     * ========================================================================================== */
+
+    override fun hasData(): Boolean {
+        return doWithRealm(realmConfiguration) {
+            !it.isEmpty
+                    // Check if there is a MetaData object
+                    && it.where<CryptoMetadataEntity>().count() > 0
+        }
+    }
+
+    override fun deleteStore() {
+        doRealmTransaction(realmConfiguration) {
+            it.deleteAll()
+        }
+    }
+
+    override fun open() {
+        realmLocker = Realm.getInstance(realmConfiguration)
+    }
 
     override fun close() {
         olmSessionsToRelease.forEach {
@@ -203,20 +204,31 @@ internal class RealmCryptoStore @Inject constructor(
         }?.deviceId ?: ""
     }
 
-    override fun storeAccount(account: OlmAccount) {
-        olmAccount = account
-
+    override fun saveOlmAccount() {
         doRealmTransaction(realmConfiguration) {
-            it.where<CryptoMetadataEntity>().findFirst()?.putOlmAccount(account)
+            it.where<CryptoMetadataEntity>().findFirst()?.putOlmAccount(olmAccount)
         }
     }
 
-    override fun getAccount(): OlmAccount? {
-        if (olmAccount == null) {
-            olmAccount = doRealmQueryAndCopy(realmConfiguration) { it.where<CryptoMetadataEntity>().findFirst() }?.getOlmAccount()
-        }
+    override fun getOlmAccount(): OlmAccount {
+        return olmAccount!!
+    }
 
-        return olmAccount
+    override fun getOrCreateOlmAccount(): OlmAccount {
+        doRealmTransaction(realmConfiguration) {
+           val metaData = it.where<CryptoMetadataEntity>().findFirst()
+            val existing =  metaData!!.getOlmAccount()
+            if (existing == null) {
+                Timber.d("## Crypto Creating olm account")
+                val created = OlmAccount()
+                metaData.putOlmAccount(created)
+                olmAccount = created
+            } else {
+                Timber.d("## Crypto Access existing account")
+                olmAccount = existing
+            }
+        }
+        return olmAccount!!
     }
 
     override fun storeUserDevice(userId: String?, deviceInfo: CryptoDeviceInfo?) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt
index af097f4431..6574132c7f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt
@@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.network.executeRequest
 import im.vector.matrix.android.internal.task.Task
 import im.vector.matrix.android.internal.util.convertToUTF8
 import org.greenrobot.eventbus.EventBus
+import timber.log.Timber
 import javax.inject.Inject
 
 internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadResponse> {
@@ -50,6 +51,8 @@ internal class DefaultUploadKeysTask @Inject constructor(
                 oneTimeKeys = params.oneTimeKeys
         )
 
+        Timber.i("## Uploading device keys -> $body")
+
         return executeRequest(eventBus) {
             apiCall = if (encodedDeviceId.isBlank()) {
                 cryptoApi.uploadKeys(body)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt
index 989ddc9804..e3a765f95c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt
@@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerif
 import im.vector.matrix.android.api.session.crypto.verification.SasMode
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
 import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
 import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@@ -35,6 +36,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
         private val cryptoStore: IMXCryptoStore,
         crossSigningService: CrossSigningService,
         outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        incomingGossipingRequestManager: IncomingGossipingRequestManager,
         deviceFingerprint: String,
         transactionId: String,
         otherUserID: String,
@@ -46,6 +48,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
         cryptoStore,
         crossSigningService,
         outgoingGossipingRequestManager,
+        incomingGossipingRequestManager,
         deviceFingerprint,
         transactionId,
         otherUserID,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
index 6c7e8f29d3..7fd97d0231 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
@@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode
 import im.vector.matrix.android.api.session.crypto.verification.OutgoingSasVerificationTransaction
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
 import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
 import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@@ -32,6 +33,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
         cryptoStore: IMXCryptoStore,
         crossSigningService: CrossSigningService,
         outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        incomingGossipingRequestManager: IncomingGossipingRequestManager,
         deviceFingerprint: String,
         transactionId: String,
         otherUserId: String,
@@ -43,6 +45,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
         cryptoStore,
         crossSigningService,
         outgoingGossipingRequestManager,
+        incomingGossipingRequestManager,
         deviceFingerprint,
         transactionId,
         otherUserId,
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 400b2c520e..dc68fa6b76 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
@@ -50,6 +50,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati
 import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent
 import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone
 import im.vector.matrix.android.internal.crypto.DeviceListManager
+import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
 import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
@@ -90,6 +91,7 @@ internal class DefaultVerificationService @Inject constructor(
         @DeviceId private val deviceId: String?,
         private val cryptoStore: IMXCryptoStore,
         private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
         private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
         private val deviceListManager: DeviceListManager,
         private val setDeviceVerificationAction: SetDeviceVerificationAction,
@@ -454,7 +456,7 @@ internal class DefaultVerificationService @Inject constructor(
     private suspend fun handleStart(otherUserId: String?,
                                     startReq: ValidVerificationInfoStart,
                                     txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? {
-        Timber.d("## SAS onStartRequestReceived ${startReq.transactionId}")
+        Timber.d("## SAS onStartRequestReceived $startReq")
         if (otherUserId?.let { checkKeysAreDownloaded(it, startReq.fromDevice) } != null) {
             val tid = startReq.transactionId
             var existing = getExistingTransaction(otherUserId, tid)
@@ -466,15 +468,17 @@ internal class DefaultVerificationService @Inject constructor(
             // smallest is used, and the other m.key.verification.start event is ignored.
             // In the case of a single user verifying two of their devices, the device ID is
             // compared instead .
-            if (existing != null && !existing.isIncoming) {
+            if (existing is DefaultOutgoingSASDefaultVerificationTransaction) {
                 val readyRequest = getExistingVerificationRequest(otherUserId, tid)
                 if (readyRequest?.isReady == true) {
                     if (isOtherPrioritary(otherUserId, existing.otherDeviceId ?: "")) {
+                        Timber.d("## SAS concurrent start isOtherPrioritary, clear")
                         // The other is prioritary!
                         // I should replace my outgoing with an incoming
                         removeTransaction(otherUserId, tid)
                         existing = null
                     } else {
+                        Timber.d("## SAS concurrent start i am prioritary, ignore")
                         // i am prioritary, ignore this start event!
                         return null
                     }
@@ -530,6 +534,7 @@ internal class DefaultVerificationService @Inject constructor(
                             cryptoStore,
                             crossSigningService,
                             outgoingGossipingRequestManager,
+                            incomingGossipingRequestManager,
                             myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                             startReq.transactionId,
                             otherUserId,
@@ -544,7 +549,7 @@ internal class DefaultVerificationService @Inject constructor(
                         existing.onStartReceived(startReq)
                         return null
                     } else {
-                        Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionId}")
+                        Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionId} / $existing")
                         return CancelCode.UnexpectedMessage
                     }
                 }
@@ -754,6 +759,7 @@ internal class DefaultVerificationService @Inject constructor(
 
     private suspend fun onReadyReceived(event: Event) {
         val readyReq = event.getClearContent().toModel<KeyVerificationReady>()?.asValidObject()
+        Timber.v("## SAS onReadyReceived $readyReq")
 
         if (readyReq == null || event.senderId == null) {
             // ignore
@@ -834,17 +840,18 @@ internal class DefaultVerificationService @Inject constructor(
         if (readyReq.methods.contains(VERIFICATION_METHOD_RECIPROCATE)) {
             // Create the pending transaction
             val tx = DefaultQrCodeVerificationTransaction(
-                    setDeviceVerificationAction,
-                    readyReq.transactionId,
-                    senderId,
-                    readyReq.fromDevice,
-                    crossSigningService,
-                    outgoingGossipingRequestManager,
-                    cryptoStore,
-                    qrCodeData,
-                    userId,
-                    deviceId ?: "",
-                    false)
+                    setDeviceVerificationAction = setDeviceVerificationAction,
+                    transactionId = readyReq.transactionId,
+                    otherUserId = senderId,
+                    otherDeviceId = readyReq.fromDevice,
+                    crossSigningService = crossSigningService,
+                    outgoingGossipingRequestManager = outgoingGossipingRequestManager,
+                    incomingGossipingRequestManager = incomingGossipingRequestManager,
+                    cryptoStore = cryptoStore,
+                    qrCodeData = qrCodeData,
+                    userId = userId,
+                    deviceId = deviceId ?: "",
+                    isIncoming = false)
 
             tx.transport = transportCreator.invoke(tx)
 
@@ -1001,13 +1008,11 @@ internal class DefaultVerificationService @Inject constructor(
     }
 
     private fun addTransaction(tx: DefaultVerificationTransaction) {
-        tx.otherUserId.let { otherUserId ->
-            synchronized(txMap) {
-                val txInnerMap = txMap.getOrPut(otherUserId) { HashMap() }
-                txInnerMap[tx.transactionId] = tx
-                dispatchTxAdded(tx)
-                tx.addListener(this)
-            }
+        synchronized(txMap) {
+            val txInnerMap = txMap.getOrPut(tx.otherUserId) { HashMap() }
+            txInnerMap[tx.transactionId] = tx
+            dispatchTxAdded(tx)
+            tx.addListener(this)
         }
     }
 
@@ -1022,6 +1027,7 @@ internal class DefaultVerificationService @Inject constructor(
                     cryptoStore,
                     crossSigningService,
                     outgoingGossipingRequestManager,
+                    incomingGossipingRequestManager,
                     myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                     txID,
                     otherUserId,
@@ -1096,6 +1102,18 @@ internal class DefaultVerificationService @Inject constructor(
         return verificationRequest
     }
 
+    override fun cancelVerificationRequest(request: PendingVerificationRequest) {
+        if (request.roomId != null) {
+            val transport = verificationTransportRoomMessageFactory.createTransport(request.roomId, null)
+            transport.cancelTransaction(request.transactionId ?: "", request.otherUserId, null, CancelCode.User)
+        } else {
+            val transport = verificationTransportToDeviceFactory.createTransport(null)
+            request.targetDevices?.forEach { deviceId ->
+                transport.cancelTransaction(request.transactionId ?: "", request.otherUserId, deviceId, CancelCode.User)
+            }
+        }
+    }
+
     override fun requestKeyVerification(methods: List<VerificationMethod>, otherUserId: String, otherDevices: List<String>?): PendingVerificationRequest {
         // TODO refactor this with the DM one
         Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
@@ -1198,6 +1216,7 @@ internal class DefaultVerificationService @Inject constructor(
                     cryptoStore,
                     crossSigningService,
                     outgoingGossipingRequestManager,
+                    incomingGossipingRequestManager,
                     myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                     transactionId,
                     otherUserId,
@@ -1329,17 +1348,18 @@ internal class DefaultVerificationService @Inject constructor(
             if (VERIFICATION_METHOD_RECIPROCATE in result) {
                 // Create the pending transaction
                 val tx = DefaultQrCodeVerificationTransaction(
-                        setDeviceVerificationAction,
-                        transactionId,
-                        otherUserId,
-                        otherDeviceId,
-                        crossSigningService,
-                        outgoingGossipingRequestManager,
-                        cryptoStore,
-                        qrCodeData,
-                        userId,
-                        deviceId ?: "",
-                        false)
+                        setDeviceVerificationAction = setDeviceVerificationAction,
+                        transactionId = transactionId,
+                        otherUserId = otherUserId,
+                        otherDeviceId = otherDeviceId,
+                        crossSigningService = crossSigningService,
+                        outgoingGossipingRequestManager = outgoingGossipingRequestManager,
+                        incomingGossipingRequestManager = incomingGossipingRequestManager,
+                        cryptoStore = cryptoStore,
+                        qrCodeData = qrCodeData,
+                        userId = userId,
+                        deviceId = deviceId ?: "",
+                        isIncoming = false)
 
                 tx.transport = transportCreator.invoke(tx)
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt
index cb83fdfe8b..973b9944cb 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt
@@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY
 import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
+import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
 import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
@@ -33,6 +34,7 @@ internal abstract class DefaultVerificationTransaction(
         private val setDeviceVerificationAction: SetDeviceVerificationAction,
         private val crossSigningService: CrossSigningService,
         private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
         private val userId: String,
         override val transactionId: String,
         override val otherUserId: String,
@@ -86,6 +88,8 @@ internal abstract class DefaultVerificationTransaction(
         }
 
         if (otherUserId == userId) {
+            incomingGossipingRequestManager.onVerificationCompleteForDevice(otherDeviceId!!)
+
             // If me it's reasonable to sign and upload the device signature
             // Notice that i might not have the private keys, so may not be able to do it
             crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
@@ -96,7 +100,7 @@ internal abstract class DefaultVerificationTransaction(
         }
 
         transport.done(transactionId) {
-            if (otherUserId == userId) {
+            if (otherUserId == userId && !crossSigningService.canCrossSign()) {
                 outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*")))
                 outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*")))
             }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt
index cfb3d7e38e..a878ad06eb 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt
@@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.verification.SasMode
 import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
 import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
 import im.vector.matrix.android.internal.crypto.model.MXKey
@@ -44,6 +45,7 @@ internal abstract class SASDefaultVerificationTransaction(
         private val cryptoStore: IMXCryptoStore,
         crossSigningService: CrossSigningService,
         outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        incomingGossipingRequestManager: IncomingGossipingRequestManager,
         private val deviceFingerprint: String,
         transactionId: String,
         otherUserId: String,
@@ -53,6 +55,7 @@ internal abstract class SASDefaultVerificationTransaction(
         setDeviceVerificationAction,
         crossSigningService,
         outgoingGossipingRequestManager,
+        incomingGossipingRequestManager,
         userId,
         transactionId,
         otherUserId,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt
index 8fc12f000a..41d8ce7f44 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt
@@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode
 import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
 import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager
 import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
 import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
@@ -38,6 +39,7 @@ internal class DefaultQrCodeVerificationTransaction(
         override var otherDeviceId: String?,
         private val crossSigningService: CrossSigningService,
         outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        incomingGossipingRequestManager: IncomingGossipingRequestManager,
         private val cryptoStore: IMXCryptoStore,
         // Not null only if other user is able to scan QR code
         private val qrCodeData: QrCodeData?,
@@ -48,6 +50,7 @@ internal class DefaultQrCodeVerificationTransaction(
         setDeviceVerificationAction,
         crossSigningService,
         outgoingGossipingRequestManager,
+        incomingGossipingRequestManager,
         userId,
         transactionId,
         otherUserId,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt
index 2185d3b278..22ebb4273a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt
@@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.Session
 import im.vector.matrix.android.internal.crypto.CancelGossipRequestWorker
 import im.vector.matrix.android.internal.crypto.CryptoModule
 import im.vector.matrix.android.internal.crypto.SendGossipRequestWorker
+import im.vector.matrix.android.internal.crypto.SendGossipWorker
 import im.vector.matrix.android.internal.crypto.verification.SendVerificationMessageWorker
 import im.vector.matrix.android.internal.di.MatrixComponent
 import im.vector.matrix.android.internal.di.SessionAssistedInjectModule
@@ -109,8 +110,11 @@ internal interface SessionComponent {
     fun inject(worker: SendVerificationMessageWorker)
 
     fun inject(worker: SendGossipRequestWorker)
+
     fun inject(worker: CancelGossipRequestWorker)
 
+    fun inject(worker: SendGossipWorker)
+
     @Component.Factory
     interface Factory {
         fun create(
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 4071c9224f..4fa0cb5013 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
@@ -53,9 +53,9 @@ internal class FileUploader @Inject constructor(@Authenticated
 
     suspend fun uploadByteArray(byteArray: ByteArray,
                                 filename: String?,
-                                mimeType: String,
+                                mimeType: String?,
                                 progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
-        val uploadBody = byteArray.toRequestBody(mimeType.toMediaTypeOrNull())
+        val uploadBody = byteArray.toRequestBody(mimeType?.toMediaTypeOrNull())
         return upload(uploadBody, filename, progressListener)
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt
index 083cac0278..eae2bf8f6d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt
@@ -16,12 +16,12 @@
 
 package im.vector.matrix.android.internal.session.content
 
+import android.content.Context
 import android.graphics.Bitmap
-import android.media.ThumbnailUtils
-import android.provider.MediaStore
+import android.media.MediaMetadataRetriever
 import im.vector.matrix.android.api.session.content.ContentAttachmentData
+import timber.log.Timber
 import java.io.ByteArrayOutputStream
-import java.io.File
 
 internal object ThumbnailExtractor {
 
@@ -33,34 +33,40 @@ internal object ThumbnailExtractor {
             val mimeType: String
     )
 
-    fun extractThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
-        val file = File(attachment.path)
-        if (!file.exists() || !file.isFile) {
-            return null
-        }
+    fun extractThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? {
         return if (attachment.type == ContentAttachmentData.Type.VIDEO) {
-            extractVideoThumbnail(attachment)
+            extractVideoThumbnail(context, attachment)
         } else {
             null
         }
     }
 
-    private fun extractVideoThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
-        val thumbnail = ThumbnailUtils.createVideoThumbnail(attachment.path, MediaStore.Video.Thumbnails.MINI_KIND) ?: return null
-        val outputStream = ByteArrayOutputStream()
-        thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
-        val thumbnailWidth = thumbnail.width
-        val thumbnailHeight = thumbnail.height
-        val thumbnailSize = outputStream.size()
-        val thumbnailData = ThumbnailData(
-                width = thumbnailWidth,
-                height = thumbnailHeight,
-                size = thumbnailSize.toLong(),
-                bytes = outputStream.toByteArray(),
-                mimeType = "image/jpeg"
-        )
-        thumbnail.recycle()
-        outputStream.reset()
+    private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? {
+        var thumbnailData: ThumbnailData? = null
+        val mediaMetadataRetriever = MediaMetadataRetriever()
+        try {
+            mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
+            val thumbnail = mediaMetadataRetriever.frameAtTime
+
+            val outputStream = ByteArrayOutputStream()
+            thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+            val thumbnailWidth = thumbnail.width
+            val thumbnailHeight = thumbnail.height
+            val thumbnailSize = outputStream.size()
+            thumbnailData = ThumbnailData(
+                    width = thumbnailWidth,
+                    height = thumbnailHeight,
+                    size = thumbnailSize.toLong(),
+                    bytes = outputStream.toByteArray(),
+                    mimeType = "image/jpeg"
+            )
+            thumbnail.recycle()
+            outputStream.reset()
+        } catch (e: Exception) {
+            Timber.e(e, "Cannot extract video thumbnail")
+        } finally {
+            mediaMetadataRetriever.release()
+        }
         return thumbnailData
     }
 }
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 1c88f87804..1b736d349f 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
@@ -17,12 +17,9 @@
 package im.vector.matrix.android.internal.session.content
 
 import android.content.Context
-import android.graphics.BitmapFactory
 import androidx.work.CoroutineWorker
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
-import id.zelory.compressor.Compressor
-import id.zelory.compressor.constraint.default
 import im.vector.matrix.android.api.session.content.ContentAttachmentData
 import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.events.model.toContent
@@ -41,8 +38,6 @@ import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 import im.vector.matrix.android.internal.worker.getSessionComponent
 import timber.log.Timber
 import java.io.ByteArrayInputStream
-import java.io.File
-import java.io.FileInputStream
 import javax.inject.Inject
 
 private data class NewImageAttributes(
@@ -94,8 +89,90 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
 
         var newImageAttributes: NewImageAttributes? = null
 
-        val attachmentFile = try {
-            File(attachment.path)
+        try {
+            val inputStream = context.contentResolver.openInputStream(attachment.queryUri)
+                    ?: return Result.success(
+                            WorkerParamsFactory.toData(
+                                    params.copy(
+                                            lastFailureMessage = "Cannot openInputStream for file: " + attachment.queryUri.toString()
+                                    )
+                            )
+                    )
+
+            inputStream.use {
+                var uploadedThumbnailUrl: String? = null
+                var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
+
+                ThumbnailExtractor.extractThumbnail(context, params.attachment)?.let { thumbnailData ->
+                    val thumbnailProgressListener = object : ProgressRequestBody.Listener {
+                        override fun onProgress(current: Long, total: Long) {
+                            notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) }
+                        }
+                    }
+
+                    try {
+                        val contentUploadResponse = if (params.isEncrypted) {
+                            Timber.v("Encrypt thumbnail")
+                            notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
+                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
+                            uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
+                            fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
+                                    "thumb_${attachment.name}",
+                                    "application/octet-stream",
+                                    thumbnailProgressListener)
+                        } else {
+                            fileUploader.uploadByteArray(thumbnailData.bytes,
+                                    "thumb_${attachment.name}",
+                                    thumbnailData.mimeType,
+                                    thumbnailProgressListener)
+                        }
+
+                        uploadedThumbnailUrl = contentUploadResponse.contentUri
+                    } catch (t: Throwable) {
+                        Timber.e(t, "Thumbnail update failed")
+                    }
+                }
+
+                val progressListener = object : ProgressRequestBody.Listener {
+                    override fun onProgress(current: Long, total: Long) {
+                        notifyTracker(params) {
+                            if (isStopped) {
+                                contentUploadStateTracker.setFailure(it, Throwable("Cancelled"))
+                            } else {
+                                contentUploadStateTracker.setProgress(it, current, total)
+                            }
+                        }
+                    }
+                }
+
+                var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
+
+                return try {
+                    val contentUploadResponse = if (params.isEncrypted) {
+                        Timber.v("Encrypt file")
+                        notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
+
+                        val encryptionResult = MXEncryptedAttachments.encryptAttachment(inputStream, attachment.getSafeMimeType())
+                        uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
+
+                        fileUploader
+                                .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
+                    } else {
+                        fileUploader
+                                .uploadByteArray(inputStream.readBytes(), attachment.name, attachment.getSafeMimeType(), progressListener)
+                    }
+
+                    handleSuccess(params,
+                            contentUploadResponse.contentUri,
+                            uploadedFileEncryptedFileInfo,
+                            uploadedThumbnailUrl,
+                            uploadedThumbnailEncryptedFileInfo,
+                            newImageAttributes)
+                } catch (t: Throwable) {
+                    Timber.e(t)
+                    handleFailure(params, t)
+                }
+            }
         } catch (e: Exception) {
             Timber.e(e)
             notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
@@ -106,109 +183,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                             )
                     )
             )
-        }
-                .let { originalFile ->
-                    if (attachment.type == ContentAttachmentData.Type.IMAGE) {
-                        if (params.compressBeforeSending) {
-                            Compressor.compress(context, originalFile) {
-                                default(
-                                        width = MAX_IMAGE_SIZE,
-                                        height = MAX_IMAGE_SIZE
-                                )
-                            }.also { compressedFile ->
-                                // Update the params
-                                val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
-                                BitmapFactory.decodeFile(compressedFile.absolutePath, options)
-                                val fileSize = compressedFile.length().toInt()
-
-                                newImageAttributes = NewImageAttributes(
-                                        options.outWidth,
-                                        options.outHeight,
-                                        fileSize
-                                )
-                            }
-                        } else {
-                            // TODO Fix here the image rotation issue
-                            originalFile
-                        }
-                    } else {
-                        // Other type
-                        originalFile
-                    }
-                }
-
-        var uploadedThumbnailUrl: String? = null
-        var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
-
-        ThumbnailExtractor.extractThumbnail(params.attachment)?.let { thumbnailData ->
-            val thumbnailProgressListener = object : ProgressRequestBody.Listener {
-                override fun onProgress(current: Long, total: Long) {
-                    notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) }
-                }
-            }
-
-            try {
-                val contentUploadResponse = if (params.isEncrypted) {
-                    Timber.v("Encrypt thumbnail")
-                    notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
-                    val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
-                    uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
-                    fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
-                            "thumb_${attachment.name}",
-                            "application/octet-stream",
-                            thumbnailProgressListener)
-                } else {
-                    fileUploader.uploadByteArray(thumbnailData.bytes,
-                            "thumb_${attachment.name}",
-                            thumbnailData.mimeType,
-                            thumbnailProgressListener)
-                }
-
-                uploadedThumbnailUrl = contentUploadResponse.contentUri
-            } catch (t: Throwable) {
-                Timber.e(t)
-                return handleFailure(params, t)
-            }
-        }
-
-        val progressListener = object : ProgressRequestBody.Listener {
-            override fun onProgress(current: Long, total: Long) {
-                notifyTracker(params) {
-                    if (isStopped) {
-                        contentUploadStateTracker.setFailure(it, Throwable("Cancelled"))
-                    } else {
-                        contentUploadStateTracker.setProgress(it, current, total)
-                    }
-                }
-            }
-        }
-
-        var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
-
-        return try {
-            val contentUploadResponse = if (params.isEncrypted) {
-                Timber.v("Encrypt file")
-                notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
-
-                val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.getSafeMimeType())
-                uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
-
-                fileUploader
-                        .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
-            } else {
-                fileUploader
-                        .uploadFile(attachmentFile, attachment.name, attachment.getSafeMimeType(), progressListener)
-            }
-
-            handleSuccess(params,
-                    contentUploadResponse.contentUri,
-                    uploadedFileEncryptedFileInfo,
-                    uploadedThumbnailUrl,
-                    uploadedThumbnailEncryptedFileInfo,
-                    newImageAttributes)
-        } catch (t: Throwable) {
-            Timber.e(t)
-            handleFailure(params, t)
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
index f10c40ded5..a4a6eb6972 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
@@ -16,6 +16,7 @@
 
 package im.vector.matrix.android.internal.session.room.send
 
+import android.content.Context
 import android.graphics.Bitmap
 import android.media.MediaMetadataRetriever
 import androidx.exifinterface.media.ExifInterface
@@ -74,6 +75,7 @@ import javax.inject.Inject
  * The transactionId is used as loc
  */
 internal class LocalEchoEventFactory @Inject constructor(
+        private val context: Context,
         @UserId private val userId: String,
         private val stringProvider: StringProvider,
         private val textPillsUtils: TextPillsUtils,
@@ -266,14 +268,14 @@ internal class LocalEchoEventFactory @Inject constructor(
                         height = height?.toInt() ?: 0,
                         size = attachment.size.toInt()
                 ),
-                url = attachment.path
+                url = attachment.queryUri.toString()
         )
         return createEvent(roomId, content)
     }
 
     private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
         val mediaDataRetriever = MediaMetadataRetriever()
-        mediaDataRetriever.setDataSource(attachment.path)
+        mediaDataRetriever.setDataSource(context, attachment.queryUri)
 
         // Use frame to calculate height and width as we are sure to get the right ones
         val firstFrame: Bitmap? = mediaDataRetriever.frameAtTime
@@ -281,7 +283,7 @@ internal class LocalEchoEventFactory @Inject constructor(
         val width = firstFrame?.width ?: 0
         mediaDataRetriever.release()
 
-        val thumbnailInfo = ThumbnailExtractor.extractThumbnail(attachment)?.let {
+        val thumbnailInfo = ThumbnailExtractor.extractThumbnail(context, attachment)?.let {
             ThumbnailInfo(
                     width = it.width,
                     height = it.height,
@@ -299,10 +301,10 @@ internal class LocalEchoEventFactory @Inject constructor(
                         size = attachment.size,
                         duration = attachment.duration?.toInt() ?: 0,
                         // Glide will be able to use the local path and extract a thumbnail.
-                        thumbnailUrl = attachment.path,
+                        thumbnailUrl = attachment.queryUri.toString(),
                         thumbnailInfo = thumbnailInfo
                 ),
-                url = attachment.path
+                url = attachment.queryUri.toString()
         )
         return createEvent(roomId, content)
     }
@@ -315,7 +317,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                         mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
                         size = attachment.size
                 ),
-                url = attachment.path
+                url = attachment.queryUri.toString()
         )
         return createEvent(roomId, content)
     }
@@ -329,7 +331,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                                 ?: "application/octet-stream",
                         size = attachment.size
                 ),
-                url = attachment.path
+                url = attachment.queryUri.toString()
         )
         return createEvent(roomId, content)
     }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt
index 02c06b0e56..e129513d4b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -128,7 +128,7 @@ internal class TimelineEventDecryptor @Inject constructor(
                 }
             }
         } catch (t: Throwable) {
-            Timber.e(t, "Failed to decrypt event $eventId")
+            Timber.e("Failed to decrypt event $eventId, ${t.localizedMessage}")
         } finally {
             synchronized(existingRequests) {
                 existingRequests.remove(request)
diff --git a/multipicker/.gitignore b/multipicker/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/multipicker/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/multipicker/build.gradle b/multipicker/build.gradle
new file mode 100644
index 0000000000..8b08a9d3ef
--- /dev/null
+++ b/multipicker/build.gradle
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+    compileSdkVersion 29
+
+    defaultConfig {
+        minSdkVersion 19
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles 'consumer-rules.pro'
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'androidx.core:core-ktx:1.2.0'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+
+    implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
+
+    // Log
+    implementation 'com.jakewharton.timber:timber:4.7.1'
+}
diff --git a/multipicker/consumer-rules.pro b/multipicker/consumer-rules.pro
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/multipicker/proguard-rules.pro b/multipicker/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/multipicker/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/multipicker/src/main/AndroidManifest.xml b/multipicker/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..e1f12697e0
--- /dev/null
+++ b/multipicker/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="im.vector.riotx.multipicker">
+
+    <application>
+        <provider
+            android:name=".provider.MultiPickerFileProvider"
+            android:authorities="${applicationId}.multipicker.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/multipicker_provider_paths" />
+        </provider>
+    </application>
+
+</manifest>
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt
new file mode 100644
index 0000000000..05e4c337b6
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.media.MediaMetadataRetriever
+import android.provider.MediaStore
+import im.vector.riotx.multipicker.entity.MultiPickerAudioType
+
+/**
+ * Audio file picker implementation
+ */
+class AudioPicker(override val requestCode: Int) : Picker<MultiPickerAudioType>(requestCode) {
+
+    /**
+     * Call this function from onActivityResult(int, int, Intent).
+     * Returns selected audio files or empty list if request code is wrong
+     * or result code is not Activity.RESULT_OK
+     * or user did not select any files.
+     */
+    override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerAudioType> {
+        if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+            return emptyList()
+        }
+
+        val audioList = mutableListOf<MultiPickerAudioType>()
+
+        getSelectedUriList(data).forEach { selectedUri ->
+            val projection = arrayOf(
+                    MediaStore.Audio.Media.DISPLAY_NAME,
+                    MediaStore.Audio.Media.SIZE
+            )
+
+            context.contentResolver.query(
+                    selectedUri,
+                    projection,
+                    null,
+                    null,
+                    null
+            )?.use { cursor ->
+                val nameColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME)
+                val sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE)
+
+                if (cursor.moveToNext()) {
+                    val name = cursor.getString(nameColumn)
+                    val size = cursor.getLong(sizeColumn)
+                    var duration = 0L
+
+                    context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
+                        val mediaMetadataRetriever = MediaMetadataRetriever()
+                        mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
+                        duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
+                    }
+
+                    audioList.add(
+                            MultiPickerAudioType(
+                                    name,
+                                    size,
+                                    context.contentResolver.getType(selectedUri),
+                                    selectedUri,
+                                    duration
+                            )
+                    )
+                }
+            }
+        }
+        return audioList
+    }
+
+    override fun createIntent(): Intent {
+        return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+            addCategory(Intent.CATEGORY_OPENABLE)
+            putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+            type = "audio/*"
+        }
+    }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt
new file mode 100644
index 0000000000..240d809373
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.MediaStore
+import androidx.core.content.FileProvider
+import androidx.fragment.app.Fragment
+import im.vector.riotx.multipicker.entity.MultiPickerImageType
+import im.vector.riotx.multipicker.utils.ImageUtils
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * Implementation of taking a photo with Camera
+ */
+class CameraPicker(val requestCode: Int) {
+
+    /**
+     * Start camera by using an Activity
+     * @param activity Activity to handle onActivityResult().
+     * @return Uri of taken photo or null if the operation is cancelled.
+     */
+    fun startWithExpectingFile(activity: Activity): Uri? {
+        val photoUri = createPhotoUri(activity)
+        val intent = createIntent().apply {
+            putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
+        }
+        activity.startActivityForResult(intent, requestCode)
+        return photoUri
+    }
+
+    /**
+     * Start camera by using a Fragment
+     * @param fragment Fragment to handle onActivityResult().
+     * @return Uri of taken photo or null if the operation is cancelled.
+     */
+    fun startWithExpectingFile(fragment: Fragment): Uri? {
+        val photoUri = createPhotoUri(fragment.requireContext())
+        val intent = createIntent().apply {
+            putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
+        }
+        fragment.startActivityForResult(intent, requestCode)
+        return photoUri
+    }
+
+    /**
+     * Call this function from onActivityResult(int, int, Intent).
+     * @return Taken photo or null if request code is wrong
+     * or result code is not Activity.RESULT_OK
+     * or user cancelled the operation.
+     */
+    fun getTakenPhoto(context: Context, requestCode: Int, resultCode: Int, photoUri: Uri): MultiPickerImageType? {
+        if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) {
+            val projection = arrayOf(
+                    MediaStore.Images.Media.DISPLAY_NAME,
+                    MediaStore.Images.Media.SIZE
+            )
+
+            context.contentResolver.query(
+                    photoUri,
+                    projection,
+                    null,
+                    null,
+                    null
+            )?.use { cursor ->
+                val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
+                val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
+
+                if (cursor.moveToNext()) {
+                    val name = cursor.getString(nameColumn)
+                    val size = cursor.getLong(sizeColumn)
+
+                    val bitmap = ImageUtils.getBitmap(context, photoUri)
+                    val orientation = ImageUtils.getOrientation(context, photoUri)
+
+                    return MultiPickerImageType(
+                            name,
+                            size,
+                            context.contentResolver.getType(photoUri),
+                            photoUri,
+                            bitmap?.width ?: 0,
+                            bitmap?.height ?: 0,
+                            orientation
+                    )
+                }
+            }
+        }
+        return null
+    }
+
+    private fun createIntent(): Intent {
+        return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+    }
+
+    private fun createPhotoUri(context: Context): Uri {
+        val file = createImageFile(context)
+        val authority = context.packageName + ".multipicker.fileprovider"
+        return FileProvider.getUriForFile(context, authority, file)
+    }
+
+    private fun createImageFile(context: Context): File {
+        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
+        val storageDir: File = context.filesDir
+        return File.createTempFile(
+                "${timeStamp}_", /* prefix */
+                ".jpg", /* suffix */
+                storageDir /* directory */
+        )
+    }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt
new file mode 100644
index 0000000000..b0ae0e4cda
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.multipicker
+
+import android.app.Activity
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.provider.ContactsContract
+import im.vector.riotx.multipicker.entity.MultiPickerContactType
+
+/**
+ * Contact Picker implementation
+ */
+class ContactPicker(override val requestCode: Int) : Picker<MultiPickerContactType>(requestCode) {
+
+    /**
+     * Call this function from onActivityResult(int, int, Intent).
+     * Returns selected contact or empty list if request code is wrong
+     * or result code is not Activity.RESULT_OK
+     * or user did not select any files.
+     */
+    override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerContactType> {
+        if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+            return emptyList()
+        }
+
+        val contactList = mutableListOf<MultiPickerContactType>()
+
+        data?.data?.let { selectedUri ->
+            val projection = arrayOf(
+                    ContactsContract.Contacts.DISPLAY_NAME,
+                    ContactsContract.Contacts.PHOTO_URI,
+                    ContactsContract.Contacts._ID
+            )
+
+            context.contentResolver.query(
+                    selectedUri,
+                    projection,
+                    null,
+                    null,
+                    null
+            )?.use { cursor ->
+                if (cursor.moveToFirst()) {
+                    val idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID)
+                    val nameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
+                    val photoUriColumn = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI)
+
+                    val contactId = cursor.getInt(idColumn)
+                    var name = cursor.getString(nameColumn)
+                    var photoUri = cursor.getString(photoUriColumn)
+                    var phoneNumberList = mutableListOf<String>()
+                    var emailList = mutableListOf<String>()
+
+                    getRawContactId(context.contentResolver, contactId)?.let { rawContactId ->
+                        val selection = ContactsContract.Data.RAW_CONTACT_ID + " = ?"
+                        val selectionArgs = arrayOf(rawContactId.toString())
+
+                        context.contentResolver.query(
+                                ContactsContract.Data.CONTENT_URI,
+                                arrayOf(
+                                        ContactsContract.Data.MIMETYPE,
+                                        ContactsContract.Data.DATA1
+                                ),
+                                selection,
+                                selectionArgs,
+                                null
+                        )?.use { cursor ->
+                            while (cursor.moveToNext()) {
+                                val mimeType = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE))
+                                val contactData = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1))
+
+                                if (mimeType == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) {
+                                    name = contactData
+                                }
+                                if (mimeType == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) {
+                                    phoneNumberList.add(contactData)
+                                }
+                                if (mimeType == ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) {
+                                    emailList.add(contactData)
+                                }
+                            }
+                        }
+                    }
+                    contactList.add(
+                            MultiPickerContactType(
+                                    name,
+                                    photoUri,
+                                    phoneNumberList,
+                                    emailList
+                            )
+                    )
+                }
+            }
+        }
+
+        return contactList
+    }
+
+    private fun getRawContactId(contentResolver: ContentResolver, contactId: Int): Int? {
+        val projection = arrayOf(ContactsContract.RawContacts._ID)
+        val selection = ContactsContract.RawContacts.CONTACT_ID + " = ?"
+        val selectionArgs = arrayOf(contactId.toString() + "")
+        return contentResolver.query(
+                ContactsContract.RawContacts.CONTENT_URI,
+                projection,
+                selection,
+                selectionArgs,
+                null
+        )?.use { cursor ->
+            return if (cursor.moveToFirst()) cursor.getInt(cursor.getColumnIndex(ContactsContract.RawContacts._ID)) else null
+        }
+    }
+
+    override fun createIntent(): Intent {
+        return Intent(Intent.ACTION_PICK).apply {
+            putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+            type = ContactsContract.Contacts.CONTENT_TYPE
+        }
+    }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt
new file mode 100644
index 0000000000..e8c74fad19
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.provider.OpenableColumns
+import im.vector.riotx.multipicker.entity.MultiPickerFileType
+
+/**
+ * Implementation of selecting any type of files
+ */
+class FilePicker(override val requestCode: Int) : Picker<MultiPickerFileType>(requestCode) {
+
+    /**
+     * Call this function from onActivityResult(int, int, Intent).
+     * Returns selected files or empty list if request code is wrong
+     * or result code is not Activity.RESULT_OK
+     * or user did not select any files.
+     */
+    override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerFileType> {
+        if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+            return emptyList()
+        }
+
+        val fileList = mutableListOf<MultiPickerFileType>()
+
+        getSelectedUriList(data).forEach { selectedUri ->
+            context.contentResolver.query(selectedUri, null, null, null, null)
+                    ?.use { cursor ->
+                        val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+                        val sizeColumn = cursor.getColumnIndex(OpenableColumns.SIZE)
+                        if (cursor.moveToFirst()) {
+                            val name = cursor.getString(nameColumn)
+                            val size = cursor.getLong(sizeColumn)
+
+                            fileList.add(
+                                    MultiPickerFileType(
+                                            name,
+                                            size,
+                                            context.contentResolver.getType(selectedUri),
+                                            selectedUri
+                                    )
+                            )
+                        }
+                    }
+        }
+        return fileList
+    }
+
+    override fun createIntent(): Intent {
+        return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+            addCategory(Intent.CATEGORY_OPENABLE)
+            putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+            type = "*/*"
+        }
+    }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt
new file mode 100644
index 0000000000..d7bf383f03
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.provider.MediaStore
+import im.vector.riotx.multipicker.entity.MultiPickerImageType
+import im.vector.riotx.multipicker.utils.ImageUtils
+
+/**
+ * Image Picker implementation
+ */
+class ImagePicker(override val requestCode: Int) : Picker<MultiPickerImageType>(requestCode) {
+
+    /**
+     * Call this function from onActivityResult(int, int, Intent).
+     * Returns selected image files or empty list if request code is wrong
+     * or result code is not Activity.RESULT_OK
+     * or user did not select any files.
+     */
+    override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerImageType> {
+        if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+            return emptyList()
+        }
+
+        val imageList = mutableListOf<MultiPickerImageType>()
+
+        getSelectedUriList(data).forEach { selectedUri ->
+            val projection = arrayOf(
+                    MediaStore.Images.Media.DISPLAY_NAME,
+                    MediaStore.Images.Media.SIZE
+            )
+
+            context.contentResolver.query(
+                    selectedUri,
+                    projection,
+                    null,
+                    null,
+                    null
+            )?.use { cursor ->
+                val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
+                val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
+
+                if (cursor.moveToNext()) {
+                    val name = cursor.getString(nameColumn)
+                    val size = cursor.getLong(sizeColumn)
+
+                    val bitmap = ImageUtils.getBitmap(context, selectedUri)
+                    val orientation = ImageUtils.getOrientation(context, selectedUri)
+
+                    imageList.add(
+                            MultiPickerImageType(
+                                    name,
+                                    size,
+                                    context.contentResolver.getType(selectedUri),
+                                    selectedUri,
+                                    bitmap?.width ?: 0,
+                                    bitmap?.height ?: 0,
+                                    orientation
+                            )
+                    )
+                }
+            }
+        }
+        return imageList
+    }
+
+    override fun createIntent(): Intent {
+        return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+            addCategory(Intent.CATEGORY_OPENABLE)
+            putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+            type = "image/*"
+        }
+    }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt
new file mode 100644
index 0000000000..24769e11c3
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.multipicker
+
+class MultiPicker<T> {
+
+    companion object Type {
+        val IMAGE by lazy { MultiPicker<ImagePicker>() }
+        val FILE by lazy { MultiPicker<FilePicker>() }
+        val VIDEO by lazy { MultiPicker<VideoPicker>() }
+        val AUDIO by lazy { MultiPicker<AudioPicker>() }
+        val CONTACT by lazy { MultiPicker<ContactPicker>() }
+        val CAMERA by lazy { MultiPicker<CameraPicker>() }
+
+        const val REQUEST_CODE_PICK_IMAGE = 5000
+        const val REQUEST_CODE_PICK_VIDEO = 5001
+        const val REQUEST_CODE_PICK_FILE = 5002
+        const val REQUEST_CODE_PICK_AUDIO = 5003
+        const val REQUEST_CODE_PICK_CONTACT = 5004
+        const val REQUEST_CODE_TAKE_PHOTO = 5005
+
+        @Suppress("UNCHECKED_CAST")
+        fun <T> get(type: MultiPicker<T>): T {
+            return when (type) {
+                IMAGE -> ImagePicker(REQUEST_CODE_PICK_IMAGE) as T
+                VIDEO -> VideoPicker(REQUEST_CODE_PICK_VIDEO) as T
+                FILE  -> FilePicker(REQUEST_CODE_PICK_FILE) as T
+                AUDIO  -> AudioPicker(REQUEST_CODE_PICK_AUDIO) as T
+                CONTACT  -> ContactPicker(REQUEST_CODE_PICK_CONTACT) as T
+                CAMERA  -> CameraPicker(REQUEST_CODE_TAKE_PHOTO) as T
+                else  -> throw IllegalArgumentException("Unsupported type $type")
+            }
+        }
+    }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt
new file mode 100644
index 0000000000..43ac5d5fdd
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.net.Uri
+import androidx.fragment.app.Fragment
+
+/**
+ * Abstract class to provide all types of Pickers
+ */
+abstract class Picker<T>(open val requestCode: Int) {
+
+    protected var single = false
+
+    /**
+     * Call this function from onActivityResult(int, int, Intent).
+     * @return selected files or empty list if request code is wrong
+     * or result code is not Activity.RESULT_OK
+     * or user did not select any files.
+     */
+    abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<T>
+
+    /**
+     * Use this function to retrieve files which are shared from another application or internally
+     * by using android.intent.action.SEND or android.intent.action.SEND_MULTIPLE actions.
+     */
+    fun getIncomingFiles(context: Context, data: Intent?): List<T> {
+        if (data == null) return emptyList()
+
+        val uriList = mutableListOf<Uri>()
+        if (data.action == Intent.ACTION_SEND) {
+            (data.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { uriList.add(it) }
+        } else if (data.action == Intent.ACTION_SEND_MULTIPLE) {
+            val extraUriList: List<Uri>? = data.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
+            extraUriList?.let { uriList.addAll(it) }
+        }
+
+        val resInfoList: List<ResolveInfo> = context.packageManager.queryIntentActivities(data, PackageManager.MATCH_DEFAULT_ONLY)
+        uriList.forEach {
+            for (resolveInfo in resInfoList) {
+                val packageName: String = resolveInfo.activityInfo.packageName
+                context.grantUriPermission(packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+            }
+        }
+        return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data)
+    }
+
+    /**
+     * Call this function to disable multiple selection of files.
+     */
+    fun single(): Picker<T> {
+        single = true
+        return this
+    }
+
+    abstract fun createIntent(): Intent
+
+    /**
+     * Start Storage Access Framework UI by using an Activity.
+     * @param activity Activity to handle onActivityResult().
+     */
+    fun startWith(activity: Activity) {
+        activity.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode)
+    }
+
+    /**
+     * Start Storage Access Framework UI by using a Fragment.
+     * @param fragment Fragment to handle onActivityResult().
+     */
+    fun startWith(fragment: Fragment) {
+        fragment.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode)
+    }
+
+    protected fun getSelectedUriList(data: Intent?): List<Uri> {
+        val selectedUriList = mutableListOf<Uri>()
+        val dataUri = data?.data
+        val clipData = data?.clipData
+
+        if (clipData != null) {
+            for (i in 0 until clipData.itemCount) {
+                selectedUriList.add(clipData.getItemAt(i).uri)
+            }
+        } else if (dataUri != null) {
+            selectedUriList.add(dataUri)
+        } else {
+            data?.extras?.get(Intent.EXTRA_STREAM)?.let {
+                (it as? List<*>)?.filterIsInstance<Uri>()?.let { uriList ->
+                    selectedUriList.addAll(uriList)
+                }
+                if (it is Uri) {
+                    selectedUriList.add(it)
+                }
+            }
+        }
+        return selectedUriList
+    }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt
new file mode 100644
index 0000000000..b85ffacd48
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.media.MediaMetadataRetriever
+import android.provider.MediaStore
+import im.vector.riotx.multipicker.entity.MultiPickerVideoType
+
+/**
+ * Video Picker implementation
+ */
+class VideoPicker(override val requestCode: Int) : Picker<MultiPickerVideoType>(requestCode) {
+
+    /**
+     * Call this function from onActivityResult(int, int, Intent).
+     * Returns selected video files or empty list if request code is wrong
+     * or result code is not Activity.RESULT_OK
+     * or user did not select any files.
+     */
+    override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerVideoType> {
+        if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+            return emptyList()
+        }
+
+        val videoList = mutableListOf<MultiPickerVideoType>()
+
+        getSelectedUriList(data).forEach { selectedUri ->
+            val projection = arrayOf(
+                    MediaStore.Video.Media.DISPLAY_NAME,
+                    MediaStore.Video.Media.SIZE
+            )
+
+            context.contentResolver.query(
+                    selectedUri,
+                    projection,
+                    null,
+                    null,
+                    null
+            )?.use { cursor ->
+                val nameColumn = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME)
+                val sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE)
+
+                if (cursor.moveToNext()) {
+                    val name = cursor.getString(nameColumn)
+                    val size = cursor.getLong(sizeColumn)
+                    var duration = 0L
+                    var width = 0
+                    var height = 0
+                    var orientation = 0
+
+                    context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
+                        val mediaMetadataRetriever = MediaMetadataRetriever()
+                        mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
+                        duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
+                        width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt()
+                        height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt()
+                        orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION).toInt()
+                    }
+
+                    videoList.add(
+                            MultiPickerVideoType(
+                                    name,
+                                    size,
+                                    context.contentResolver.getType(selectedUri),
+                                    selectedUri,
+                                    width,
+                                    height,
+                                    orientation,
+                                    duration
+                            )
+                    )
+                }
+            }
+        }
+        return videoList
+    }
+
+    override fun createIntent(): Intent {
+        return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+            addCategory(Intent.CATEGORY_OPENABLE)
+            putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+            type = "video/*"
+        }
+    }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt
new file mode 100644
index 0000000000..6afe022024
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.multipicker.entity
+
+import android.net.Uri
+
+data class MultiPickerAudioType(
+        override val displayName: String?,
+        override val size: Long,
+        override val mimeType: String?,
+        override val contentUri: Uri,
+        val duration: Long
+) : MultiPickerBaseType
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt
new file mode 100644
index 0000000000..777e4d8441
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.multipicker.entity
+
+import android.net.Uri
+
+interface MultiPickerBaseType {
+    val displayName: String?
+    val size: Long
+    val mimeType: String?
+    val contentUri: Uri
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt
new file mode 100644
index 0000000000..a9135443a2
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.multipicker.entity
+
+data class MultiPickerContactType(
+        val displayName: String,
+        val photoUri: String?,
+        val phoneNumberList: List<String>,
+        val emailList: List<String>
+)
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt
new file mode 100644
index 0000000000..5417520d28
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.multipicker.entity
+
+import android.net.Uri
+
+data class MultiPickerFileType(
+        override val displayName: String?,
+        override val size: Long,
+        override val mimeType: String?,
+        override val contentUri: Uri
+) : MultiPickerBaseType
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt
new file mode 100644
index 0000000000..b1aef171b4
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.multipicker.entity
+
+import android.net.Uri
+
+data class MultiPickerImageType(
+        override val displayName: String?,
+        override val size: Long,
+        override val mimeType: String?,
+        override val contentUri: Uri,
+        val width: Int,
+        val height: Int,
+        val orientation: Int
+) : MultiPickerBaseType
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt
new file mode 100644
index 0000000000..ba9a8d233e
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.multipicker.entity
+
+import android.net.Uri
+
+data class MultiPickerVideoType(
+        override val displayName: String?,
+        override val size: Long,
+        override val mimeType: String?,
+        override val contentUri: Uri,
+        val width: Int,
+        val height: Int,
+        val orientation: Int,
+        val duration: Long
+) : MultiPickerBaseType
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt
new file mode 100644
index 0000000000..048b2ca199
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.multipicker.provider
+
+import androidx.core.content.FileProvider
+
+class MultiPickerFileProvider : FileProvider()
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt
new file mode 100644
index 0000000000..c5171e7d84
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.multipicker.utils
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.ImageDecoder
+import android.net.Uri
+import android.os.Build
+import androidx.exifinterface.media.ExifInterface
+import timber.log.Timber
+
+object ImageUtils {
+
+    fun getBitmap(context: Context, uri: Uri): Bitmap? {
+        return try {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri))
+            } else {
+                context.contentResolver.openInputStream(uri)?.use { inputStream ->
+                    BitmapFactory.decodeStream(inputStream)
+                }
+            }
+        } catch (e: Exception) {
+            Timber.e(e, "Cannot decode Bitmap: %s", uri.toString())
+            null
+        }
+    }
+
+    fun getOrientation(context: Context, uri: Uri): Int {
+        var orientation = 0
+        context.contentResolver.openInputStream(uri)?.use { inputStream ->
+            try {
+                ExifInterface(inputStream).let {
+                    orientation = it.rotationDegrees
+                }
+            } catch (e: Exception) {
+                Timber.e(e, "Cannot read orientation: %s", uri.toString())
+            }
+        }
+        return orientation
+    }
+}
diff --git a/multipicker/src/main/res/xml/multipicker_provider_paths.xml b/multipicker/src/main/res/xml/multipicker_provider_paths.xml
new file mode 100644
index 0000000000..ff9b81ce98
--- /dev/null
+++ b/multipicker/src/main/res/xml/multipicker_provider_paths.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths>
+    <files-path
+        name="external_files"
+        path="." />
+</paths>
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index d020abade4..04307e89d9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,2 @@
 include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch'
+include ':multipicker'
diff --git a/vector/build.gradle b/vector/build.gradle
index 5b7a18b531..96e6e5a2a8 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -254,6 +254,7 @@ dependencies {
     implementation project(":matrix-sdk-android")
     implementation project(":matrix-sdk-android-rx")
     implementation project(":diff-match-patch")
+    implementation project(":multipicker")
     implementation 'com.android.support:multidex:1.0.3'
 
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@@ -323,7 +324,7 @@ dependencies {
     implementation 'com.nulab-inc:zxcvbn:1.2.7'
 
     //Alerter
-    implementation 'com.tapadoo.android:alerter:4.0.3'
+    implementation 'com.tapadoo.android:alerter:5.1.2'
 
     implementation 'com.otaliastudios:autocomplete:1.1.0'
 
@@ -347,9 +348,6 @@ dependencies {
     // Badge for compatibility
     implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
 
-    // File picker
-    implementation 'com.kbeanie:multipicker:1.6@aar'
-
     // DI
     implementation "com.google.dagger:dagger:$daggerVersion"
     kapt "com.google.dagger:dagger-compiler:$daggerVersion"
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 2e56e20ce7..092817a6cc 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
     <!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore -->
     <!-- Tell that the Camera is not mandatory to install the application -->
diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt
index 81cf1402b0..bd85596924 100644
--- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt
+++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt
@@ -48,6 +48,7 @@ import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
 import im.vector.riotx.features.notifications.NotificationDrawerManager
 import im.vector.riotx.features.notifications.NotificationUtils
 import im.vector.riotx.features.notifications.PushRuleTriggerListener
+import im.vector.riotx.features.popup.PopupAlertManager
 import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
 import im.vector.riotx.features.session.SessionListener
 import im.vector.riotx.features.settings.VectorPreferences
@@ -77,6 +78,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
     @Inject lateinit var notificationUtils: NotificationUtils
     @Inject lateinit var appStateHandler: AppStateHandler
     @Inject lateinit var rxConfig: RxConfig
+    @Inject lateinit var popupAlertManager: PopupAlertManager
     lateinit var vectorComponent: VectorComponent
     private var fontThreadHandler: Handler? = null
 
@@ -102,7 +104,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
         BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
         EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
         EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
-        registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())
+        registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager))
         val fontRequest = FontRequest(
                 "com.google.android.gms.fonts",
                 "com.google.android.gms",
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 4e60a1bdf7..4deaef32ab 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
@@ -26,6 +26,8 @@ import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment
 import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
 import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
 import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
+import im.vector.riotx.features.crypto.verification.cancel.VerificationCancelFragment
+import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFragment
 import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
 import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
 import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
@@ -336,6 +338,16 @@ interface FragmentModule {
     @FragmentKey(VerificationConclusionFragment::class)
     fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment
 
+    @Binds
+    @IntoMap
+    @FragmentKey(VerificationCancelFragment::class)
+    fun bindVerificationCancelFragment(fragment: VerificationCancelFragment): Fragment
+
+    @Binds
+    @IntoMap
+    @FragmentKey(VerificationNotMeFragment::class)
+    fun bindVerificationNotMeFragment(fragment: VerificationNotMeFragment): Fragment
+
     @Binds
     @IntoMap
     @FragmentKey(QrCodeScannerFragment::class)
diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
index 4ae92b29b1..2652f58b04 100644
--- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
+++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
@@ -45,6 +45,7 @@ import im.vector.riotx.features.notifications.NotificationBroadcastReceiver
 import im.vector.riotx.features.notifications.NotificationDrawerManager
 import im.vector.riotx.features.notifications.NotificationUtils
 import im.vector.riotx.features.notifications.PushRuleTriggerListener
+import im.vector.riotx.features.popup.PopupAlertManager
 import im.vector.riotx.features.rageshake.BugReporter
 import im.vector.riotx.features.rageshake.VectorFileLogger
 import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
@@ -128,6 +129,8 @@ interface VectorComponent {
 
     fun emojiDataSource(): EmojiDataSource
 
+    fun alertManager() : PopupAlertManager
+
     @Component.Factory
     interface Factory {
         fun create(@BindsInstance context: Context): VectorComponent
diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt
index 51f3ce611a..3b3132229c 100644
--- a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt
+++ b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt
@@ -16,10 +16,13 @@
 
 package im.vector.riotx.core.extensions
 
+import androidx.core.content.ContextCompat
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.airbnb.epoxy.EpoxyController
+import im.vector.riotx.R
+import im.vector.riotx.features.themes.ThemeUtils
 
 /**
  * Apply a Vertical LinearLayout Manager to the recyclerView and set the adapter from the epoxy controller
@@ -40,7 +43,13 @@ fun RecyclerView.configureWith(epoxyController: EpoxyController,
         itemAnimator?.let { this.itemAnimator = it }
     }
     if (showDivider) {
-        addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
+        addItemDecoration(
+                DividerItemDecoration(context, DividerItemDecoration.VERTICAL).apply {
+                    ContextCompat.getDrawable(context, ThemeUtils.getResourceId(context, R.drawable.divider_horizontal_light))?.let {
+                        setDrawable(it)
+                    }
+                }
+        )
     }
     setHasFixedSize(hasFixedSize)
     adapter = epoxyController.adapter
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt
index c576ebe1b9..daea538e12 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt
@@ -18,20 +18,13 @@ package im.vector.riotx.features.attachments
 import android.app.Activity
 import android.content.Context
 import android.content.Intent
+import android.net.Uri
 import android.os.Bundle
 import androidx.fragment.app.Fragment
-import com.kbeanie.multipicker.api.Picker.PICK_AUDIO
-import com.kbeanie.multipicker.api.Picker.PICK_CONTACT
-import com.kbeanie.multipicker.api.Picker.PICK_FILE
-import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_CAMERA
-import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_DEVICE
-import com.kbeanie.multipicker.core.ImagePickerImpl
-import com.kbeanie.multipicker.core.PickerManager
-import com.kbeanie.multipicker.utils.IntentUtils
 import im.vector.matrix.android.BuildConfig
 import im.vector.matrix.android.api.session.content.ContentAttachmentData
 import im.vector.riotx.core.platform.Restorable
-import im.vector.riotx.features.attachments.AttachmentsHelper.Callback
+import im.vector.riotx.multipicker.MultiPicker
 import timber.log.Timber
 
 private const val CAPTURE_PATH_KEY = "CAPTURE_PATH_KEY"
@@ -39,20 +32,8 @@ private const val PENDING_TYPE_KEY = "PENDING_TYPE_KEY"
 
 /**
  * This class helps to handle attachments by providing simple methods.
- * The process is asynchronous and you must implement [Callback] methods to get the data or a failure.
  */
-class AttachmentsHelper private constructor(private val context: Context,
-                                            private val pickerManagerFactory: PickerManagerFactory) : Restorable {
-
-    companion object {
-        fun create(fragment: Fragment, callback: Callback): AttachmentsHelper {
-            return AttachmentsHelper(fragment.requireContext(), FragmentPickerManagerFactory(fragment, callback))
-        }
-
-        fun create(activity: Activity, callback: Callback): AttachmentsHelper {
-            return AttachmentsHelper(activity, ActivityPickerManagerFactory(activity, callback))
-        }
-    }
+class AttachmentsHelper(val context: Context, val callback: Callback) : Restorable {
 
     interface Callback {
         fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
@@ -66,39 +47,15 @@ class AttachmentsHelper private constructor(private val context: Context,
     }
 
     // Capture path allows to handle camera image picking. It must be restored if the activity gets killed.
-    private var capturePath: String? = null
+    private var captureUri: Uri? = null
     // The pending type is set if we have to handle permission request. It must be restored if the activity gets killed.
     var pendingType: AttachmentTypeSelectorView.Type? = null
 
-    private val imagePicker by lazy {
-        pickerManagerFactory.createImagePicker()
-    }
-
-    private val videoPicker by lazy {
-        pickerManagerFactory.createVideoPicker()
-    }
-
-    private val cameraImagePicker by lazy {
-        pickerManagerFactory.createCameraImagePicker()
-    }
-
-    private val filePicker by lazy {
-        pickerManagerFactory.createFilePicker()
-    }
-
-    private val audioPicker by lazy {
-        pickerManagerFactory.createAudioPicker()
-    }
-
-    private val contactPicker by lazy {
-        pickerManagerFactory.createContactPicker()
-    }
-
     // Restorable
 
     override fun onSaveInstanceState(outState: Bundle) {
-        capturePath?.also {
-            outState.putString(CAPTURE_PATH_KEY, it)
+        captureUri?.also {
+            outState.putParcelable(CAPTURE_PATH_KEY, it)
         }
         pendingType?.also {
             outState.putSerializable(PENDING_TYPE_KEY, it)
@@ -106,10 +63,7 @@ class AttachmentsHelper private constructor(private val context: Context,
     }
 
     override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
-        capturePath = savedInstanceState?.getString(CAPTURE_PATH_KEY)
-        if (capturePath != null) {
-            cameraImagePicker.reinitialize(capturePath)
-        }
+        captureUri = savedInstanceState?.getParcelable(CAPTURE_PATH_KEY) as? Uri
         pendingType = savedInstanceState?.getSerializable(PENDING_TYPE_KEY) as? AttachmentTypeSelectorView.Type
     }
 
@@ -118,36 +72,36 @@ class AttachmentsHelper private constructor(private val context: Context,
     /**
      * Starts the process for handling file picking
      */
-    fun selectFile() {
-        filePicker.pickFile()
+    fun selectFile(fragment: Fragment) {
+        MultiPicker.get(MultiPicker.FILE).startWith(fragment)
     }
 
     /**
      * Starts the process for handling image picking
      */
-    fun selectGallery() {
-        imagePicker.pickImage()
+    fun selectGallery(fragment: Fragment) {
+        MultiPicker.get(MultiPicker.IMAGE).startWith(fragment)
     }
 
     /**
      * Starts the process for handling audio picking
      */
-    fun selectAudio() {
-        audioPicker.pickAudio()
+    fun selectAudio(fragment: Fragment) {
+        MultiPicker.get(MultiPicker.AUDIO).startWith(fragment)
     }
 
     /**
      * Starts the process for handling capture image picking
      */
-    fun openCamera() {
-        capturePath = cameraImagePicker.pickImage()
+    fun openCamera(fragment: Fragment) {
+        captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(fragment)
     }
 
     /**
      * Starts the process for handling contact picking
      */
-    fun selectContact() {
-        contactPicker.pickContact()
+    fun selectContact(fragment: Fragment) {
+        MultiPicker.get(MultiPicker.CONTACT).startWith(fragment)
     }
 
     /**
@@ -157,14 +111,58 @@ class AttachmentsHelper private constructor(private val context: Context,
      */
     fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
         if (resultCode == Activity.RESULT_OK) {
-            val pickerManager = getPickerManagerForRequestCode(requestCode)
-            if (pickerManager != null) {
-                if (pickerManager is ImagePickerImpl) {
-                    pickerManager.reinitialize(capturePath)
+            when (requestCode) {
+                MultiPicker.REQUEST_CODE_PICK_FILE    -> {
+                    callback.onContentAttachmentsReady(
+                            MultiPicker.get(MultiPicker.FILE)
+                                    .getSelectedFiles(context, requestCode, resultCode, data)
+                                    .map { it.toContentAttachmentData() }
+                    )
                 }
-                pickerManager.submit(data)
-                return true
+                MultiPicker.REQUEST_CODE_PICK_AUDIO   -> {
+                    callback.onContentAttachmentsReady(
+                            MultiPicker.get(MultiPicker.AUDIO)
+                                    .getSelectedFiles(context, requestCode, resultCode, data)
+                                    .map { it.toContentAttachmentData() }
+                    )
+                }
+                MultiPicker.REQUEST_CODE_PICK_CONTACT -> {
+                    MultiPicker.get(MultiPicker.CONTACT)
+                            .getSelectedFiles(context, requestCode, resultCode, data)
+                            .firstOrNull()
+                            ?.toContactAttachment()
+                            ?.let {
+                                callback.onContactAttachmentReady(it)
+                            }
+                }
+                MultiPicker.REQUEST_CODE_PICK_IMAGE   -> {
+                    callback.onContentAttachmentsReady(
+                            MultiPicker.get(MultiPicker.IMAGE)
+                                    .getSelectedFiles(context, requestCode, resultCode, data)
+                                    .map { it.toContentAttachmentData() }
+                    )
+                }
+                MultiPicker.REQUEST_CODE_TAKE_PHOTO   -> {
+                    captureUri?.let { captureUri ->
+                        MultiPicker.get(MultiPicker.CAMERA)
+                                .getTakenPhoto(context, requestCode, resultCode, captureUri)
+                                ?.let {
+                                    callback.onContentAttachmentsReady(
+                                            listOf(it).map { it.toContentAttachmentData() }
+                                    )
+                                }
+                    }
+                }
+                MultiPicker.REQUEST_CODE_PICK_VIDEO   -> {
+                    callback.onContentAttachmentsReady(
+                            MultiPicker.get(MultiPicker.VIDEO)
+                                    .getSelectedFiles(context, requestCode, resultCode, data)
+                                    .map { it.toContentAttachmentData() }
+                    )
+                }
+                else                                  -> return false
             }
+            return true
         }
         return false
     }
@@ -174,39 +172,35 @@ class AttachmentsHelper private constructor(private val context: Context,
      *
      * @return true if it can handle the intent data, false otherwise
      */
-    fun handleShareIntent(intent: Intent): Boolean {
+    fun handleShareIntent(context: Context, intent: Intent): Boolean {
         val type = intent.resolveType(context) ?: return false
         if (type.startsWith("image")) {
-            imagePicker.submit(safeShareIntent(intent))
+            callback.onContentAttachmentsReady(
+                    MultiPicker.get(MultiPicker.IMAGE).getIncomingFiles(context, intent).map {
+                        it.toContentAttachmentData()
+                    }
+            )
         } else if (type.startsWith("video")) {
-            videoPicker.submit(safeShareIntent(intent))
+            callback.onContentAttachmentsReady(
+                    MultiPicker.get(MultiPicker.VIDEO).getIncomingFiles(context, intent).map {
+                        it.toContentAttachmentData()
+                    }
+            )
         } else if (type.startsWith("audio")) {
-            videoPicker.submit(safeShareIntent(intent))
+            callback.onContentAttachmentsReady(
+                    MultiPicker.get(MultiPicker.AUDIO).getIncomingFiles(context, intent).map {
+                        it.toContentAttachmentData()
+                    }
+            )
         } else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
-            filePicker.submit(safeShareIntent(intent))
+            callback.onContentAttachmentsReady(
+                    MultiPicker.get(MultiPicker.FILE).getIncomingFiles(context, intent).map {
+                        it.toContentAttachmentData()
+                    }
+            )
         } else {
             return false
         }
         return true
     }
-
-    private fun safeShareIntent(intent: Intent): Intent {
-        // Work around for getPickerIntentForSharing doing NPE in android 10
-        return try {
-            IntentUtils.getPickerIntentForSharing(intent)
-        } catch (failure: Throwable) {
-            intent
-        }
-    }
-
-    private fun getPickerManagerForRequestCode(requestCode: Int): PickerManager? {
-        return when (requestCode) {
-            PICK_IMAGE_DEVICE -> imagePicker
-            PICK_IMAGE_CAMERA -> cameraImagePicker
-            PICK_FILE         -> filePicker
-            PICK_CONTACT      -> contactPicker
-            PICK_AUDIO        -> audioPicker
-            else              -> null
-        }
-    }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt
index a3de5084de..02b712b8a7 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt
@@ -16,51 +16,48 @@
 
 package im.vector.riotx.features.attachments
 
-import com.kbeanie.multipicker.api.entity.ChosenAudio
-import com.kbeanie.multipicker.api.entity.ChosenContact
-import com.kbeanie.multipicker.api.entity.ChosenFile
-import com.kbeanie.multipicker.api.entity.ChosenImage
-import com.kbeanie.multipicker.api.entity.ChosenVideo
 import im.vector.matrix.android.api.session.content.ContentAttachmentData
+import im.vector.riotx.multipicker.entity.MultiPickerAudioType
+import im.vector.riotx.multipicker.entity.MultiPickerBaseType
+import im.vector.riotx.multipicker.entity.MultiPickerContactType
+import im.vector.riotx.multipicker.entity.MultiPickerFileType
+import im.vector.riotx.multipicker.entity.MultiPickerImageType
+import im.vector.riotx.multipicker.entity.MultiPickerVideoType
 import timber.log.Timber
 
-fun ChosenContact.toContactAttachment(): ContactAttachment {
+fun MultiPickerContactType.toContactAttachment(): ContactAttachment {
     return ContactAttachment(
             displayName = displayName,
             photoUri = photoUri,
-            emails = emails.toList(),
-            phones = phones.toList()
+            emails = emailList.toList(),
+            phones = phoneNumberList.toList()
     )
 }
 
-fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
+fun MultiPickerFileType.toContentAttachmentData(): ContentAttachmentData {
     if (mimeType == null) Timber.w("No mimeType")
     return ContentAttachmentData(
-            path = originalPath,
             mimeType = mimeType,
             type = mapType(),
             size = size,
-            date = createdAt?.time ?: System.currentTimeMillis(),
             name = displayName,
-            queryUri = queryUri
+            queryUri = contentUri
     )
 }
 
-fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData {
+fun MultiPickerAudioType.toContentAttachmentData(): ContentAttachmentData {
     if (mimeType == null) Timber.w("No mimeType")
     return ContentAttachmentData(
-            path = originalPath,
             mimeType = mimeType,
             type = mapType(),
             size = size,
-            date = createdAt?.time ?: System.currentTimeMillis(),
             name = displayName,
             duration = duration,
-            queryUri = queryUri
+            queryUri = contentUri
     )
 }
 
-private fun ChosenFile.mapType(): ContentAttachmentData.Type {
+private fun MultiPickerBaseType.mapType(): ContentAttachmentData.Type {
     return when {
         mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE
         mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO
@@ -69,10 +66,9 @@ private fun ChosenFile.mapType(): ContentAttachmentData.Type {
     }
 }
 
-fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
+fun MultiPickerImageType.toContentAttachmentData(): ContentAttachmentData {
     if (mimeType == null) Timber.w("No mimeType")
     return ContentAttachmentData(
-            path = originalPath,
             mimeType = mimeType,
             type = mapType(),
             name = displayName,
@@ -80,23 +76,20 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
             height = height.toLong(),
             width = width.toLong(),
             exifOrientation = orientation,
-            date = createdAt?.time ?: System.currentTimeMillis(),
-            queryUri = queryUri
+            queryUri = contentUri
     )
 }
 
-fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
+fun MultiPickerVideoType.toContentAttachmentData(): ContentAttachmentData {
     if (mimeType == null) Timber.w("No mimeType")
     return ContentAttachmentData(
-            path = originalPath,
             mimeType = mimeType,
             type = ContentAttachmentData.Type.VIDEO,
             size = size,
-            date = createdAt?.time ?: System.currentTimeMillis(),
             height = height.toLong(),
             width = width.toLong(),
             duration = duration,
             name = displayName,
-            queryUri = queryUri
+            queryUri = contentUri
     )
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt
deleted file mode 100644
index 62956e08c8..0000000000
--- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.riotx.features.attachments
-
-import com.kbeanie.multipicker.api.callbacks.AudioPickerCallback
-import com.kbeanie.multipicker.api.callbacks.ContactPickerCallback
-import com.kbeanie.multipicker.api.callbacks.FilePickerCallback
-import com.kbeanie.multipicker.api.callbacks.ImagePickerCallback
-import com.kbeanie.multipicker.api.callbacks.VideoPickerCallback
-import com.kbeanie.multipicker.api.entity.ChosenAudio
-import com.kbeanie.multipicker.api.entity.ChosenContact
-import com.kbeanie.multipicker.api.entity.ChosenFile
-import com.kbeanie.multipicker.api.entity.ChosenImage
-import com.kbeanie.multipicker.api.entity.ChosenVideo
-
-/**
- * This class delegates the PickerManager callbacks to an [AttachmentsHelper.Callback]
- */
-class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback)
-    : ImagePickerCallback,
-        FilePickerCallback,
-        VideoPickerCallback,
-        AudioPickerCallback,
-        ContactPickerCallback {
-
-    override fun onContactChosen(contact: ChosenContact?) {
-        if (contact == null) {
-            callback.onAttachmentsProcessFailed()
-        } else {
-            val contactAttachment = contact.toContactAttachment()
-            callback.onContactAttachmentReady(contactAttachment)
-        }
-    }
-
-    override fun onAudiosChosen(audios: MutableList<ChosenAudio>?) {
-        if (audios.isNullOrEmpty()) {
-            callback.onAttachmentsProcessFailed()
-        } else {
-            val attachments = audios.map {
-                it.toContentAttachmentData()
-            }
-            callback.onContentAttachmentsReady(attachments)
-        }
-    }
-
-    override fun onFilesChosen(files: MutableList<ChosenFile>?) {
-        if (files.isNullOrEmpty()) {
-            callback.onAttachmentsProcessFailed()
-        } else {
-            val attachments = files.map {
-                it.toContentAttachmentData()
-            }
-            callback.onContentAttachmentsReady(attachments)
-        }
-    }
-
-    override fun onImagesChosen(images: MutableList<ChosenImage>?) {
-        if (images.isNullOrEmpty()) {
-            callback.onAttachmentsProcessFailed()
-        } else {
-            val attachments = images.map {
-                it.toContentAttachmentData()
-            }
-            callback.onContentAttachmentsReady(attachments)
-        }
-    }
-
-    override fun onVideosChosen(videos: MutableList<ChosenVideo>?) {
-        if (videos.isNullOrEmpty()) {
-            callback.onAttachmentsProcessFailed()
-        } else {
-            val attachments = videos.map {
-                it.toContentAttachmentData()
-            }
-            callback.onContentAttachmentsReady(attachments)
-        }
-    }
-
-    override fun onError(error: String?) {
-        callback.onAttachmentsProcessFailed()
-    }
-}
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt b/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt
deleted file mode 100644
index 6c03f21ab3..0000000000
--- a/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.riotx.features.attachments
-
-import android.app.Activity
-import androidx.fragment.app.Fragment
-import com.kbeanie.multipicker.api.AudioPicker
-import com.kbeanie.multipicker.api.CameraImagePicker
-import com.kbeanie.multipicker.api.ContactPicker
-import com.kbeanie.multipicker.api.FilePicker
-import com.kbeanie.multipicker.api.ImagePicker
-import com.kbeanie.multipicker.api.VideoPicker
-
-/**
- * Factory for creating different pickers. It allows to use with fragment or activity builders.
- */
-interface PickerManagerFactory {
-
-    fun createImagePicker(): ImagePicker
-
-    fun createCameraImagePicker(): CameraImagePicker
-
-    fun createVideoPicker(): VideoPicker
-
-    fun createFilePicker(): FilePicker
-
-    fun createAudioPicker(): AudioPicker
-
-    fun createContactPicker(): ContactPicker
-}
-
-class ActivityPickerManagerFactory(private val activity: Activity, callback: AttachmentsHelper.Callback) : PickerManagerFactory {
-
-    private val attachmentsPickerCallback = AttachmentsPickerCallback(callback)
-
-    override fun createImagePicker(): ImagePicker {
-        return ImagePicker(activity).also {
-            it.setImagePickerCallback(attachmentsPickerCallback)
-            it.allowMultiple()
-        }
-    }
-
-    override fun createCameraImagePicker(): CameraImagePicker {
-        return CameraImagePicker(activity).also {
-            it.setImagePickerCallback(attachmentsPickerCallback)
-        }
-    }
-
-    override fun createVideoPicker(): VideoPicker {
-        return VideoPicker(activity).also {
-            it.setVideoPickerCallback(attachmentsPickerCallback)
-            it.allowMultiple()
-        }
-    }
-
-    override fun createFilePicker(): FilePicker {
-        return FilePicker(activity).also {
-            it.allowMultiple()
-            it.setFilePickerCallback(attachmentsPickerCallback)
-        }
-    }
-
-    override fun createAudioPicker(): AudioPicker {
-        return AudioPicker(activity).also {
-            it.allowMultiple()
-            it.setAudioPickerCallback(attachmentsPickerCallback)
-        }
-    }
-
-    override fun createContactPicker(): ContactPicker {
-        return ContactPicker(activity).also {
-            it.setContactPickerCallback(attachmentsPickerCallback)
-        }
-    }
-}
-
-class FragmentPickerManagerFactory(private val fragment: Fragment, callback: AttachmentsHelper.Callback) : PickerManagerFactory {
-
-    private val attachmentsPickerCallback = AttachmentsPickerCallback(callback)
-
-    override fun createImagePicker(): ImagePicker {
-        return ImagePicker(fragment).also {
-            it.setImagePickerCallback(attachmentsPickerCallback)
-            it.allowMultiple()
-        }
-    }
-
-    override fun createCameraImagePicker(): CameraImagePicker {
-        return CameraImagePicker(fragment).also {
-            it.setImagePickerCallback(attachmentsPickerCallback)
-        }
-    }
-
-    override fun createVideoPicker(): VideoPicker {
-        return VideoPicker(fragment).also {
-            it.setVideoPickerCallback(attachmentsPickerCallback)
-            it.allowMultiple()
-        }
-    }
-
-    override fun createFilePicker(): FilePicker {
-        return FilePicker(fragment).also {
-            it.allowMultiple()
-            it.setFilePickerCallback(attachmentsPickerCallback)
-        }
-    }
-
-    override fun createAudioPicker(): AudioPicker {
-        return AudioPicker(fragment).also {
-            it.allowMultiple()
-            it.setAudioPickerCallback(attachmentsPickerCallback)
-        }
-    }
-
-    override fun createContactPicker(): ContactPicker {
-        return ContactPicker(fragment).also {
-            it.setContactPickerCallback(attachmentsPickerCallback)
-        }
-    }
-}
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt
index 34f018aaf9..60ee722116 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt
@@ -25,7 +25,7 @@ class AttachmentBigPreviewController @Inject constructor() : TypedEpoxyControlle
     override fun buildModels(data: AttachmentsPreviewViewState) {
         data.attachments.forEach {
             attachmentBigPreviewItem {
-                id(it.path)
+                id(it.queryUri.toString())
                 attachment(it)
             }
         }
@@ -43,7 +43,7 @@ class AttachmentMiniaturePreviewController @Inject constructor() : TypedEpoxyCon
     override fun buildModels(data: AttachmentsPreviewViewState) {
         data.attachments.forEachIndexed { index, contentAttachmentData ->
             attachmentMiniaturePreviewItem {
-                id(contentAttachmentData.path)
+                id(contentAttachmentData.queryUri.toString())
                 attachment(contentAttachmentData)
                 checked(data.currentAttachmentIndex == index)
                 clickListener { _ ->
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt
index 3b43fa6e20..373298bf31 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt
@@ -33,11 +33,10 @@ abstract class AttachmentPreviewItem<H : AttachmentPreviewItem.Holder> : VectorE
     abstract val attachment: ContentAttachmentData
 
     override fun bind(holder: H) {
-        val path = attachment.path
         if (attachment.type == ContentAttachmentData.Type.VIDEO || attachment.type == ContentAttachmentData.Type.IMAGE) {
             Glide.with(holder.view.context)
                     .asBitmap()
-                    .load(path)
+                    .load(attachment.queryUri)
                     .apply(RequestOptions().frame(0))
                     .into(holder.imageView)
         } else {
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt
index 5acc59b035..aef724331f 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt
@@ -17,10 +17,11 @@
 
 package im.vector.riotx.features.attachments.preview
 
+import android.net.Uri
 import im.vector.riotx.core.platform.VectorViewModelAction
 
 sealed class AttachmentsPreviewAction : VectorViewModelAction {
     object RemoveCurrentAttachment : AttachmentsPreviewAction()
     data class SetCurrentAttachment(val index: Int): AttachmentsPreviewAction()
-    data class UpdatePathOfCurrentAttachment(val newPath: String): AttachmentsPreviewAction()
+    data class UpdatePathOfCurrentAttachment(val newUri: Uri): AttachmentsPreviewAction()
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt
index e52b497df4..3b1972ffbc 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt
@@ -172,9 +172,9 @@ class AttachmentsPreviewFragment @Inject constructor(
     }
 
     private fun handleCropResult(result: Intent) {
-        val resultPath = UCrop.getOutput(result)?.path
-        if (resultPath != null) {
-            viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultPath))
+        val resultUri = UCrop.getOutput(result)
+        if (resultUri != null) {
+            viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultUri))
         } else {
             Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
         }
@@ -202,8 +202,7 @@ class AttachmentsPreviewFragment @Inject constructor(
     private fun doHandleEditAction() = withState(viewModel) {
         val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
         val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
-        // Note: using currentAttachment.queryUri.toUri() make the app crash when sharing from Google Photos
-        val uri = File(currentAttachment.path).toUri()
+        val uri = currentAttachment.queryUri
         UCrop.of(uri, destinationFile.toUri())
                 .withOptions(
                         UCrop.Options()
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt
index 1f6c8c2f8b..d1e44fa963 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt
@@ -62,7 +62,7 @@ class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialS
     private fun handleUpdatePathOfCurrentAttachment(action: AttachmentsPreviewAction.UpdatePathOfCurrentAttachment) = withState {
         val attachments = it.attachments.mapIndexed { index, contentAttachmentData ->
             if (index == it.currentAttachmentIndex) {
-                contentAttachmentData.copy(path = action.newPath)
+                contentAttachmentData.copy(queryUri = action.newUri)
             } else {
                 contentAttachmentData
             }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt
index d9860e6bad..ddb50628d6 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt
@@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
 import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
 import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
 import im.vector.riotx.R
+import im.vector.riotx.features.popup.DefaultVectorAlert
 import im.vector.riotx.features.popup.PopupAlertManager
 import timber.log.Timber
 import java.text.DateFormat
@@ -54,7 +55,7 @@ import javax.inject.Singleton
  */
 
 @Singleton
-class KeyRequestHandler @Inject constructor(private val context: Context)
+class KeyRequestHandler @Inject constructor(private val context: Context, private val popupAlertManager: PopupAlertManager)
     : GossipingRequestListener,
         VerificationService.Listener {
 
@@ -118,9 +119,9 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
                 }
 
                 if (deviceInfo.isUnknown) {
-                    session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(false, false), userId, deviceId)
+                    session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false), userId, deviceId)
 
-                    deviceInfo.trustLevel = DeviceTrustLevel(false, false)
+                    deviceInfo.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
 
                     // can we get more info on this device?
                     session?.cryptoService()?.getDevicesList(object : MatrixCallback<DevicesListResponse> {
@@ -188,7 +189,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
             }
         }
 
-        val alert = PopupAlertManager.VectorAlert(
+        val alert = DefaultVectorAlert(
                 alertManagerId(userId, deviceId),
                 context.getString(R.string.key_share_request),
                 dialogText,
@@ -210,7 +211,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
             denyAllRequests(mappingKey)
         })
 
-        PopupAlertManager.postVectorAlert(alert)
+        popupAlertManager.postVectorAlert(alert)
     }
 
     private fun denyAllRequests(mappingKey: String) {
@@ -250,7 +251,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
                     && it.requestId == request.requestId
         }
         if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) {
-            PopupAlertManager.cancelAlert(alertMgrUniqueKey)
+            popupAlertManager.cancelAlert(alertMgrUniqueKey)
             alertsToRequests.remove(keyForMap(userId, deviceId))
         }
     }
@@ -261,7 +262,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
             if (state == VerificationTxState.Verified) {
                 // ok it's verified, see if we have key request for that
                 shareAllSessions("${tx.otherDeviceId}${tx.otherUserId}")
-                PopupAlertManager.cancelAlert("ikr_${tx.otherDeviceId}${tx.otherUserId}")
+                popupAlertManager.cancelAlert("ikr_${tx.otherDeviceId}${tx.otherUserId}")
             }
         }
         // should do it with QR tx also
@@ -271,7 +272,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
     override fun markedAsManuallyVerified(userId: String, deviceId: String) {
         // accept related requests
         shareAllSessions(keyForMap(userId, deviceId))
-        PopupAlertManager.cancelAlert(alertManagerId(userId, deviceId))
+        popupAlertManager.cancelAlert(alertManagerId(userId, deviceId))
     }
 
     private fun keyForMap(userId: String, deviceId: String) = "$deviceId$userId"
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt
index e7e26f52a4..ccd3e6578a 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt
@@ -17,15 +17,17 @@ package im.vector.riotx.features.crypto.verification
 
 import android.content.Context
 import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
 import im.vector.matrix.android.api.session.crypto.verification.VerificationService
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
-import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
+import im.vector.matrix.android.api.util.toMatrixItem
 import im.vector.riotx.R
 import im.vector.riotx.core.platform.VectorBaseActivity
 import im.vector.riotx.features.home.room.detail.RoomDetailActivity
 import im.vector.riotx.features.home.room.detail.RoomDetailArgs
 import im.vector.riotx.features.popup.PopupAlertManager
+import im.vector.riotx.features.popup.VerificationVectorAlert
 import im.vector.riotx.features.themes.ThemeUtils
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -34,7 +36,9 @@ import javax.inject.Singleton
  * Listens to the VerificationManager and add a new notification when an incoming request is detected.
  */
 @Singleton
-class IncomingVerificationRequestHandler @Inject constructor(private val context: Context) : VerificationService.Listener {
+class IncomingVerificationRequestHandler @Inject constructor(
+        private val context: Context,
+        private val popupAlertManager: PopupAlertManager) : VerificationService.Listener {
 
     private var session: Session? = null
 
@@ -58,7 +62,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
                 val name = session?.getUser(tx.otherUserId)?.displayName
                         ?: tx.otherUserId
 
-                val alert = PopupAlertManager.VectorAlert(
+                val alert = VerificationVectorAlert(
                         uid,
                         context.getString(R.string.sas_incoming_request_notif_title),
                         context.getString(R.string.sas_incoming_request_notif_content, name),
@@ -68,12 +72,14 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
                                 // TODO a bit too hugly :/
                                 activity.supportFragmentManager.findFragmentByTag(VerificationBottomSheet.WAITING_SELF_VERIF_TAG)?.let {
                                     false.also {
-                                        PopupAlertManager.cancelAlert(uid)
+                                        popupAlertManager.cancelAlert(uid)
                                     }
                                 } ?: true
                             } else true
                         })
                         .apply {
+                            matrixItem = session?.getUser(tx.otherUserId)?.toMatrixItem()
+
                             contentAction = Runnable {
                                 (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
                                     it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
@@ -99,11 +105,11 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
                             // 10mn expiration
                             expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L)
                         }
-                PopupAlertManager.postVectorAlert(alert)
+                popupAlertManager.postVectorAlert(alert)
             }
             is VerificationTxState.TerminalTxState -> {
                 // cancel related notification
-                PopupAlertManager.cancelAlert(uid)
+                popupAlertManager.cancelAlert(uid)
             }
             else                                   -> Unit
         }
@@ -115,7 +121,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
             val name = session?.getUser(pr.otherUserId)?.displayName
                     ?: pr.otherUserId
 
-            val alert = PopupAlertManager.VectorAlert(
+            val alert = VerificationVectorAlert(
                     uniqueIdForVerificationRequest(pr),
                     context.getString(R.string.sas_incoming_request_notif_title),
                     "$name(${pr.otherUserId})",
@@ -128,6 +134,8 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
                         } else true
                     })
                     .apply {
+                        matrixItem = session?.getUser(pr.otherUserId)?.toMatrixItem()
+
                         contentAction = Runnable {
                             (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
                                 val roomId = pr.roomId
@@ -148,14 +156,14 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
                         // 5mn expiration
                         expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L)
                     }
-            PopupAlertManager.postVectorAlert(alert)
+            popupAlertManager.postVectorAlert(alert)
         }
     }
 
     override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
         // If an incoming request is readied (by another device?) we should discard the alert
         if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) {
-            PopupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr))
+            popupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr))
         }
     }
 
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt
index e1218ec4a9..5b7adbdb91 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt
@@ -16,9 +16,11 @@
 package im.vector.riotx.features.crypto.verification
 
 import android.app.Activity
+import android.app.Dialog
 import android.content.Intent
 import android.os.Bundle
 import android.os.Parcelable
+import android.view.KeyEvent
 import android.view.View
 import android.widget.ImageView
 import android.widget.TextView
@@ -34,19 +36,24 @@ import im.vector.matrix.android.api.session.Session
 import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
 import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
 import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
+import im.vector.matrix.android.api.session.crypto.verification.CancelCode
 import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
 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.VectorBaseActivity
 import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
 import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity
+import im.vector.riotx.features.crypto.verification.cancel.VerificationCancelFragment
+import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFragment
 import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
 import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
 import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
 import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment
 import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
 import im.vector.riotx.features.home.AvatarRenderer
+import im.vector.riotx.features.settings.VectorSettingsActivity
 import kotlinx.android.parcel.Parcelize
 import timber.log.Timber
 import javax.inject.Inject
@@ -58,6 +65,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
     data class VerificationArgs(
             val otherUserId: String,
             val verificationId: String? = null,
+            val verificationLocalId: String? = null,
             val roomId: String? = null,
             // Special mode where UX should show loading wheel until other session sends a request/tx
             val selfVerificationMode: Boolean = false
@@ -80,13 +88,17 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
     lateinit var otherUserNameText: TextView
 
     @BindView(R.id.verificationRequestShield)
-    lateinit var otherUserShield: View
+    lateinit var otherUserShield: ImageView
 
     @BindView(R.id.verificationRequestAvatar)
     lateinit var otherUserAvatarImageView: ImageView
 
     override fun getLayoutResId() = R.layout.bottom_sheet_verification
 
+    init {
+        isCancelable = false
+    }
+
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
@@ -110,10 +122,27 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
                             .show()
                     Unit
                 }
+                VerificationBottomSheetViewEvents.GoToSettings         -> {
+                    dismiss()
+                    (activity as? VectorBaseActivity)?.navigator?.openSettings(requireContext(), VectorSettingsActivity.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY)
+                }
             }.exhaustive
         }
     }
 
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        return super.onCreateDialog(savedInstanceState).apply {
+            setOnKeyListener { _, keyCode, keyEvent ->
+                if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == KeyEvent.ACTION_UP) {
+                    viewModel.queryCancel()
+                    true
+                } else {
+                    false
+                }
+            }
+        }
+    }
+
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
         if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) {
             data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)?.let {
@@ -127,15 +156,16 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
 
         state.otherUserMxItem?.let { matrixItem ->
             if (state.isMe) {
+                avatarRenderer.render(matrixItem, otherUserAvatarImageView)
                 if (state.sasTransactionState == VerificationTxState.Verified
                         || state.qrTransactionState == VerificationTxState.Verified
                         || state.verifiedFromPrivateKeys) {
-                    otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_trusted)
+                    otherUserShield.setImageResource(R.drawable.ic_shield_trusted)
                 } else {
-                    otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_warning)
+                    otherUserShield.setImageResource(R.drawable.ic_shield_warning)
                 }
                 otherUserNameText.text = getString(R.string.complete_security)
-                otherUserShield.isVisible = false
+                otherUserShield.isVisible = true
             } else {
                 avatarRenderer.render(matrixItem, otherUserAvatarImageView)
 
@@ -149,6 +179,18 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
             }
         }
 
+        if (state.userThinkItsNotHim) {
+            otherUserNameText.text = getString(R.string.dialog_title_warning)
+            showFragment(VerificationNotMeFragment::class, Bundle())
+            return@withState
+        }
+
+        if (state.userWantsToCancel) {
+            otherUserNameText.text = getString(R.string.are_you_sure)
+            showFragment(VerificationCancelFragment::class, Bundle())
+            return@withState
+        }
+
         if (state.selfVerificationMode && state.verifiedFromPrivateKeys) {
             showFragment(VerificationConclusionFragment::class, Bundle().apply {
                 putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe))
@@ -222,7 +264,14 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
         // Transaction has not yet started
         if (state.pendingRequest.invoke()?.cancelConclusion != null) {
             // The request has been declined, we should dismiss
-            dismiss()
+            otherUserNameText.text = getString(R.string.verification_cancelled)
+            showFragment(VerificationConclusionFragment::class, Bundle().apply {
+                putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
+                        false,
+                        state.pendingRequest.invoke()?.cancelConclusion?.value ?: CancelCode.User.value,
+                        state.isMe))
+            })
+            return@withState
         }
 
         // If it's an outgoing
@@ -267,6 +316,10 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
         }
     }
 
+    override fun dismiss() {
+        super.dismiss()
+    }
+
     companion object {
 
         const val SECRET_REQUEST_CODE = 101
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt
index d7c02a8d3b..7e3a5441de 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt
@@ -24,5 +24,6 @@ import im.vector.riotx.core.platform.VectorViewEvents
 sealed class VerificationBottomSheetViewEvents : VectorViewEvents {
     object Dismiss : VerificationBottomSheetViewEvents()
     object AccessSecretStore : VerificationBottomSheetViewEvents()
+    object GoToSettings : VerificationBottomSheetViewEvents()
     data class ModalError(val errorMessage: CharSequence) : VerificationBottomSheetViewEvents()
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt
index db8dd895b4..731f12cca4 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt
@@ -31,7 +31,9 @@ import im.vector.matrix.android.api.session.Session
 import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
 import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
 import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
+import im.vector.matrix.android.api.session.crypto.verification.CancelCode
 import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
+import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
 import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
 import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
 import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
@@ -44,7 +46,6 @@ import im.vector.matrix.android.api.util.MatrixItem
 import im.vector.matrix.android.api.util.toMatrixItem
 import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
 import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
-import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
 import im.vector.riotx.core.extensions.exhaustive
 import im.vector.riotx.core.platform.VectorViewModel
 import timber.log.Timber
@@ -60,7 +61,10 @@ data class VerificationBottomSheetViewState(
         // true when we display the loading and we wait for the other (incoming request)
         val selfVerificationMode: Boolean = false,
         val verifiedFromPrivateKeys: Boolean = false,
-        val isMe: Boolean = false
+        val isMe: Boolean = false,
+        val currentDeviceCanCrossSign: Boolean = false,
+        val userWantsToCancel: Boolean = false,
+        val userThinkItsNotHim: Boolean = false
 ) : MvRxState
 
 class VerificationBottomSheetViewModel @AssistedInject constructor(
@@ -111,7 +115,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
                     pendingRequest = if (pr != null) Success(pr) else Uninitialized,
                     selfVerificationMode = selfVerificationMode,
                     roomId = args.roomId,
-                    isMe = args.otherUserId == session.myUserId
+                    isMe = args.otherUserId == session.myUserId,
+                    currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign()
             )
         }
 
@@ -137,6 +142,57 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
                    args: VerificationBottomSheet.VerificationArgs): VerificationBottomSheetViewModel
     }
 
+    fun queryCancel() = withState {
+        if (it.userThinkItsNotHim) {
+            setState {
+                copy(userThinkItsNotHim = false)
+            }
+        } else {
+            setState {
+                copy(userWantsToCancel = true)
+            }
+        }
+    }
+
+    fun confirmCancel() = withState { state ->
+        cancelAllPendingVerifications(state)
+        _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
+    }
+
+    private fun cancelAllPendingVerifications(state: VerificationBottomSheetViewState) {
+        session.cryptoService()
+                .verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
+                    session.cryptoService().verificationService().cancelVerificationRequest(it)
+                }
+        session.cryptoService()
+                .verificationService()
+                .getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
+                ?.cancel(CancelCode.User)
+    }
+
+    fun continueFromCancel() {
+        setState {
+            copy(userWantsToCancel = false)
+        }
+    }
+
+    fun continueFromWasNotMe() {
+        setState {
+            copy(userThinkItsNotHim = false)
+        }
+    }
+
+    fun itWasNotMe() {
+        setState {
+            copy(userThinkItsNotHim = true)
+        }
+    }
+
+    fun goToSettings() = withState { state ->
+        cancelAllPendingVerifications(state)
+        _viewEvents.post(VerificationBottomSheetViewEvents.GoToSettings)
+    }
+
     companion object : MvRxViewModelFactory<VerificationBottomSheetViewModel, VerificationBottomSheetViewState> {
 
         override fun create(viewModelContext: ViewModelContext, state: VerificationBottomSheetViewState): VerificationBottomSheetViewModel? {
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt
new file mode 100644
index 0000000000..1beea4ae9f
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.verification.cancel
+
+import com.airbnb.epoxy.EpoxyController
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.dividerItem
+import im.vector.riotx.core.resources.ColorProvider
+import im.vector.riotx.core.resources.StringProvider
+import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState
+import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
+import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
+import javax.inject.Inject
+
+class VerificationCancelController @Inject constructor(
+        private val stringProvider: StringProvider,
+        private val colorProvider: ColorProvider
+) : EpoxyController() {
+
+    var listener: Listener? = null
+
+    private var viewState: VerificationBottomSheetViewState? = null
+
+    fun update(viewState: VerificationBottomSheetViewState) {
+        this.viewState = viewState
+        requestModelBuild()
+    }
+
+    override fun buildModels() {
+        val state = viewState ?: return
+
+        if (state.isMe) {
+            if (state.currentDeviceCanCrossSign) {
+                bottomSheetVerificationNoticeItem {
+                    id("notice")
+                    notice(stringProvider.getString(R.string.verify_cancel_self_verification_from_trusted))
+                }
+            } else {
+                bottomSheetVerificationNoticeItem {
+                    id("notice")
+                    notice(stringProvider.getString(R.string.verify_cancel_self_verification_from_untrusted))
+                }
+            }
+        } else {
+            bottomSheetVerificationNoticeItem {
+                id("notice")
+                notice(stringProvider.getString(R.string.verify_cancel_self_verification_from_untrusted))
+            }
+        }
+
+        dividerItem {
+            id("sep0")
+        }
+
+        bottomSheetVerificationActionItem {
+            id("cancel")
+            title(stringProvider.getString(R.string.cancel))
+            titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+            iconRes(R.drawable.ic_arrow_right)
+            iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+            listener { listener?.onTapCancel() }
+        }
+
+        dividerItem {
+            id("sep1")
+        }
+
+        bottomSheetVerificationActionItem {
+            id("continue")
+            title(stringProvider.getString(R.string._continue))
+            titleColor(colorProvider.getColor(R.color.riotx_positive_accent))
+            iconRes(R.drawable.ic_arrow_right)
+            iconColor(colorProvider.getColor(R.color.riotx_positive_accent))
+            listener { listener?.onTapContinue() }
+        }
+    }
+
+    interface Listener {
+        fun onTapCancel()
+        fun onTapContinue()
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelFragment.kt
new file mode 100644
index 0000000000..0c5c070156
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelFragment.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.verification.cancel
+
+import android.os.Bundle
+import android.view.View
+import com.airbnb.mvrx.parentFragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.riotx.R
+import im.vector.riotx.core.extensions.cleanup
+import im.vector.riotx.core.extensions.configureWith
+import im.vector.riotx.core.platform.VectorBaseFragment
+import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel
+import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
+import javax.inject.Inject
+
+class VerificationCancelFragment @Inject constructor(
+        val controller: VerificationCancelController
+) : VectorBaseFragment(), VerificationCancelController.Listener {
+
+    private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
+
+    override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        setupRecyclerView()
+    }
+
+    override fun onDestroyView() {
+        bottomSheetVerificationRecyclerView.cleanup()
+        controller.listener = null
+        super.onDestroyView()
+    }
+
+    private fun setupRecyclerView() {
+        bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
+        controller.listener = this
+    }
+
+    override fun invalidate() = withState(viewModel) { state ->
+        controller.update(state)
+    }
+
+    override fun onTapCancel() {
+        viewModel.confirmCancel()
+    }
+
+    override fun onTapContinue() {
+        viewModel.continueFromCancel()
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeController.kt
new file mode 100644
index 0000000000..3978ab8ba5
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeController.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.verification.cancel
+
+import com.airbnb.epoxy.EpoxyController
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.dividerItem
+import im.vector.riotx.core.resources.ColorProvider
+import im.vector.riotx.core.resources.StringProvider
+import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState
+import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
+import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
+import im.vector.riotx.features.html.EventHtmlRenderer
+import javax.inject.Inject
+
+class VerificationNotMeController @Inject constructor(
+        private val stringProvider: StringProvider,
+        private val colorProvider: ColorProvider,
+        private val eventHtmlRenderer: EventHtmlRenderer
+) : EpoxyController() {
+
+    var listener: Listener? = null
+
+    private var viewState: VerificationBottomSheetViewState? = null
+
+    fun update(viewState: VerificationBottomSheetViewState) {
+        this.viewState = viewState
+        requestModelBuild()
+    }
+
+    override fun buildModels() {
+        bottomSheetVerificationNoticeItem {
+            id("notice")
+            notice(eventHtmlRenderer.render(stringProvider.getString(R.string.verify_not_me_self_verification)))
+        }
+
+        dividerItem {
+            id("sep0")
+        }
+
+        bottomSheetVerificationActionItem {
+            id("skip")
+            title(stringProvider.getString(R.string.skip))
+            titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
+            iconRes(R.drawable.ic_arrow_right)
+            iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
+            listener { listener?.onTapSkip() }
+        }
+
+        dividerItem {
+            id("sep1")
+        }
+
+        bottomSheetVerificationActionItem {
+            id("settings")
+            title(stringProvider.getString(R.string.settings))
+            titleColor(colorProvider.getColor(R.color.riotx_positive_accent))
+            iconRes(R.drawable.ic_arrow_right)
+            iconColor(colorProvider.getColor(R.color.riotx_positive_accent))
+            listener { listener?.onTapSettings() }
+        }
+    }
+
+    interface Listener {
+        fun onTapSkip()
+        fun onTapSettings()
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeFragment.kt
new file mode 100644
index 0000000000..b764639078
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeFragment.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.verification.cancel
+
+import android.os.Bundle
+import android.view.View
+import com.airbnb.mvrx.parentFragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.riotx.R
+import im.vector.riotx.core.extensions.cleanup
+import im.vector.riotx.core.extensions.configureWith
+import im.vector.riotx.core.platform.VectorBaseFragment
+import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel
+import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
+import javax.inject.Inject
+
+class VerificationNotMeFragment @Inject constructor(
+        val controller: VerificationNotMeController
+) : VectorBaseFragment(), VerificationNotMeController.Listener {
+
+    private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
+
+    override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        setupRecyclerView()
+    }
+
+    override fun onDestroyView() {
+        bottomSheetVerificationRecyclerView.cleanup()
+        controller.listener = null
+        super.onDestroyView()
+    }
+
+    private fun setupRecyclerView() {
+        bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
+        controller.listener = this
+    }
+
+    override fun invalidate() = withState(viewModel) { state ->
+        controller.update(state)
+    }
+
+    override fun onTapSkip() {
+        viewModel.continueFromWasNotMe()
+    }
+
+    override fun onTapSettings() {
+        viewModel.goToSettings()
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt
index 87bb843291..919869500f 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt
@@ -95,10 +95,27 @@ class VerificationChooseMethodController @Inject constructor(
                 listener { listener?.doVerifyBySas() }
             }
         }
+
+        if (state.isMe && state.canCrossSign) {
+            dividerItem {
+                id("sep_notMe")
+            }
+
+            bottomSheetVerificationActionItem {
+                id("wasnote")
+                title(stringProvider.getString(R.string.verify_new_session_was_not_me))
+                titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+                subTitle(stringProvider.getString(R.string.verify_new_session_compromized))
+                iconRes(R.drawable.ic_arrow_right)
+                iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
+                listener { listener?.onClickOnWasNotMe() }
+            }
+        }
     }
 
     interface Listener {
         fun openCamera()
         fun doVerifyBySas()
+        fun onClickOnWasNotMe()
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
index e0b7f97383..eb32f5b0e3 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
@@ -89,6 +89,10 @@ class VerificationChooseMethodFragment @Inject constructor(
         }
     }
 
+    override fun onClickOnWasNotMe() {
+        sharedViewModel.itWasNotMe()
+    }
+
     private fun doOpenQRCodeScanner() {
         QrCodeScannerActivity.startForResult(this)
     }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt
index c7fdf77123..3c3009ed01 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt
@@ -39,7 +39,9 @@ data class VerificationChooseMethodViewState(
         val otherCanShowQrCode: Boolean = false,
         val otherCanScanQrCode: Boolean = false,
         val qrCodeText: String? = null,
-        val SASModeAvailable: Boolean = false
+        val SASModeAvailable: Boolean = false,
+        val isMe: Boolean = false,
+        val canCrossSign: Boolean = false
 ) : MvRxState
 
 class VerificationChooseMethodViewModel @AssistedInject constructor(
@@ -61,6 +63,10 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
         }
     }
 
+    override fun verificationRequestCreated(pr: PendingVerificationRequest) {
+        verificationRequestUpdated(pr)
+    }
+
     override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
         val pvr = session.cryptoService().verificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId)
 
@@ -103,6 +109,8 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
             val qrCodeVerificationTransaction = verificationService.getExistingTransaction(args.otherUserId, args.verificationId ?: "")
 
             return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
+                    isMe = session.myUserId == pvr?.otherUserId,
+                    canCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
                     transactionId = args.verificationId ?: "",
                     otherCanShowQrCode = pvr?.otherCanShowQrCode().orFalse(),
                     otherCanScanQrCode = pvr?.otherCanScanQrCode().orFalse(),
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt
index 9719651bd4..bd40bec210 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt
@@ -58,6 +58,8 @@ class VerificationConclusionController @Inject constructor(
                     id("image")
                     imageRes(R.drawable.ic_shield_trusted)
                 }
+
+                bottomDone()
             }
             ConclusionState.WARNING -> {
                 bottomSheetVerificationNoticeItem {
@@ -74,10 +76,32 @@ class VerificationConclusionController @Inject constructor(
                     id("warning_notice")
                     notice(eventHtmlRenderer.render(stringProvider.getString(R.string.verification_conclusion_compromised)))
                 }
-            }
-            else                    -> Unit
-        }
 
+                bottomDone()
+            }
+            ConclusionState.CANCELLED -> {
+                bottomSheetVerificationNoticeItem {
+                    id("notice_cancelled")
+                    notice(stringProvider.getString(R.string.verify_cancelled_notice))
+                }
+
+                dividerItem {
+                    id("sep0")
+                }
+
+                bottomSheetVerificationActionItem {
+                    id("got_it")
+                    title(stringProvider.getString(R.string.sas_got_it))
+                    titleColor(colorProvider.getColor(R.color.riotx_accent))
+                    iconRes(R.drawable.ic_arrow_right)
+                    iconColor(colorProvider.getColor(R.color.riotx_accent))
+                    listener { listener?.onButtonTapped() }
+                }
+            }
+        }
+    }
+
+    private fun bottomDone() {
         dividerItem {
             id("sep0")
         }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt
index 854809084e..7405722c04 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt
@@ -66,12 +66,7 @@ class VerificationConclusionFragment @Inject constructor(
     }
 
     override fun invalidate() = withState(viewModel) { state ->
-        if (state.conclusionState == ConclusionState.CANCELLED) {
-            // Just dismiss in this case
-            sharedViewModel.handle(VerificationAction.GotItConclusion)
-        } else {
-            controller.update(state)
-        }
+        controller.update(state)
     }
 
     override fun onButtonTapped() {
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt
index 05ed2f1799..60d0d86aeb 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt
@@ -84,11 +84,16 @@ class VerificationRequestController @Inject constructor(
                 listener { listener?.onClickDismiss() }
             }
         } else {
-            val styledText = matrixItem.let {
-                stringProvider.getString(R.string.verification_request_notice, it.id)
-                        .toSpannable()
-                        .colorizeMatchingText(it.id, colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color))
-            }
+            val styledText =
+                    if (state.isMe) {
+                        stringProvider.getString(R.string.verify_new_session_notice)
+                    } else {
+                        matrixItem.let {
+                            stringProvider.getString(R.string.verification_request_notice, it.id)
+                                    .toSpannable()
+                                    .colorizeMatchingText(it.id, colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color))
+                        }
+                    }
 
             bottomSheetVerificationNoticeItem {
                 id("notice")
@@ -119,18 +124,42 @@ class VerificationRequestController @Inject constructor(
                 }
                 is Success       -> {
                     if (!pr.invoke().isReady) {
-                        bottomSheetVerificationWaitingItem {
-                            id("waiting")
-                            title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName()))
+                        if (state.isMe) {
+                            bottomSheetVerificationWaitingItem {
+                                id("waiting")
+                                title(stringProvider.getString(R.string.verification_request_waiting))
+                            }
+                        } else {
+                            bottomSheetVerificationWaitingItem {
+                                id("waiting")
+                                title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName()))
+                            }
                         }
                     }
                 }
             }
         }
+
+        if (state.isMe && state.currentDeviceCanCrossSign) {
+            dividerItem {
+                id("sep_notMe")
+            }
+
+            bottomSheetVerificationActionItem {
+                id("wasnote")
+                title(stringProvider.getString(R.string.verify_new_session_was_not_me))
+                titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+                subTitle(stringProvider.getString(R.string.verify_new_session_compromized))
+                iconRes(R.drawable.ic_arrow_right)
+                iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
+                listener { listener?.onClickOnWasNotMe() }
+            }
+        }
     }
 
     interface Listener {
         fun onClickOnVerificationStart()
+        fun onClickOnWasNotMe()
         fun onClickRecoverFromPassphrase()
         fun onClickDismiss()
     }
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt
index 64000d07a1..b6c3659988 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt
@@ -69,4 +69,8 @@ class VerificationRequestFragment @Inject constructor(
     override fun onClickDismiss() {
         viewModel.handle(VerificationAction.SkipVerification)
     }
+
+    override fun onClickOnWasNotMe() {
+        viewModel.itWasNotMe()
+    }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt
index ac4d29dd96..6d85dd8a3e 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt
@@ -96,8 +96,8 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
     // PRIVATE API *********************************************************************************
 
     private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
-        val resolvedUrl = activeSessionHolder.getActiveSession().contentUrlResolver()
-                .resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
+        val resolvedUrl = activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
+                ?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
 
         return glideRequest
                 .load(resolvedUrl)
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 dfe80de9de..0fa3e5416d 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
@@ -41,6 +41,7 @@ import im.vector.riotx.core.platform.VectorBaseActivity
 import im.vector.riotx.core.pushers.PushersManager
 import im.vector.riotx.features.disclaimer.showDisclaimerDialog
 import im.vector.riotx.features.notifications.NotificationDrawerManager
+import im.vector.riotx.features.popup.DefaultVectorAlert
 import im.vector.riotx.features.popup.PopupAlertManager
 import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
 import im.vector.riotx.features.settings.VectorPreferences
@@ -60,6 +61,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
     @Inject lateinit var pushManager: PushersManager
     @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
     @Inject lateinit var vectorPreferences: VectorPreferences
+    @Inject lateinit var popupAlertManager: PopupAlertManager
 
     private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
         override fun onDrawerStateChanged(newState: Int) {
@@ -149,8 +151,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
         if (crossSigningEnabledOnAccount && myCrossSigningKeys?.isTrusted() == false) {
             // We need to ask
             sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true
-            PopupAlertManager.postVectorAlert(
-                    PopupAlertManager.VectorAlert(
+            popupAlertManager.postVectorAlert(
+                    DefaultVectorAlert(
                             uid = "completeSecurity",
                             title = getString(R.string.new_signin),
                             description = getString(R.string.complete_security),
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 85f14e99a8..47338f6335 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
@@ -1,3 +1,4 @@
+
 /*
  * Copyright 2019 New Vector Ltd
  *
@@ -19,8 +20,10 @@ package im.vector.riotx.features.home
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
+import androidx.core.content.ContextCompat
 import androidx.core.view.forEachIndexed
 import androidx.lifecycle.Observer
+import com.airbnb.mvrx.activityViewModel
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
 import com.google.android.material.bottomnavigation.BottomNavigationItemView
@@ -32,11 +35,14 @@ import im.vector.riotx.R
 import im.vector.riotx.core.extensions.commitTransactionNow
 import im.vector.riotx.core.glide.GlideApp
 import im.vector.riotx.core.platform.ToolbarConfigurable
+import im.vector.riotx.core.platform.VectorBaseActivity
 import im.vector.riotx.core.platform.VectorBaseFragment
 import im.vector.riotx.core.ui.views.KeysBackupBanner
 import im.vector.riotx.features.home.room.list.RoomListFragment
 import im.vector.riotx.features.home.room.list.RoomListParams
 import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
+import im.vector.riotx.features.popup.PopupAlertManager
+import im.vector.riotx.features.popup.VerificationVectorAlert
 import im.vector.riotx.features.workers.signout.SignOutViewModel
 import kotlinx.android.synthetic.main.fragment_home_detail.*
 import timber.log.Timber
@@ -48,12 +54,15 @@ private const val INDEX_ROOMS = 2
 
 class HomeDetailFragment @Inject constructor(
         val homeDetailViewModelFactory: HomeDetailViewModel.Factory,
-        private val avatarRenderer: AvatarRenderer
+        private val avatarRenderer: AvatarRenderer,
+        private val alertManager: PopupAlertManager
 ) : VectorBaseFragment(), KeysBackupBanner.Delegate {
 
     private val unreadCounterBadgeViews = arrayListOf<UnreadCounterBadgeView>()
 
     private val viewModel: HomeDetailViewModel by fragmentViewModel()
+    private val unknownDeviceDetectorSharedViewModel : UnknownDeviceDetectorSharedViewModel by activityViewModel()
+
     private lateinit var sharedActionViewModel: HomeSharedActionViewModel
 
     override fun getLayoutResId() = R.layout.fragment_home_detail
@@ -77,6 +86,38 @@ class HomeDetailFragment @Inject constructor(
         viewModel.selectSubscribe(this, HomeDetailViewState::displayMode) { displayMode ->
             switchDisplayMode(displayMode)
         }
+
+        unknownDeviceDetectorSharedViewModel.subscribe {
+            it.unknownSessions.invoke()?.let { unknownDevices ->
+                Timber.v("## Detector - ${unknownDevices.size} Unknown sessions")
+                unknownDevices.forEachIndexed { index, deviceInfo ->
+                    Timber.v("## Detector - #$index deviceId:${deviceInfo.second.deviceId} lastSeenTs:${deviceInfo.second.lastSeenTs}")
+                }
+                val uid = "Newest_Device"
+                alertManager.cancelAlert(uid)
+                if (it.canCrossSign && unknownDevices.isNotEmpty()) {
+                    val newest = unknownDevices.first().second
+                    val user = unknownDevices.first().first
+                    alertManager.postVectorAlert(
+                            VerificationVectorAlert(
+                                    uid = uid,
+                                    title = getString(R.string.new_session),
+                                    description = getString(R.string.new_session_review),
+                                    iconId = R.drawable.ic_shield_warning
+                            ).apply {
+                                matrixItem = user
+                                colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent)
+                                contentAction = Runnable {
+                                    (weakCurrentActivity?.get() as? VectorBaseActivity)
+                                            ?.navigator
+                                            ?.requestSessionVerification(requireContext(), newest.deviceId ?: "")
+                                }
+                                dismissedAction = Runnable {}
+                            }
+                    )
+                }
+            }
+        }
     }
 
     private fun onGroupChange(groupSummary: GroupSummary?) {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt
new file mode 100644
index 0000000000..180a989858
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.home
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.ViewModelContext
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.util.MatrixItem
+import im.vector.matrix.android.api.util.NoOpCancellable
+import im.vector.matrix.android.api.util.toMatrixItem
+import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
+import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
+import im.vector.matrix.rx.rx
+import im.vector.matrix.rx.singleBuilder
+import im.vector.riotx.core.di.HasScreenInjector
+import im.vector.riotx.core.platform.EmptyAction
+import im.vector.riotx.core.platform.EmptyViewEvents
+import im.vector.riotx.core.platform.VectorViewModel
+import io.reactivex.android.schedulers.AndroidSchedulers
+
+data class UnknownDevicesState(
+        val unknownSessions: Async<List<Pair<MatrixItem?, DeviceInfo>>> = Uninitialized,
+        val canCrossSign: Boolean = false
+) : MvRxState
+
+class UnknownDeviceDetectorSharedViewModel(session: Session, initialState: UnknownDevicesState)
+    : VectorViewModel<UnknownDevicesState, EmptyAction, EmptyViewEvents>(initialState) {
+
+    init {
+        session.rx().liveUserCryptoDevices(session.myUserId)
+                .observeOn(AndroidSchedulers.mainThread())
+                .switchMap { deviceList ->
+                    //                    Timber.v("## Detector - ============================")
+//                    Timber.v("## Detector - Crypto device update  ${deviceList.map { "${it.deviceId} : ${it.isVerified}" }}")
+                    singleBuilder<DevicesListResponse> {
+                        session.cryptoService().getDevicesList(it)
+                        NoOpCancellable
+                    }.map { resp ->
+                        //                        Timber.v("## Detector - Device Infos  ${resp.devices?.map { "${it.deviceId} : lastSeen:${it.lastSeenTs}" }}")
+                        resp.devices?.filter { info ->
+                            deviceList.firstOrNull { info.deviceId == it.deviceId }?.let {
+                                !it.isVerified
+                            } ?: false
+                        }?.sortedByDescending { it.lastSeenTs }
+                                ?.map {
+                                    session.getUser(it.user_id ?: "")?.toMatrixItem() to it
+                                } ?: emptyList()
+                    }
+                            .toObservable()
+                }
+                .execute { async ->
+                    copy(unknownSessions = async)
+                }
+
+        session.rx().liveCrossSigningInfo(session.myUserId)
+                .execute {
+                    copy(canCrossSign = session.cryptoService().crossSigningService().canCrossSign())
+                }
+    }
+
+    override fun handle(action: EmptyAction) {}
+
+    companion object : MvRxViewModelFactory<UnknownDeviceDetectorSharedViewModel, UnknownDevicesState> {
+        override fun create(viewModelContext: ViewModelContext, state: UnknownDevicesState): UnknownDeviceDetectorSharedViewModel? {
+            val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
+            return UnknownDeviceDetectorSharedViewModel(session, state)
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index e748478e6a..f58d7be718 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -250,7 +250,7 @@ class RoomDetailFragment @Inject constructor(
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
-        attachmentsHelper = AttachmentsHelper.create(this, this).register()
+        attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
         keyboardStateUtils = KeyboardStateUtils(requireActivity())
         setupToolbar(roomToolbar)
         setupRecyclerView()
@@ -290,9 +290,9 @@ class RoomDetailFragment @Inject constructor(
 
         roomDetailViewModel.observeViewEvents {
             when (it) {
-                is RoomDetailViewEvents.Failure             -> showErrorInSnackbar(it.throwable)
-                is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
-                is RoomDetailViewEvents.ActionSuccess       -> displayRoomDetailActionSuccess(it)
+                is RoomDetailViewEvents.Failure                -> showErrorInSnackbar(it.throwable)
+                is RoomDetailViewEvents.OnNewTimelineEvents    -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
+                is RoomDetailViewEvents.ActionSuccess          -> displayRoomDetailActionSuccess(it)
                 is RoomDetailViewEvents.ActionFailure          -> displayRoomDetailActionFailure(it)
                 is RoomDetailViewEvents.ShowMessage            -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
                 is RoomDetailViewEvents.NavigateToEvent        -> navigateToEvent(it)
@@ -665,7 +665,7 @@ class RoomDetailFragment @Inject constructor(
     private fun sendUri(uri: Uri): Boolean {
         roomDetailViewModel.preventAttachmentPreview = true
         val shareIntent = Intent(Intent.ACTION_SEND, uri)
-        val isHandled = attachmentsHelper.handleShareIntent(shareIntent)
+        val isHandled = attachmentsHelper.handleShareIntent(requireContext(), shareIntent)
         if (!isHandled) {
             roomDetailViewModel.preventAttachmentPreview = false
             Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show()
@@ -1350,11 +1350,11 @@ class RoomDetailFragment @Inject constructor(
 
     private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
         when (type) {
-            AttachmentTypeSelectorView.Type.CAMERA  -> attachmentsHelper.openCamera()
-            AttachmentTypeSelectorView.Type.FILE    -> attachmentsHelper.selectFile()
-            AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery()
-            AttachmentTypeSelectorView.Type.AUDIO   -> attachmentsHelper.selectAudio()
-            AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact()
+            AttachmentTypeSelectorView.Type.CAMERA  -> attachmentsHelper.openCamera(this)
+            AttachmentTypeSelectorView.Type.FILE    -> attachmentsHelper.selectFile(this)
+            AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this)
+            AttachmentTypeSelectorView.Type.AUDIO   -> attachmentsHelper.selectAudio(this)
+            AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
             AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
         }.exhaustive
     }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index 2ad90f073a..cef172da73 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -610,7 +610,7 @@ class RoomDetailViewModel @AssistedInject constructor(
             when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
                 null -> room.sendMedias(attachments, action.compressBeforeSending, emptySet())
                 else -> _viewEvents.post(RoomDetailViewEvents.FileTooBigError(
-                        tooBigFile.name ?: tooBigFile.path,
+                        tooBigFile.name ?: tooBigFile.queryUri.toString(),
                         tooBigFile.size,
                         maxUploadFileSize
                 ))
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt
index aac48202c4..f7c9ff04e4 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt
@@ -58,7 +58,7 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment(), ViewReac
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
         sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
-        recyclerView.configureWith(epoxyController, hasFixedSize = false)
+        recyclerView.configureWith(epoxyController, hasFixedSize = false, showDivider = true)
         bottomSheetTitle.text = context?.getString(R.string.reactions)
         epoxyController.listener = this
     }
diff --git a/vector/src/main/java/im/vector/riotx/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/riotx/features/lifecycle/VectorActivityLifecycleCallbacks.kt
index bf932a74be..668adc7edd 100644
--- a/vector/src/main/java/im/vector/riotx/features/lifecycle/VectorActivityLifecycleCallbacks.kt
+++ b/vector/src/main/java/im/vector/riotx/features/lifecycle/VectorActivityLifecycleCallbacks.kt
@@ -20,14 +20,13 @@ import android.app.Activity
 import android.app.Application
 import android.os.Bundle
 import im.vector.riotx.features.popup.PopupAlertManager
-import javax.inject.Inject
 
-class VectorActivityLifecycleCallbacks @Inject constructor() : Application.ActivityLifecycleCallbacks {
+class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager: PopupAlertManager) : Application.ActivityLifecycleCallbacks {
     override fun onActivityPaused(activity: Activity) {
     }
 
     override fun onActivityResumed(activity: Activity) {
-        PopupAlertManager.onNewActivityDisplayed(activity)
+        popupAlertManager.onNewActivityDisplayed(activity)
     }
 
     override fun onActivityStarted(activity: Activity) {
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 a080cabf1b..2e91090ec4 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
@@ -83,12 +83,12 @@ class DefaultNavigator @Inject constructor(
         }
     }
 
-    override fun requestSessionVerification(context: Context) {
+    override fun requestSessionVerification(context: Context, otherSessionId: String) {
         val session = sessionHolder.getSafeActiveSession() ?: return
         val pr = session.cryptoService().verificationService().requestKeyVerification(
                 supportedVerificationMethodsProvider.provide(),
                 session.myUserId,
-                session.cryptoService().getUserDevices(session.myUserId).map { it.deviceId }
+                listOf(otherSessionId)
         )
         if (context is VectorBaseActivity) {
             VerificationBottomSheet.withArgs(
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 fcb3d7bb44..65ef08dd05 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
@@ -30,7 +30,7 @@ interface Navigator {
 
     fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String)
 
-    fun requestSessionVerification(context: Context)
+    fun requestSessionVerification(context: Context, otherSessionId: String)
 
     fun waitSessionVerification(context: Context)
 
diff --git a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt
index aa198eba02..1876d83617 100644
--- a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt
+++ b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt
@@ -20,20 +20,23 @@ import android.os.Build
 import android.os.Handler
 import android.os.Looper
 import android.view.View
-import androidx.annotation.ColorInt
-import androidx.annotation.ColorRes
-import androidx.annotation.DrawableRes
+import android.widget.ImageView
 import com.tapadoo.alerter.Alerter
 import com.tapadoo.alerter.OnHideAlertListener
+import dagger.Lazy
 import im.vector.riotx.R
+import im.vector.riotx.features.home.AvatarRenderer
 import timber.log.Timber
 import java.lang.ref.WeakReference
+import javax.inject.Inject
+import javax.inject.Singleton
 
 /**
  * Responsible of displaying important popup alerts on top of the screen.
  * Alerts are stacked and will be displayed sequentially
  */
-object PopupAlertManager {
+@Singleton
+class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<AvatarRenderer>) {
 
     private var weakCurrentActivity: WeakReference<Activity>? = null
     private var currentAlerter: VectorAlert? = null
@@ -160,9 +163,19 @@ object PopupAlertManager {
         clearLightStatusBar()
 
         alert.weakCurrentActivity = WeakReference(activity)
-        Alerter.create(activity)
-                .setTitle(alert.title)
+        val alerter = if (alert is VerificationVectorAlert) Alerter.create(activity, R.layout.alerter_verification_layout)
+        else Alerter.create(activity)
+
+        alerter.setTitle(alert.title)
                 .setText(alert.description)
+                .also { al ->
+                    if (alert is VerificationVectorAlert) {
+                        val tvCustomView = al.getLayoutContainer()
+                        tvCustomView?.findViewById<ImageView>(R.id.ivUserAvatar)?.let { imageView ->
+                            alert.matrixItem?.let { avatarRenderer.get().render(it, imageView) }
+                        }
+                    }
+                }
                 .apply {
                     if (!animate) {
                         setEnterAnimation(R.anim.anim_alerter_no_anim)
@@ -226,37 +239,4 @@ object PopupAlertManager {
             displayNextIfPossible()
         }, 500)
     }
-
-    /**
-     * Dataclass to describe an important alert with actions.
-     */
-    data class VectorAlert(val uid: String,
-                           val title: String,
-                           val description: String,
-                           @DrawableRes val iconId: Int?,
-                           val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null) {
-
-        data class Button(val title: String, val action: Runnable, val autoClose: Boolean)
-
-        // will be set by manager, and accessible by actions at runtime
-        var weakCurrentActivity: WeakReference<Activity>? = null
-
-        val actions = ArrayList<Button>()
-
-        var contentAction: Runnable? = null
-        var dismissedAction: Runnable? = null
-
-        /** If this timestamp is after current time, this alert will be skipped */
-        var expirationTimestamp: Long? = null
-
-        fun addButton(title: String, action: Runnable, autoClose: Boolean = true) {
-            actions.add(Button(title, action, autoClose))
-        }
-
-        @ColorRes
-        var colorRes: Int? = null
-
-        @ColorInt
-        var colorInt: Int? = null
-    }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/popup/VectorAlert.kt b/vector/src/main/java/im/vector/riotx/features/popup/VectorAlert.kt
new file mode 100644
index 0000000000..259df1c7e0
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/popup/VectorAlert.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.popup
+
+import android.app.Activity
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import im.vector.matrix.android.api.util.MatrixItem
+import java.lang.ref.WeakReference
+
+interface VectorAlert {
+    val uid: String
+    val title: String
+    val description: String
+    val iconId: Int?
+    val shouldBeDisplayedIn: ((Activity) -> Boolean)?
+
+    data class Button(val title: String, val action: Runnable, val autoClose: Boolean)
+
+    // will be set by manager, and accessible by actions at runtime
+    var weakCurrentActivity: WeakReference<Activity>?
+
+    val actions: MutableList<Button>
+
+    var contentAction: Runnable?
+    var dismissedAction: Runnable?
+
+    /** If this timestamp is after current time, this alert will be skipped */
+    var expirationTimestamp: Long?
+
+    fun addButton(title: String, action: Runnable, autoClose: Boolean = true) {
+        actions.add(Button(title, action, autoClose))
+    }
+
+    var colorRes: Int?
+
+    var colorInt: Int?
+}
+
+/**
+ * Dataclass to describe an important alert with actions.
+ */
+open class DefaultVectorAlert(override val uid: String,
+                              override val title: String,
+                              override val description: String,
+                              @DrawableRes override val iconId: Int?,
+                              override val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null) : VectorAlert {
+
+    // will be set by manager, and accessible by actions at runtime
+    override var weakCurrentActivity: WeakReference<Activity>? = null
+
+    override val actions = ArrayList<VectorAlert.Button>()
+
+    override var contentAction: Runnable? = null
+    override var dismissedAction: Runnable? = null
+
+    /** If this timestamp is after current time, this alert will be skipped */
+    override var expirationTimestamp: Long? = null
+
+    override fun addButton(title: String, action: Runnable, autoClose: Boolean) {
+        actions.add(VectorAlert.Button(title, action, autoClose))
+    }
+
+    @ColorRes
+    override var colorRes: Int? = null
+
+    @ColorInt
+    override var colorInt: Int? = null
+}
+
+class VerificationVectorAlert(uid: String,
+                              title: String,
+                              override val description: String,
+                              @DrawableRes override val iconId: Int?,
+                              override val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null
+) : DefaultVectorAlert(
+        uid, title, description, iconId, shouldBeDisplayedIn
+) {
+    var matrixItem: MatrixItem? = null
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt
index 909d40a74c..5db14fdbd2 100755
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt
@@ -57,6 +57,8 @@ class VectorSettingsActivity : VectorBaseActivity(),
             when (intent.getIntExtra(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOT)) {
                 EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS ->
                     replaceFragment(R.id.vector_settings_page, VectorSettingsAdvancedSettingsFragment::class.java, null, FRAGMENT_TAG)
+                EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY ->
+                    replaceFragment(R.id.vector_settings_page, VectorSettingsSecurityPrivacyFragment::class.java, null, FRAGMENT_TAG)
                 else                                  ->
                     replaceFragment(R.id.vector_settings_page, VectorSettingsRootFragment::class.java, null, FRAGMENT_TAG)
             }
@@ -116,6 +118,7 @@ class VectorSettingsActivity : VectorBaseActivity(),
 
         const val EXTRA_DIRECT_ACCESS_ROOT = 0
         const val EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS = 1
+        const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY = 2
 
         private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment"
     }
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 cf74e83b1f..e33b12d19a 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
@@ -78,6 +78,17 @@ class CrossSigningEpoxyController @Inject constructor(
                         interactionListener?.onResetCrossSigningKeys()
                     }
                 }
+
+                bottomSheetVerificationActionItem {
+                    id("verify")
+                    title(stringProvider.getString(R.string.complete_security))
+                    titleColor(colorProvider.getColor(R.color.riotx_positive_accent))
+                    iconRes(R.drawable.ic_arrow_right)
+                    iconColor(colorProvider.getColor(R.color.riotx_positive_accent))
+                    listener {
+                        interactionListener?.verifySession()
+                    }
+                }
             }
         } else if (data.xSigningIsEnableInAccount) {
             genericItem {
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt
index 9863134cb7..8e18472499 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt
@@ -62,7 +62,7 @@ class DeviceVerificationInfoBottomSheet : VectorBaseBottomSheetDialogFragment(),
         super.onActivityCreated(savedInstanceState)
         recyclerView.configureWith(
                 epoxyController,
-                showDivider = true,
+                showDivider = false,
                 hasFixedSize = false)
         epoxyController.callback = this
         bottomSheetTitle.isVisible = false
diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt
index 74821ab2fe..aa665b5653 100644
--- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt
@@ -72,18 +72,18 @@ class IncomingShareFragment @Inject constructor(
         super.onViewCreated(view, savedInstanceState)
         setupRecyclerView()
         setupToolbar(incomingShareToolbar)
-        attachmentsHelper = AttachmentsHelper.create(this, this).register()
+        attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
 
         val intent = vectorBaseActivity.intent
         val isShareManaged = when (intent?.action) {
             Intent.ACTION_SEND          -> {
-                var isShareManaged = attachmentsHelper.handleShareIntent(intent)
+                var isShareManaged = attachmentsHelper.handleShareIntent(requireContext(), intent)
                 if (!isShareManaged) {
                     isShareManaged = handleTextShare(intent)
                 }
                 isShareManaged
             }
-            Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(intent)
+            Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(requireContext(), intent)
             else                        -> false
         }
 
diff --git a/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
index 40a14b3e6f..1f835164db 100644
--- a/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
@@ -62,10 +62,10 @@ object ThemeUtils {
      */
     fun setApplicationTheme(context: Context, aTheme: String) {
         when (aTheme) {
-            THEME_DARK_VALUE -> context.setTheme(R.style.AppTheme_Dark)
-            THEME_BLACK_VALUE -> context.setTheme(R.style.AppTheme_Black)
+            THEME_DARK_VALUE   -> context.setTheme(R.style.AppTheme_Dark)
+            THEME_BLACK_VALUE  -> context.setTheme(R.style.AppTheme_Black)
             THEME_STATUS_VALUE -> context.setTheme(R.style.AppTheme_Status)
-            else -> context.setTheme(R.style.AppTheme_Light)
+            else               -> context.setTheme(R.style.AppTheme_Light)
         }
 
         // Clear the cache
@@ -170,6 +170,7 @@ object ThemeUtils {
                     R.drawable.bg_search_edit_text_light     -> R.drawable.bg_search_edit_text_dark
                     R.drawable.bg_unread_notification_light  -> R.drawable.bg_unread_notification_dark
                     R.drawable.vector_label_background_light -> R.drawable.vector_label_background_dark
+                    R.drawable.divider_horizontal_light      -> R.drawable.divider_horizontal_dark
                     else                                     -> {
                         Timber.w("Warning, missing case for wanted drawable in dark theme")
                         resourceId
@@ -181,6 +182,7 @@ object ThemeUtils {
                     R.drawable.bg_search_edit_text_light     -> R.drawable.bg_search_edit_text_black
                     R.drawable.bg_unread_notification_light  -> R.drawable.bg_unread_notification_black
                     R.drawable.vector_label_background_light -> R.drawable.vector_label_background_black
+                    R.drawable.divider_horizontal_light      -> R.drawable.divider_horizontal_black
                     else                                     -> {
                         Timber.w("Warning, missing case for wanted drawable in black theme")
                         resourceId
diff --git a/vector/src/main/res/drawable/divider_horizontal_black.xml b/vector/src/main/res/drawable/divider_horizontal_black.xml
new file mode 100644
index 0000000000..43a68bbe2d
--- /dev/null
+++ b/vector/src/main/res/drawable/divider_horizontal_black.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size android:height="1dp" />
+    <solid android:color="@color/riotx_header_panel_border_mobile_black" />
+</shape>
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/divider_horizontal_dark.xml b/vector/src/main/res/drawable/divider_horizontal_dark.xml
new file mode 100644
index 0000000000..24a9307799
--- /dev/null
+++ b/vector/src/main/res/drawable/divider_horizontal_dark.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size android:height="1dp" />
+    <solid android:color="@color/riotx_header_panel_border_mobile_dark" />
+</shape>
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/divider_horizontal_light.xml b/vector/src/main/res/drawable/divider_horizontal_light.xml
new file mode 100644
index 0000000000..4b215ecaa4
--- /dev/null
+++ b/vector/src/main/res/drawable/divider_horizontal_light.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size android:height="1dp" />
+    <solid android:color="@color/riotx_header_panel_border_mobile_light" />
+</shape>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/alerter_verification_layout.xml b/vector/src/main/res/layout/alerter_verification_layout.xml
new file mode 100644
index 0000000000..b06883b056
--- /dev/null
+++ b/vector/src/main/res/layout/alerter_verification_layout.xml
@@ -0,0 +1,92 @@
+<?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="match_parent"
+    android:clipToPadding="false"
+    tools:background="@android:color/darker_gray"
+    tools:foreground="?android:attr/selectableItemBackground"
+    tools:style="@style/AlertStyle">
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/ivUserAvatar"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/alerter_texts"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:src="@tools:sample/avatars" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/ivIcon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        app:layout_constraintCircle="@+id/ivUserAvatar"
+        app:layout_constraintCircleAngle="135"
+        app:layout_constraintCircleRadius="20dp"
+        tools:ignore="MissingConstraints"
+        android:src="@drawable/ic_shield_warning"
+        tools:visibility="visible" />
+
+    <LinearLayout
+        android:id="@+id/alerter_texts"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:orientation="vertical"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivUserAvatar"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tvTitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/alerter_padding_half"
+            android:layout_marginEnd="@dimen/alerter_padding_half"
+            android:paddingStart="@dimen/alerter_padding_small"
+            android:paddingLeft="@dimen/alerter_padding_small"
+            android:paddingEnd="@dimen/alerter_padding_small"
+            android:textAppearance="@style/AlertTextAppearance.Title"
+            android:visibility="gone"
+            tools:text="Title"
+            tools:visibility="visible" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tvText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/alerter_padding_half"
+            android:layout_marginEnd="@dimen/alerter_padding_half"
+            android:paddingStart="@dimen/alerter_padding_small"
+            android:paddingLeft="@dimen/alerter_padding_small"
+            android:paddingTop="@dimen/alerter_padding_small"
+            android:paddingEnd="@dimen/alerter_padding_small"
+            android:paddingBottom="@dimen/alerter_padding_small"
+            android:textAppearance="@style/AlertTextAppearance.Text"
+            android:visibility="gone"
+            tools:text="Text"
+            tools:visibility="visible" />
+
+    </LinearLayout>
+
+    <!--    <FrameLayout-->
+    <!--        android:id="@+id/flRightIconContainer"-->
+    <!--        android:layout_width="wrap_content"-->
+    <!--        android:layout_height="wrap_content"-->
+    <!--        android:layout_gravity="center_vertical">-->
+
+    <!--        <androidx.appcompat.widget.AppCompatImageView-->
+    <!--            android:id="@+id/ivRightIcon"-->
+    <!--            android:layout_width="@dimen/alerter_alert_icn_size"-->
+    <!--            android:layout_height="@dimen/alerter_alert_icn_size"-->
+    <!--            android:maxWidth="@dimen/alerter_alert_icn_size"-->
+    <!--            android:maxHeight="@dimen/alerter_alert_icn_size"-->
+    <!--            android:visibility="gone"-->
+    <!--            app:srcCompat="@drawable/alerter_ic_notifications"-->
+    <!--            app:tint="@color/alert_default_icon_color"-->
+    <!--            tools:visibility="visible" />-->
+    <!--    </FrameLayout>-->
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml b/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml
index 80d877ac2d..6a4b6d3eaa 100644
--- a/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml
+++ b/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml
@@ -3,6 +3,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:paddingTop="8dp"
     android:orientation="vertical">
 
     <TextView
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 76bcfe7290..9e07f43cb8 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -287,7 +287,7 @@
     <string name="login_error_no_homeserver_found">This is not a valid Matrix server address</string>
     <string name="login_error_homeserver_not_found">Cannot reach a homeserver at this URL, please check it</string>
     <string name="login_error_ssl_handshake">Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect</string>
-    <string name="login_mobile_device">Mobile</string>
+    <string name="login_mobile_device">RiotX Android</string>
 
     <string name="login_error_forbidden">Invalid username/password</string>
     <string name="login_error_unknown_token">The access token specified was not recognised</string>
diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
index 45fc3a3781..de28f316fa 100644
--- a/vector/src/main/res/values/strings_riotX.xml
+++ b/vector/src/main/res/values/strings_riotX.xml
@@ -14,6 +14,23 @@
 
     <string name="refresh">Refresh</string>
 
+
+    <string name="new_session">New Session</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>
+    <string name="verify_new_session_compromized">Your account may be compromised</string>
+
+    <string name="verify_cancel_self_verification_from_untrusted">If you cancel, you won’t be able to read encrypted messages on this device, and other users won’t trust it</string>
+    <string name="verify_cancel_self_verification_from_trusted">If you cancel, you won’t be able to read encrypted messages on your new device, and other users won’t trust it</string>
+
+    <string name="verify_not_me_self_verification">
+        One of the following may be compromised:\n\n- Your password\n- Your homeserver\n- This device, or the other device\n- The internet connection either device is using\n\nWe recommend you change your password &amp; recovery key in Settings immediately.
+    </string>
+
+    <string name="verify_cancelled_notice">Verify your devices from Settings.</string>
+    <string name="verification_cancelled">Verification Cancelled</string>
+
     <!-- END Strings added by Valere -->
 
 
diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml
index 9bfdcc8231..09775d4d41 100644
--- a/vector/src/main/res/values/theme_dark.xml
+++ b/vector/src/main/res/values/theme_dark.xml
@@ -36,8 +36,6 @@
         <!-- Drawables -->
         <item name="riotx_highlighted_message_background">@drawable/highlighted_message_background_dark</item>
 
-        <item name="android:listDivider">@color/riotx_header_panel_border_mobile_dark</item>
-
         <!-- Material color: Note: this block should be the same in all theme because it references only common colors and ?riotx attributes -->
         <item name="colorPrimary">@color/riotx_accent</item>
         <item name="colorPrimaryVariant">@color/primary_color_dark_light</item>
diff --git a/vector/src/main/res/values/theme_light.xml b/vector/src/main/res/values/theme_light.xml
index 3a479c9368..c63dfa9057 100644
--- a/vector/src/main/res/values/theme_light.xml
+++ b/vector/src/main/res/values/theme_light.xml
@@ -35,8 +35,6 @@
         <!-- Drawables -->
         <item name="riotx_highlighted_message_background">@drawable/highlighted_message_background_light</item>
 
-        <item name="android:listDivider">@color/riotx_header_panel_border_mobile_light</item>
-
         <!-- Material color: Note: this block should be the same in all theme because it references only common colors and ?riotx attributes -->
         <item name="colorPrimary">@color/riotx_accent</item>
         <!--item name="colorPrimaryVariant">@color/primary_color_dark_light</item-->