forked from GitHub-Mirror/riotX-android
Merge pull request #182 from vector-im/feature/cryptoKeys
Crypto: Import/export room keys (the old way)
This commit is contained in:
commit
0b6b95110f
@ -1,30 +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.matrix.android.api.util
|
|
||||||
|
|
||||||
import arrow.core.*
|
|
||||||
|
|
||||||
inline fun <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix()
|
|
||||||
.fold(
|
|
||||||
{
|
|
||||||
f(it)
|
|
||||||
Failure(it)
|
|
||||||
},
|
|
||||||
{ Success(it) }
|
|
||||||
)
|
|
@ -26,6 +26,7 @@ import im.vector.matrix.android.api.auth.data.Credentials
|
|||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||||
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.DefaultSession
|
import im.vector.matrix.android.internal.session.DefaultSession
|
||||||
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
||||||
@ -57,7 +58,7 @@ internal class DefaultAuthenticator(private val retrofitBuilder: Retrofit.Builde
|
|||||||
|
|
||||||
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password)
|
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password)
|
||||||
sessionOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
|
sessionOrFailure.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
return CancelableCoroutine(job)
|
return CancelableCoroutine(job)
|
||||||
|
|
||||||
|
@ -1,61 +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.matrix.android.internal.crypto
|
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import android.os.Looper
|
|
||||||
|
|
||||||
private const val THREAD_CRYPTO_NAME = "Crypto_Thread"
|
|
||||||
|
|
||||||
// TODO Remove and replace by Task
|
|
||||||
internal object CryptoAsyncHelper {
|
|
||||||
|
|
||||||
private var uiHandler: Handler? = null
|
|
||||||
private var cryptoBackgroundHandler: Handler? = null
|
|
||||||
|
|
||||||
fun getUiHandler(): Handler {
|
|
||||||
return uiHandler
|
|
||||||
?: Handler(Looper.getMainLooper())
|
|
||||||
.also { uiHandler = it }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun getDecryptBackgroundHandler(): Handler {
|
|
||||||
return getCryptoBackgroundHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getEncryptBackgroundHandler(): Handler {
|
|
||||||
return getCryptoBackgroundHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCryptoBackgroundHandler(): Handler {
|
|
||||||
return cryptoBackgroundHandler
|
|
||||||
?: createCryptoBackgroundHandler()
|
|
||||||
.also { cryptoBackgroundHandler = it }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createCryptoBackgroundHandler(): Handler {
|
|
||||||
val handlerThread = HandlerThread(THREAD_CRYPTO_NAME)
|
|
||||||
handlerThread.start()
|
|
||||||
return Handler(handlerThread.looper)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -19,8 +19,11 @@
|
|||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
|
import com.squareup.moshi.Types
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
@ -64,6 +67,7 @@ import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificat
|
|||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||||
@ -137,6 +141,8 @@ internal class CryptoManager(
|
|||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor
|
||||||
) : CryptoService {
|
) : CryptoService {
|
||||||
|
|
||||||
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
// MXEncrypting instance for each room.
|
// MXEncrypting instance for each room.
|
||||||
private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap()
|
private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap()
|
||||||
private val isStarting = AtomicBoolean(false)
|
private val isStarting = AtomicBoolean(false)
|
||||||
@ -597,10 +603,7 @@ internal class CryptoManager(
|
|||||||
val result = withContext(coroutineDispatchers.crypto) {
|
val result = withContext(coroutineDispatchers.crypto) {
|
||||||
internalDecryptEvent(event, timeline)
|
internalDecryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
result.fold(
|
result.foldToCallback(callback)
|
||||||
{ callback.onFailure(it) },
|
|
||||||
{ callback.onSuccess(it) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,7 +790,10 @@ internal class CryptoManager(
|
|||||||
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
||||||
* @param callback the exported keys
|
* @param callback the exported keys
|
||||||
*/
|
*/
|
||||||
fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback<ByteArray>) {
|
private fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback<ByteArray>) {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
Try {
|
||||||
val iterationCount = Math.max(0, anIterationCount)
|
val iterationCount = Math.max(0, anIterationCount)
|
||||||
|
|
||||||
val exportedSessions = ArrayList<MegolmSessionData>()
|
val exportedSessions = ArrayList<MegolmSessionData>()
|
||||||
@ -802,20 +808,13 @@ internal class CryptoManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val encryptedRoomKeys: ByteArray
|
|
||||||
|
|
||||||
try {
|
|
||||||
val adapter = MoshiProvider.providesMoshi()
|
val adapter = MoshiProvider.providesMoshi()
|
||||||
.adapter(List::class.java)
|
.adapter(List::class.java)
|
||||||
|
|
||||||
encryptedRoomKeys = MXMegolmExportEncryption
|
MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
||||||
.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
}
|
||||||
} catch (e: Exception) {
|
}.foldToCallback(callback)
|
||||||
callback.onFailure(e)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.onSuccess(encryptedRoomKeys)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -830,40 +829,33 @@ internal class CryptoManager(
|
|||||||
password: String,
|
password: String,
|
||||||
progressListener: ProgressListener?,
|
progressListener: ProgressListener?,
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
Try {
|
||||||
Timber.v("## importRoomKeys starts")
|
Timber.v("## importRoomKeys starts")
|
||||||
|
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
val roomKeys: String
|
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
|
||||||
|
|
||||||
try {
|
|
||||||
roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
callback.onFailure(e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val importedSessions: List<MegolmSessionData>
|
|
||||||
|
|
||||||
val t1 = System.currentTimeMillis()
|
val t1 = System.currentTimeMillis()
|
||||||
|
|
||||||
Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms")
|
Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms")
|
||||||
|
|
||||||
try {
|
val importedSessions = MoshiProvider.providesMoshi()
|
||||||
val list = MoshiProvider.providesMoshi()
|
.adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
|
||||||
.adapter(List::class.java)
|
|
||||||
.fromJson(roomKeys)
|
.fromJson(roomKeys)
|
||||||
importedSessions = list as List<MegolmSessionData>
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "## importRoomKeys failed")
|
|
||||||
callback.onFailure(e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val t2 = System.currentTimeMillis()
|
val t2 = System.currentTimeMillis()
|
||||||
|
|
||||||
Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms")
|
Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms")
|
||||||
|
|
||||||
megolmSessionDataImporter.handle(importedSessions, true, progressListener, callback)
|
if (importedSessions == null) {
|
||||||
|
throw Exception("Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
megolmSessionDataImporter.handle(importedSessions, true, uiHandler, progressListener)
|
||||||
|
}
|
||||||
|
}.foldToCallback(callback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1061,10 +1053,7 @@ internal class CryptoManager(
|
|||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||||
deviceListManager
|
deviceListManager
|
||||||
.downloadKeys(userIds, forceDownload)
|
.downloadKeys(userIds, forceDownload)
|
||||||
.fold(
|
.foldToCallback(callback)
|
||||||
{ callback.onFailure(it) },
|
|
||||||
{ callback.onSuccess(it) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +277,7 @@ internal class CryptoModule {
|
|||||||
// Task
|
// Task
|
||||||
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
|
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
|
||||||
// Task executor
|
// Task executor
|
||||||
|
get(),
|
||||||
get())
|
get())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import android.text.TextUtils
|
|||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.util.onError
|
import im.vector.matrix.android.internal.extensions.onError
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto
|
|||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
@ -43,16 +44,6 @@ object MXMegolmExportEncryption {
|
|||||||
// default iteration count to export the e2e keys
|
// default iteration count to export the e2e keys
|
||||||
const val DEFAULT_ITERATION_COUNT = 500000
|
const val DEFAULT_ITERATION_COUNT = 500000
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a signed byte to a int value
|
|
||||||
*
|
|
||||||
* @param bVal the byte value to convert
|
|
||||||
* @return the matched int value
|
|
||||||
*/
|
|
||||||
private fun byteToInt(bVal: Byte): Int {
|
|
||||||
return (bVal and 0xFF.toByte()).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the AES key from the deriveKeys result.
|
* Extract the AES key from the deriveKeys result.
|
||||||
*
|
*
|
||||||
@ -108,7 +99,8 @@ object MXMegolmExportEncryption {
|
|||||||
|
|
||||||
val salt = Arrays.copyOfRange(body, 1, 1 + 16)
|
val salt = Arrays.copyOfRange(body, 1, 1 + 16)
|
||||||
val iv = Arrays.copyOfRange(body, 17, 17 + 16)
|
val iv = Arrays.copyOfRange(body, 17, 17 + 16)
|
||||||
val iterations = byteToInt(body[33]) shl 24 or (byteToInt(body[34]) shl 16) or (byteToInt(body[35]) shl 8) or byteToInt(body[36])
|
val iterations =
|
||||||
|
(body[33].toUnsignedInt() shl 24) or (body[34].toUnsignedInt() shl 16) or (body[35].toUnsignedInt() shl 8) or body[36].toUnsignedInt()
|
||||||
val ciphertext = Arrays.copyOfRange(body, 37, 37 + ciphertextLength)
|
val ciphertext = Arrays.copyOfRange(body, 37, 37 + ciphertextLength)
|
||||||
val hmac = Arrays.copyOfRange(body, body.size - 32, body.size)
|
val hmac = Arrays.copyOfRange(body, body.size - 32, body.size)
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.actions
|
package im.vector.matrix.android.internal.crypto.actions
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import android.os.Handler
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
|
|
||||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
|
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
|
||||||
@ -35,34 +35,32 @@ internal class MegolmSessionDataImporter(private val olmDevice: MXOlmDevice,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Import a list of megolm session keys.
|
* Import a list of megolm session keys.
|
||||||
|
* Must be call on the crypto coroutine thread
|
||||||
*
|
*
|
||||||
* @param megolmSessionsData megolm sessions.
|
* @param megolmSessionsData megolm sessions.
|
||||||
* @param backUpKeys true to back up them to the homeserver.
|
* @param backUpKeys true to back up them to the homeserver.
|
||||||
* @param progressListener the progress listener
|
* @param progressListener the progress listener
|
||||||
* @param callback
|
* @return import room keys result
|
||||||
*/
|
*/
|
||||||
|
@WorkerThread
|
||||||
fun handle(megolmSessionsData: List<MegolmSessionData>,
|
fun handle(megolmSessionsData: List<MegolmSessionData>,
|
||||||
fromBackup: Boolean,
|
fromBackup: Boolean,
|
||||||
progressListener: ProgressListener?,
|
uiHandler: Handler,
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
progressListener: ProgressListener?): ImportRoomKeysResult {
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
|
|
||||||
val totalNumbersOfKeys = megolmSessionsData.size
|
val totalNumbersOfKeys = megolmSessionsData.size
|
||||||
var cpt = 0
|
|
||||||
var lastProgress = 0
|
var lastProgress = 0
|
||||||
var totalNumbersOfImportedKeys = 0
|
var totalNumbersOfImportedKeys = 0
|
||||||
|
|
||||||
if (progressListener != null) {
|
if (progressListener != null) {
|
||||||
CryptoAsyncHelper.getUiHandler().post {
|
uiHandler.post {
|
||||||
progressListener.onProgress(0, 100)
|
progressListener.onProgress(0, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData)
|
val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData)
|
||||||
|
|
||||||
for (megolmSessionData in megolmSessionsData) {
|
megolmSessionsData.forEachIndexed { cpt, megolmSessionData ->
|
||||||
cpt++
|
|
||||||
|
|
||||||
|
|
||||||
val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm)
|
val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm)
|
||||||
|
|
||||||
if (null != decrypting) {
|
if (null != decrypting) {
|
||||||
@ -90,7 +88,7 @@ internal class MegolmSessionDataImporter(private val olmDevice: MXOlmDevice,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (progressListener != null) {
|
if (progressListener != null) {
|
||||||
CryptoAsyncHelper.getUiHandler().post {
|
uiHandler.post {
|
||||||
val progress = 100 * cpt / totalNumbersOfKeys
|
val progress = 100 * cpt / totalNumbersOfKeys
|
||||||
|
|
||||||
if (lastProgress != progress) {
|
if (lastProgress != progress) {
|
||||||
@ -111,10 +109,6 @@ internal class MegolmSessionDataImporter(private val olmDevice: MXOlmDevice,
|
|||||||
|
|
||||||
Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
|
Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
|
||||||
|
|
||||||
val finalTotalNumbersOfImportedKeys = totalNumbersOfImportedKeys
|
return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys)
|
||||||
|
|
||||||
CryptoAsyncHelper.getUiHandler().post {
|
|
||||||
callback.onSuccess(ImportRoomKeysResult(totalNumbersOfKeys, finalTotalNumbersOfImportedKeys))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,7 +31,10 @@ import im.vector.matrix.android.api.listeners.StepProgressListener
|
|||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import im.vector.matrix.android.internal.crypto.*
|
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
|
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||||
|
import im.vector.matrix.android.internal.crypto.ObjectSigner
|
||||||
import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter
|
import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature
|
||||||
@ -47,10 +50,15 @@ import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrap
|
|||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
|
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.TaskThread
|
import im.vector.matrix.android.internal.task.TaskThread
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
import org.matrix.olm.OlmPkDecryption
|
import org.matrix.olm.OlmPkDecryption
|
||||||
import org.matrix.olm.OlmPkEncryption
|
import org.matrix.olm.OlmPkEncryption
|
||||||
@ -87,7 +95,8 @@ internal class KeysBackup(
|
|||||||
private val storeSessionDataTask: StoreSessionsDataTask,
|
private val storeSessionDataTask: StoreSessionsDataTask,
|
||||||
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
|
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
|
||||||
// Task executor
|
// Task executor
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||||
) : KeysBackupService {
|
) : KeysBackupService {
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
@ -130,8 +139,9 @@ internal class KeysBackup(
|
|||||||
override fun prepareKeysBackupVersion(password: String?,
|
override fun prepareKeysBackupVersion(password: String?,
|
||||||
progressListener: ProgressListener?,
|
progressListener: ProgressListener?,
|
||||||
callback: MatrixCallback<MegolmBackupCreationInfo>) {
|
callback: MatrixCallback<MegolmBackupCreationInfo>) {
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
try {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
Try {
|
||||||
val olmPkDecryption = OlmPkDecryption()
|
val olmPkDecryption = OlmPkDecryption()
|
||||||
val megolmBackupAuthData = MegolmBackupAuthData()
|
val megolmBackupAuthData = MegolmBackupAuthData()
|
||||||
|
|
||||||
@ -173,12 +183,9 @@ internal class KeysBackup(
|
|||||||
megolmBackupCreationInfo.authData = megolmBackupAuthData
|
megolmBackupCreationInfo.authData = megolmBackupAuthData
|
||||||
megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
|
megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
|
||||||
|
|
||||||
uiHandler.post { callback.onSuccess(megolmBackupCreationInfo) }
|
megolmBackupCreationInfo
|
||||||
} catch (e: OlmException) {
|
|
||||||
Timber.e(e, "OlmException")
|
|
||||||
|
|
||||||
uiHandler.post { callback.onFailure(e) }
|
|
||||||
}
|
}
|
||||||
|
}.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +228,8 @@ internal class KeysBackup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
|
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
// If we're currently backing up to this backup... stop.
|
// If we're currently backing up to this backup... stop.
|
||||||
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
|
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
|
||||||
if (keysBackupVersion != null && version == keysBackupVersion!!.version) {
|
if (keysBackupVersion != null && version == keysBackupVersion!!.version) {
|
||||||
@ -254,6 +262,7 @@ internal class KeysBackup(
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun canRestoreKeys(): Boolean {
|
override fun canRestoreKeys(): Boolean {
|
||||||
// Server contains more keys than locally
|
// Server contains more keys than locally
|
||||||
@ -426,21 +435,17 @@ internal class KeysBackup(
|
|||||||
callback: MatrixCallback<Unit>) {
|
callback: MatrixCallback<Unit>) {
|
||||||
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
|
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
val myUserId = credentials.userId
|
|
||||||
|
|
||||||
// Get auth data to update it
|
// Get auth data to update it
|
||||||
val authData = getMegolmBackupAuthData(keysBackupVersion)
|
val authData = getMegolmBackupAuthData(keysBackupVersion)
|
||||||
|
|
||||||
if (authData == null) {
|
if (authData == null) {
|
||||||
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
|
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
callback.onFailure(IllegalArgumentException("Missing element"))
|
callback.onFailure(IllegalArgumentException("Missing element"))
|
||||||
}
|
} else {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
return@post
|
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
|
||||||
}
|
val myUserId = credentials.userId
|
||||||
|
|
||||||
// Get current signatures, or create an empty set
|
// Get current signatures, or create an empty set
|
||||||
val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap() ?: HashMap())
|
val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap() ?: HashMap())
|
||||||
@ -474,9 +479,11 @@ internal class KeysBackup(
|
|||||||
val moshi = MoshiProvider.providesMoshi()
|
val moshi = MoshiProvider.providesMoshi()
|
||||||
val adapter = moshi.adapter(Map::class.java)
|
val adapter = moshi.adapter(Map::class.java)
|
||||||
|
|
||||||
|
|
||||||
updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map<String, Any>?
|
updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map<String, Any>?
|
||||||
|
|
||||||
|
updateKeysBackupVersionBody
|
||||||
|
}
|
||||||
|
|
||||||
// And send it to the homeserver
|
// And send it to the homeserver
|
||||||
updateKeysBackupVersionTask
|
updateKeysBackupVersionTask
|
||||||
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody))
|
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody))
|
||||||
@ -493,62 +500,58 @@ internal class KeysBackup(
|
|||||||
|
|
||||||
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
|
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
callback.onSuccess(data)
|
callback.onSuccess(data)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
uiHandler.post {
|
|
||||||
callback.onFailure(failure)
|
callback.onFailure(failure)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
||||||
recoveryKey: String,
|
recoveryKey: String,
|
||||||
callback: MatrixCallback<Unit>) {
|
callback: MatrixCallback<Unit>) {
|
||||||
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
|
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) {
|
val isValid = withContext(coroutineDispatchers.crypto) {
|
||||||
|
isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.")
|
Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.")
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
|
callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
|
||||||
}
|
} else {
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
trustKeysBackupVersion(keysBackupVersion, true, callback)
|
trustKeysBackupVersion(keysBackupVersion, true, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
||||||
password: String,
|
password: String,
|
||||||
callback: MatrixCallback<Unit>) {
|
callback: MatrixCallback<Unit>) {
|
||||||
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
|
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, null)
|
val recoveryKey = withContext(coroutineDispatchers.crypto) {
|
||||||
|
recoveryKeyFromPassword(password, keysBackupVersion, null)
|
||||||
|
}
|
||||||
|
|
||||||
if (recoveryKey == null) {
|
if (recoveryKey == null) {
|
||||||
Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data")
|
Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data")
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
callback.onFailure(IllegalArgumentException("Missing element"))
|
callback.onFailure(IllegalArgumentException("Missing element"))
|
||||||
}
|
} else {
|
||||||
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check trust using the recovery key
|
// Check trust using the recovery key
|
||||||
trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback)
|
trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get public key from a Recovery key
|
* Get public key from a Recovery key
|
||||||
@ -591,12 +594,10 @@ internal class KeysBackup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getBackupProgress(progressListener: ProgressListener) {
|
override fun getBackupProgress(progressListener: ProgressListener) {
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true)
|
val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true)
|
||||||
val total = cryptoStore.inboundGroupSessionsCount(false)
|
val total = cryptoStore.inboundGroupSessionsCount(false)
|
||||||
|
|
||||||
uiHandler.post { progressListener.onProgress(backedUpKeys, total) }
|
progressListener.onProgress(backedUpKeys, total)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
||||||
@ -607,12 +608,13 @@ internal class KeysBackup(
|
|||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post(Runnable {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
Try {
|
||||||
// Check if the recovery is valid before going any further
|
// Check if the recovery is valid before going any further
|
||||||
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) {
|
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) {
|
||||||
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
|
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
|
||||||
uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) }
|
throw InvalidParameterException("Invalid recovery key")
|
||||||
return@Runnable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a PK decryption instance
|
// Get a PK decryption instance
|
||||||
@ -620,17 +622,23 @@ internal class KeysBackup(
|
|||||||
if (decryption == null) {
|
if (decryption == null) {
|
||||||
// This should not happen anymore
|
// This should not happen anymore
|
||||||
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error")
|
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error")
|
||||||
uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) }
|
throw InvalidParameterException("Invalid recovery key")
|
||||||
return@Runnable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stepProgressListener != null) {
|
decryption!!
|
||||||
uiHandler.post { stepProgressListener.onStepProgress(StepProgressListener.Step.DownloadingKey) }
|
|
||||||
}
|
}
|
||||||
|
}.fold(
|
||||||
|
{
|
||||||
|
callback.onFailure(it)
|
||||||
|
},
|
||||||
|
{ decryption ->
|
||||||
|
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)
|
||||||
|
|
||||||
// Get backed up keys from the homeserver
|
// Get backed up keys from the homeserver
|
||||||
getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback<KeysBackupData> {
|
getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback<KeysBackupData> {
|
||||||
override fun onSuccess(data: KeysBackupData) {
|
override fun onSuccess(data: KeysBackupData) {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
val importRoomKeysResult = withContext(coroutineDispatchers.crypto) {
|
||||||
val sessionsData = ArrayList<MegolmSessionData>()
|
val sessionsData = ArrayList<MegolmSessionData>()
|
||||||
// Restore that data
|
// Restore that data
|
||||||
var sessionsFromHsCount = 0
|
var sessionsFromHsCount = 0
|
||||||
@ -668,14 +676,18 @@ internal class KeysBackup(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener, object : MatrixCallback<ImportRoomKeysResult> {
|
val result = megolmSessionDataImporter.handle(sessionsData, !backUp, uiHandler, progressListener)
|
||||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
|
||||||
// Do not back up the key if it comes from a backup recovery
|
// Do not back up the key if it comes from a backup recovery
|
||||||
if (backUp) {
|
if (backUp) {
|
||||||
maybeBackupKeys()
|
maybeBackupKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.onSuccess(data)
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.onSuccess(importRoomKeysResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
@ -683,12 +695,8 @@ internal class KeysBackup(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
)
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
uiHandler.post { callback.onFailure(failure) }
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
||||||
@ -699,7 +707,8 @@ internal class KeysBackup(
|
|||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
val progressListener = if (stepProgressListener != null) {
|
val progressListener = if (stepProgressListener != null) {
|
||||||
object : ProgressListener {
|
object : ProgressListener {
|
||||||
override fun onProgress(progress: Int, total: Int) {
|
override fun onProgress(progress: Int, total: Int) {
|
||||||
@ -712,20 +721,24 @@ internal class KeysBackup(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, progressListener)
|
Try {
|
||||||
|
recoveryKeyFromPassword(password, keysBackupVersion, progressListener)
|
||||||
|
}
|
||||||
|
}.fold(
|
||||||
|
{
|
||||||
|
callback.onFailure(it)
|
||||||
|
},
|
||||||
|
{ recoveryKey ->
|
||||||
if (recoveryKey == null) {
|
if (recoveryKey == null) {
|
||||||
uiHandler.post {
|
|
||||||
Timber.v("backupKeys: Invalid configuration")
|
Timber.v("backupKeys: Invalid configuration")
|
||||||
callback.onFailure(IllegalStateException("Invalid configuration"))
|
callback.onFailure(IllegalStateException("Invalid configuration"))
|
||||||
}
|
} else {
|
||||||
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback)
|
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable
|
* Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable
|
||||||
@ -1190,7 +1203,8 @@ internal class KeysBackup(
|
|||||||
|
|
||||||
keysBackupStateManager.state = KeysBackupState.BackingUp
|
keysBackupStateManager.state = KeysBackupState.BackingUp
|
||||||
|
|
||||||
CryptoAsyncHelper.getEncryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.v("backupKeys: 2 - Encrypting keys")
|
Timber.v("backupKeys: 2 - Encrypting keys")
|
||||||
|
|
||||||
// Gather data to send to the homeserver
|
// Gather data to send to the homeserver
|
||||||
@ -1280,6 +1294,7 @@ internal class KeysBackup(
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
@ -27,17 +27,12 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf
|
|||||||
import im.vector.matrix.android.api.session.events.model.Event
|
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.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
|
|
||||||
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||||
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
||||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
import im.vector.matrix.android.internal.crypto.model.rest.*
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
@ -372,9 +367,8 @@ internal class DefaultSasVerificationService(private val credentials: Credential
|
|||||||
userId,
|
userId,
|
||||||
deviceID)
|
deviceID)
|
||||||
addTransaction(tx)
|
addTransaction(tx)
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
tx.start()
|
tx.start()
|
||||||
}
|
|
||||||
return txID
|
return txID
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Unknown verification method")
|
throw IllegalArgumentException("Unknown verification method")
|
||||||
|
@ -22,13 +22,12 @@ import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTr
|
|||||||
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
|
|
||||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
@ -125,9 +124,7 @@ internal class IncomingSASVerificationTransaction(
|
|||||||
//TODO force download keys!!
|
//TODO force download keys!!
|
||||||
//would be probably better to download the keys
|
//would be probably better to download the keys
|
||||||
//for now I cancel
|
//for now I cancel
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
cancel(CancelCode.User)
|
cancel(CancelCode.User)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// val otherKey = info.identityKey()
|
// val otherKey = info.identityKey()
|
||||||
//need to jump back to correct thread
|
//need to jump back to correct thread
|
||||||
@ -139,11 +136,9 @@ internal class IncomingSASVerificationTransaction(
|
|||||||
shortAuthenticationStrings = agreedShortCode,
|
shortAuthenticationStrings = agreedShortCode,
|
||||||
commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT)
|
commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT)
|
||||||
)
|
)
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
doAccept(accept)
|
doAccept(accept)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun doAccept(accept: KeyVerificationAccept) {
|
private fun doAccept(accept: KeyVerificationAccept) {
|
||||||
|
@ -86,7 +86,6 @@ internal class OutgoingSASVerificationRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
|
|
||||||
if (state != SasVerificationTxState.None) {
|
if (state != SasVerificationTxState.None) {
|
||||||
Timber.e("## start verification from invalid state")
|
Timber.e("## start verification from invalid state")
|
||||||
//should I cancel??
|
//should I cancel??
|
||||||
|
@ -23,7 +23,6 @@ import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
|
|||||||
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
|
|
||||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
import im.vector.matrix.android.internal.crypto.model.MXKey
|
||||||
@ -31,6 +30,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
|||||||
import im.vector.matrix.android.internal.crypto.model.rest.*
|
import im.vector.matrix.android.internal.crypto.model.rest.*
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
|
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import org.matrix.olm.OlmSAS
|
import org.matrix.olm.OlmSAS
|
||||||
@ -278,22 +278,18 @@ internal abstract class SASVerificationTransaction(
|
|||||||
.dispatchTo(object : MatrixCallback<Unit> {
|
.dispatchTo(object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.")
|
Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.")
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
if (onDone != null) {
|
if (onDone != null) {
|
||||||
onDone()
|
onDone()
|
||||||
} else {
|
} else {
|
||||||
state = nextState
|
state = nextState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state")
|
Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
cancel(onErrorReason)
|
cancel(onErrorReason)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
@ -359,11 +355,11 @@ internal abstract class SASVerificationTransaction(
|
|||||||
* or with the three numbers on separate lines.
|
* or with the three numbers on separate lines.
|
||||||
*/
|
*/
|
||||||
fun getDecimalCodeRepresentation(byteArray: ByteArray): String {
|
fun getDecimalCodeRepresentation(byteArray: ByteArray): String {
|
||||||
val b0 = byteArray[0].toInt().and(0xff) //need unsigned byte
|
val b0 = byteArray[0].toUnsignedInt() //need unsigned byte
|
||||||
val b1 = byteArray[1].toInt().and(0xff) //need unsigned byte
|
val b1 = byteArray[1].toUnsignedInt() //need unsigned byte
|
||||||
val b2 = byteArray[2].toInt().and(0xff) //need unsigned byte
|
val b2 = byteArray[2].toUnsignedInt() //need unsigned byte
|
||||||
val b3 = byteArray[3].toInt().and(0xff) //need unsigned byte
|
val b3 = byteArray[3].toUnsignedInt() //need unsigned byte
|
||||||
val b4 = byteArray[4].toInt().and(0xff) //need unsigned byte
|
val b4 = byteArray[4].toUnsignedInt() //need unsigned byte
|
||||||
//(B0 << 5 | B1 >> 3) + 1000
|
//(B0 << 5 | B1 >> 3) + 1000
|
||||||
val first = (b0.shl(5) or b1.shr(3)) + 1000
|
val first = (b0.shl(5) or b1.shr(3)) + 1000
|
||||||
//((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000
|
//((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000
|
||||||
@ -384,12 +380,12 @@ internal abstract class SASVerificationTransaction(
|
|||||||
* to that number 7 emoji are selected from a list of 64 emoji (see Appendix A)
|
* to that number 7 emoji are selected from a list of 64 emoji (see Appendix A)
|
||||||
*/
|
*/
|
||||||
fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
||||||
val b0 = byteArray[0].toInt().and(0xff)
|
val b0 = byteArray[0].toUnsignedInt()
|
||||||
val b1 = byteArray[1].toInt().and(0xff)
|
val b1 = byteArray[1].toUnsignedInt()
|
||||||
val b2 = byteArray[2].toInt().and(0xff)
|
val b2 = byteArray[2].toUnsignedInt()
|
||||||
val b3 = byteArray[3].toInt().and(0xff)
|
val b3 = byteArray[3].toUnsignedInt()
|
||||||
val b4 = byteArray[4].toInt().and(0xff)
|
val b4 = byteArray[4].toUnsignedInt()
|
||||||
val b5 = byteArray[5].toInt().and(0xff)
|
val b5 = byteArray[5].toUnsignedInt()
|
||||||
return listOf(
|
return listOf(
|
||||||
getEmojiForCode((b0 and 0xFC).shr(2)),
|
getEmojiForCode((b0 and 0xFC).shr(2)),
|
||||||
getEmojiForCode((b0 and 0x3).shl(4) or (b1 and 0xF0).shr(4)),
|
getEmojiForCode((b0 and 0x3).shl(4) or (b1 and 0xF0).shr(4)),
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
package im.vector.matrix.android.internal.di
|
package im.vector.matrix.android.internal.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
|
import android.os.Handler
|
||||||
|
import android.os.HandlerThread
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
@ -36,11 +37,14 @@ class MatrixModule(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
val cryptoHandler = CryptoAsyncHelper.getDecryptBackgroundHandler()
|
val THREAD_CRYPTO_NAME = "Crypto_Thread"
|
||||||
|
val handlerThread = HandlerThread(THREAD_CRYPTO_NAME)
|
||||||
|
handlerThread.start()
|
||||||
|
|
||||||
MatrixCoroutineDispatchers(io = Dispatchers.IO,
|
MatrixCoroutineDispatchers(io = Dispatchers.IO,
|
||||||
computation = Dispatchers.IO,
|
computation = Dispatchers.IO,
|
||||||
main = Dispatchers.Main,
|
main = Dispatchers.Main,
|
||||||
crypto = cryptoHandler.asCoroutineDispatcher("crypto")
|
crypto = Handler(handlerThread.looper).asCoroutineDispatcher("crypto")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.extensions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a signed byte to a int value
|
||||||
|
*/
|
||||||
|
fun Byte.toUnsignedInt() = toInt() and 0xff
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.extensions
|
||||||
|
|
||||||
|
import arrow.core.*
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
|
||||||
|
inline fun <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix()
|
||||||
|
.fold(
|
||||||
|
{
|
||||||
|
f(it)
|
||||||
|
Failure(it)
|
||||||
|
},
|
||||||
|
{ Success(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <A> Try<A>.foldToCallback(callback: MatrixCallback<A>): Unit = fold(
|
||||||
|
{ callback.onFailure(it) },
|
||||||
|
{ callback.onSuccess(it) })
|
@ -18,6 +18,8 @@ package im.vector.matrix.android.internal.task
|
|||||||
|
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
|
import im.vector.matrix.android.internal.extensions.onError
|
||||||
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@ -38,12 +40,10 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis
|
|||||||
task.execute(task.params)
|
task.execute(task.params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultOrFailure.fold({
|
resultOrFailure.onError {
|
||||||
Timber.d(it, "Task failed")
|
Timber.d(it, "Task failed")
|
||||||
task.callback.onFailure(it)
|
}
|
||||||
}, {
|
.foldToCallback(task.callback)
|
||||||
task.callback.onSuccess(it)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return CancelableCoroutine(job)
|
return CancelableCoroutine(job)
|
||||||
}
|
}
|
||||||
|
@ -19,34 +19,30 @@ package im.vector.riotredesign.core.dialogs
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.extensions.showPassword
|
||||||
|
import im.vector.riotredesign.core.platform.SimpleTextWatcher
|
||||||
|
|
||||||
class ExportKeysDialog {
|
class ExportKeysDialog {
|
||||||
|
|
||||||
|
var passwordVisible = false
|
||||||
|
|
||||||
fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) {
|
fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) {
|
||||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null)
|
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null)
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
.setTitle(R.string.encryption_export_room_keys)
|
.setTitle(R.string.encryption_export_room_keys)
|
||||||
.setView(dialogLayout)
|
.setView(dialogLayout)
|
||||||
|
|
||||||
val passPhrase1EditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_passphrase_edit_text)
|
val passPhrase1EditText = dialogLayout.findViewById<TextInputEditText>(R.id.exportDialogEt)
|
||||||
val passPhrase2EditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_confirm_passphrase_edit_text)
|
val passPhrase2EditText = dialogLayout.findViewById<TextInputEditText>(R.id.exportDialogEtConfirm)
|
||||||
val passPhrase2Til = dialogLayout.findViewById<TextInputLayout>(R.id.dialog_e2e_keys_confirm_passphrase_til)
|
val passPhrase2Til = dialogLayout.findViewById<TextInputLayout>(R.id.exportDialogTilConfirm)
|
||||||
val exportButton = dialogLayout.findViewById<Button>(R.id.dialog_e2e_keys_export_button)
|
val exportButton = dialogLayout.findViewById<Button>(R.id.exportDialogSubmit)
|
||||||
val textWatcher = object : TextWatcher {
|
val textWatcher = object : SimpleTextWatcher() {
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
when {
|
when {
|
||||||
TextUtils.isEmpty(passPhrase1EditText.text) -> {
|
TextUtils.isEmpty(passPhrase1EditText.text) -> {
|
||||||
@ -68,6 +64,14 @@ class ExportKeysDialog {
|
|||||||
passPhrase1EditText.addTextChangedListener(textWatcher)
|
passPhrase1EditText.addTextChangedListener(textWatcher)
|
||||||
passPhrase2EditText.addTextChangedListener(textWatcher)
|
passPhrase2EditText.addTextChangedListener(textWatcher)
|
||||||
|
|
||||||
|
val showPassword = dialogLayout.findViewById<ImageView>(R.id.exportDialogShowPassword)
|
||||||
|
showPassword.setOnClickListener {
|
||||||
|
passwordVisible = !passwordVisible
|
||||||
|
passPhrase1EditText.showPassword(passwordVisible)
|
||||||
|
passPhrase2EditText.showPassword(passwordVisible)
|
||||||
|
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
|
||||||
|
}
|
||||||
|
|
||||||
val exportDialog = builder.show()
|
val exportDialog = builder.show()
|
||||||
|
|
||||||
exportButton.setOnClickListener {
|
exportButton.setOnClickListener {
|
||||||
|
@ -18,16 +18,18 @@ package im.vector.riotredesign.core.files
|
|||||||
|
|
||||||
import android.app.DownloadManager
|
import android.app.DownloadManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import arrow.core.Try
|
||||||
import okio.Okio
|
import okio.Okio
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a string to a file with Okio
|
* Save a string to a file with Okio
|
||||||
* @return true in case of success
|
|
||||||
*/
|
*/
|
||||||
fun saveStringToFile(str: String, file: File): Boolean {
|
@WorkerThread
|
||||||
return try {
|
fun writeToFile(str: String, file: File): Try<Unit> {
|
||||||
|
return Try {
|
||||||
val sink = Okio.sink(file)
|
val sink = Okio.sink(file)
|
||||||
|
|
||||||
val bufferedSink = Okio.buffer(sink)
|
val bufferedSink = Okio.buffer(sink)
|
||||||
@ -36,14 +38,25 @@ fun saveStringToFile(str: String, file: File): Boolean {
|
|||||||
|
|
||||||
bufferedSink.close()
|
bufferedSink.close()
|
||||||
sink.close()
|
sink.close()
|
||||||
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "Error saving file")
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a byte array to a file with Okio
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
fun writeToFile(data: ByteArray, file: File): Try<Unit> {
|
||||||
|
return Try {
|
||||||
|
val sink = Okio.sink(file)
|
||||||
|
|
||||||
|
val bufferedSink = Okio.buffer(sink)
|
||||||
|
|
||||||
|
bufferedSink.write(data)
|
||||||
|
|
||||||
|
bufferedSink.close()
|
||||||
|
sink.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun addEntryToDownloadManager(context: Context,
|
fun addEntryToDownloadManager(context: Context,
|
||||||
file: File,
|
file: File,
|
||||||
|
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotredesign.core.intent
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipDescription
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.TextUtils
|
||||||
|
import androidx.core.util.PatternsCompat.WEB_URL
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspired from Riot code: RoomMediaMessage.java
|
||||||
|
*/
|
||||||
|
sealed class ExternalIntentData {
|
||||||
|
/**
|
||||||
|
* Constructor for a text message.
|
||||||
|
*
|
||||||
|
* @param text the text
|
||||||
|
* @param htmlText the HTML text
|
||||||
|
* @param format the formatted text format
|
||||||
|
*/
|
||||||
|
data class IntentDataText(
|
||||||
|
val text: CharSequence? = null,
|
||||||
|
val htmlText: String? = null,
|
||||||
|
val format: String? = null,
|
||||||
|
val clipDataItem: ClipData.Item = ClipData.Item(text, htmlText),
|
||||||
|
val mimeType: String? = if (null == htmlText) ClipDescription.MIMETYPE_TEXT_PLAIN else format
|
||||||
|
) : ExternalIntentData()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clip data
|
||||||
|
*/
|
||||||
|
data class IntentDataClipData(
|
||||||
|
val clipDataItem: ClipData.Item,
|
||||||
|
val mimeType: String?
|
||||||
|
) : ExternalIntentData()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor from a media Uri/
|
||||||
|
*
|
||||||
|
* @param uri the media uri
|
||||||
|
* @param filename the media file name
|
||||||
|
*/
|
||||||
|
data class IntentDataUri(
|
||||||
|
val uri: Uri,
|
||||||
|
val filename: String? = null
|
||||||
|
) : ExternalIntentData()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun analyseIntent(intent: Intent): List<ExternalIntentData> {
|
||||||
|
val externalIntentDataList = ArrayList<ExternalIntentData>()
|
||||||
|
|
||||||
|
|
||||||
|
// chrome adds many items when sharing an web page link
|
||||||
|
// so, test first the type
|
||||||
|
if (TextUtils.equals(intent.type, ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
||||||
|
var message: String? = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
|
|
||||||
|
if (null == message) {
|
||||||
|
val sequence = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)
|
||||||
|
if (null != sequence) {
|
||||||
|
message = sequence.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(subject)) {
|
||||||
|
if (TextUtils.isEmpty(message)) {
|
||||||
|
message = subject
|
||||||
|
} else if (WEB_URL.matcher(message!!).matches()) {
|
||||||
|
message = subject + "\n" + message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(message)) {
|
||||||
|
externalIntentDataList.add(ExternalIntentData.IntentDataText(message!!, null, intent.type))
|
||||||
|
return externalIntentDataList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var clipData: ClipData? = null
|
||||||
|
var mimetypes: MutableList<String>? = null
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||||
|
clipData = intent.clipData
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiple data
|
||||||
|
if (null != clipData) {
|
||||||
|
if (null != clipData.description) {
|
||||||
|
if (0 != clipData.description.mimeTypeCount) {
|
||||||
|
mimetypes = ArrayList()
|
||||||
|
|
||||||
|
for (i in 0 until clipData.description.mimeTypeCount) {
|
||||||
|
mimetypes.add(clipData.description.getMimeType(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the filter is "accept anything" the mimetype does not make sense
|
||||||
|
if (1 == mimetypes.size) {
|
||||||
|
if (mimetypes[0].endsWith("/*")) {
|
||||||
|
mimetypes = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val count = clipData.itemCount
|
||||||
|
|
||||||
|
for (i in 0 until count) {
|
||||||
|
val item = clipData.getItemAt(i)
|
||||||
|
var mimetype: String? = null
|
||||||
|
|
||||||
|
if (null != mimetypes) {
|
||||||
|
if (i < mimetypes.size) {
|
||||||
|
mimetype = mimetypes[i]
|
||||||
|
} else {
|
||||||
|
mimetype = mimetypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// uris list is not a valid mimetype
|
||||||
|
if (TextUtils.equals(mimetype, ClipDescription.MIMETYPE_TEXT_URILIST)) {
|
||||||
|
mimetype = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalIntentDataList.add(ExternalIntentData.IntentDataClipData(item, mimetype))
|
||||||
|
}
|
||||||
|
} else if (null != intent.data) {
|
||||||
|
externalIntentDataList.add(ExternalIntentData.IntentDataUri(intent.data!!))
|
||||||
|
}
|
||||||
|
|
||||||
|
return externalIntentDataList
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotredesign.core.intent
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
|
||||||
|
fun getFilenameFromUri(context: Context, uri: Uri): String? {
|
||||||
|
var result: String? = null
|
||||||
|
if (uri.scheme == "content") {
|
||||||
|
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
|
||||||
|
try {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = uri.path
|
||||||
|
val cut = result.lastIndexOf('/')
|
||||||
|
if (cut != -1) {
|
||||||
|
result = result.substring(cut + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotredesign.core.intent
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import im.vector.riotredesign.core.utils.getFileExtension
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mimetype from a uri.
|
||||||
|
*
|
||||||
|
* @param context the context
|
||||||
|
* @return the mimetype
|
||||||
|
*/
|
||||||
|
fun getMimeTypeFromUri(context: Context, uri: Uri): String? {
|
||||||
|
var mimeType: String? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
mimeType = context.contentResolver.getType(uri)
|
||||||
|
|
||||||
|
// try to find the mimetype from the filename
|
||||||
|
if (null == mimeType) {
|
||||||
|
val extension = getFileExtension(uri.toString())
|
||||||
|
if (extension != null) {
|
||||||
|
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null != mimeType) {
|
||||||
|
// the mimetype is sometimes in uppercase.
|
||||||
|
mimeType = mimeType.toLowerCase()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Failed to open resource input stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mimeType
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotredesign.features.crypto.keys
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
|
import im.vector.riotredesign.core.files.addEntryToDownloadManager
|
||||||
|
import im.vector.riotredesign.core.files.writeToFile
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class KeysExporter(private val session: Session) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export keys and return the file path with the callback
|
||||||
|
*/
|
||||||
|
fun export(context: Context, password: String, callback: MatrixCallback<String>) {
|
||||||
|
session.exportRoomKeys(password, object : MatrixCallback<ByteArray> {
|
||||||
|
|
||||||
|
override fun onSuccess(data: ByteArray) {
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Try {
|
||||||
|
val parentDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
val file = File(parentDir, "riotx-keys-" + System.currentTimeMillis() + ".txt")
|
||||||
|
|
||||||
|
writeToFile(data, file)
|
||||||
|
|
||||||
|
addEntryToDownloadManager(context, file, "text/plain")
|
||||||
|
|
||||||
|
file.absolutePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.foldToCallback(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
callback.onFailure(failure)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotredesign.features.crypto.keys
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
|
import im.vector.riotredesign.core.intent.getMimeTypeFromUri
|
||||||
|
import im.vector.riotredesign.core.resources.openResource
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class KeysImporter(private val session: Session) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import keys from provided Uri
|
||||||
|
*/
|
||||||
|
fun import(context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
mimetype: String?,
|
||||||
|
password: String,
|
||||||
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Try {
|
||||||
|
val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri))
|
||||||
|
|
||||||
|
if (resource?.mContentStream == null) {
|
||||||
|
throw Exception("Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
val data: ByteArray
|
||||||
|
try {
|
||||||
|
data = ByteArray(resource.mContentStream!!.available())
|
||||||
|
resource.mContentStream!!.read(data)
|
||||||
|
resource.mContentStream!!.close()
|
||||||
|
|
||||||
|
data
|
||||||
|
} catch (e: Exception) {
|
||||||
|
try {
|
||||||
|
resource.mContentStream!!.close()
|
||||||
|
} catch (e2: Exception) {
|
||||||
|
Timber.e(e2, "## importKeys()")
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
{
|
||||||
|
callback.onFailure(it)
|
||||||
|
},
|
||||||
|
{ byteArray ->
|
||||||
|
session.importRoomKeys(byteArray,
|
||||||
|
password,
|
||||||
|
null,
|
||||||
|
callback)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,10 +23,13 @@ import androidx.fragment.app.FragmentManager
|
|||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
|
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
|
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
|
||||||
import im.vector.riotredesign.core.extensions.observeEvent
|
import im.vector.riotredesign.core.extensions.observeEvent
|
||||||
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
|
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
|
||||||
|
import im.vector.riotredesign.core.utils.toast
|
||||||
|
import im.vector.riotredesign.features.crypto.keys.KeysExporter
|
||||||
|
|
||||||
class KeysBackupSetupActivity : SimpleFragmentActivity() {
|
class KeysBackupSetupActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
@ -118,19 +121,21 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exportKeysManually() {
|
private fun exportKeysManually() {
|
||||||
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
|
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
|
||||||
override fun onPassphrase(passphrase: String) {
|
override fun onPassphrase(passphrase: String) {
|
||||||
notImplemented()
|
|
||||||
/*
|
|
||||||
showWaitingView()
|
showWaitingView()
|
||||||
|
|
||||||
CommonActivityUtils.exportKeys(session, passphrase, object : SimpleApiCallback<String>(this@KeysBackupSetupActivity) {
|
KeysExporter(session)
|
||||||
override fun onSuccess(filename: String) {
|
.export(this@KeysBackupSetupActivity,
|
||||||
|
passphrase,
|
||||||
|
object : MatrixCallback<String> {
|
||||||
|
|
||||||
|
override fun onSuccess(data: String) {
|
||||||
hideWaitingView()
|
hideWaitingView()
|
||||||
|
|
||||||
AlertDialog.Builder(this@KeysBackupSetupActivity)
|
AlertDialog.Builder(this@KeysBackupSetupActivity)
|
||||||
.setMessage(getString(R.string.encryption_export_saved_as, filename))
|
.setMessage(getString(R.string.encryption_export_saved_as, data))
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.ok) { dialog, which ->
|
.setPositiveButton(R.string.ok) { dialog, which ->
|
||||||
val resultIntent = Intent()
|
val resultIntent = Intent()
|
||||||
@ -141,26 +146,13 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNetworkError(e: Exception) {
|
override fun onFailure(failure: Throwable) {
|
||||||
super.onNetworkError(e)
|
toast(failure.localizedMessage)
|
||||||
hideWaitingView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMatrixError(e: MatrixError) {
|
|
||||||
super.onMatrixError(e)
|
|
||||||
hideWaitingView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUnexpectedError(e: Exception) {
|
|
||||||
super.onUnexpectedError(e)
|
|
||||||
hideWaitingView()
|
hideWaitingView()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,15 +25,20 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import arrow.core.Try
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
|
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.files.addEntryToDownloadManager
|
import im.vector.riotredesign.core.files.addEntryToDownloadManager
|
||||||
import im.vector.riotredesign.core.files.saveStringToFile
|
import im.vector.riotredesign.core.files.writeToFile
|
||||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotredesign.core.utils.*
|
import im.vector.riotredesign.core.utils.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
|
class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
|
||||||
@ -157,30 +162,39 @@ class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun exportRecoveryKeyToFile(data: String) {
|
private fun exportRecoveryKeyToFile(data: String) {
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Try {
|
||||||
val parentDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
val parentDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
val file = File(parentDir, "recovery-key-" + System.currentTimeMillis() + ".txt")
|
val file = File(parentDir, "recovery-key-" + System.currentTimeMillis() + ".txt")
|
||||||
|
|
||||||
if (saveStringToFile(data, file)) {
|
writeToFile(data, file)
|
||||||
|
|
||||||
addEntryToDownloadManager(requireContext(), file, "text/plain")
|
addEntryToDownloadManager(requireContext(), file, "text/plain")
|
||||||
|
|
||||||
context?.let {
|
file.absolutePath
|
||||||
AlertDialog.Builder(it)
|
|
||||||
.setMessage(getString(R.string.recovery_key_export_saved_as_warning, file.absolutePath))
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(R.string.ok, null)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
viewModel.copyHasBeenMade = true
|
.fold(
|
||||||
} else {
|
{ throwable ->
|
||||||
context?.let {
|
context?.let {
|
||||||
AlertDialog.Builder(it)
|
AlertDialog.Builder(it)
|
||||||
.setTitle(R.string.dialog_title_error)
|
.setTitle(R.string.dialog_title_error)
|
||||||
.setMessage(getString(R.string.unknown_error))
|
.setMessage(throwable.localizedMessage)
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(R.string.ok, null)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{ path ->
|
||||||
|
viewModel.copyHasBeenMade = true
|
||||||
|
|
||||||
|
context?.let {
|
||||||
|
AlertDialog.Builder(it)
|
||||||
|
.setMessage(getString(R.string.recovery_key_export_saved_as_warning, path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
?.setCancelable(false)
|
||||||
|
?.setPositiveButton(R.string.ok, null)
|
||||||
|
?.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,12 +49,16 @@ import im.vector.matrix.android.api.extensions.sortByLastSeen
|
|||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
|
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
|
||||||
import im.vector.riotredesign.core.extensions.showPassword
|
import im.vector.riotredesign.core.extensions.showPassword
|
||||||
import im.vector.riotredesign.core.extensions.withArgs
|
import im.vector.riotredesign.core.extensions.withArgs
|
||||||
|
import im.vector.riotredesign.core.intent.ExternalIntentData
|
||||||
|
import im.vector.riotredesign.core.intent.analyseIntent
|
||||||
|
import im.vector.riotredesign.core.intent.getFilenameFromUri
|
||||||
import im.vector.riotredesign.core.platform.SimpleTextWatcher
|
import im.vector.riotredesign.core.platform.SimpleTextWatcher
|
||||||
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
|
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
|
||||||
import im.vector.riotredesign.core.preference.BingRule
|
import im.vector.riotredesign.core.preference.BingRule
|
||||||
@ -64,6 +68,8 @@ import im.vector.riotredesign.core.preference.VectorPreference
|
|||||||
import im.vector.riotredesign.core.utils.*
|
import im.vector.riotredesign.core.utils.*
|
||||||
import im.vector.riotredesign.features.MainActivity
|
import im.vector.riotredesign.features.MainActivity
|
||||||
import im.vector.riotredesign.features.configuration.VectorConfiguration
|
import im.vector.riotredesign.features.configuration.VectorConfiguration
|
||||||
|
import im.vector.riotredesign.features.crypto.keys.KeysExporter
|
||||||
|
import im.vector.riotredesign.features.crypto.keys.KeysImporter
|
||||||
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||||
import im.vector.riotredesign.features.themes.ThemeUtils
|
import im.vector.riotredesign.features.themes.ThemeUtils
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
@ -885,9 +891,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||||||
PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this)
|
PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Test
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
/* TODO
|
|
||||||
if (allGranted(grantResults)) {
|
if (allGranted(grantResults)) {
|
||||||
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
||||||
changeAvatar()
|
changeAvatar()
|
||||||
@ -895,7 +899,6 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||||||
exportKeys()
|
exportKeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================================================
|
//==============================================================================================================
|
||||||
@ -1782,6 +1785,9 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||||||
* @param errorMessage the error message
|
* @param errorMessage the error message
|
||||||
*/
|
*/
|
||||||
private fun onCommonDone(errorMessage: String?) {
|
private fun onCommonDone(errorMessage: String?) {
|
||||||
|
if (!isAdded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
if (!TextUtils.isEmpty(errorMessage) && errorMessage != null) {
|
if (!TextUtils.isEmpty(errorMessage) && errorMessage != null) {
|
||||||
activity?.toast(errorMessage!!)
|
activity?.toast(errorMessage!!)
|
||||||
@ -2594,38 +2600,29 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener {
|
ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener {
|
||||||
override fun onPassphrase(passphrase: String) {
|
override fun onPassphrase(passphrase: String) {
|
||||||
notImplemented()
|
|
||||||
/*
|
|
||||||
|
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
|
|
||||||
CommonActivityUtils.exportKeys(session, passphrase, object : SimpleApiCallback<String>(activity) {
|
KeysExporter(mSession)
|
||||||
override fun onSuccess(filename: String) {
|
.export(requireContext(),
|
||||||
|
passphrase,
|
||||||
|
object : MatrixCallback<String> {
|
||||||
|
override fun onSuccess(data: String) {
|
||||||
|
if (isAdded) {
|
||||||
hideLoadingView()
|
hideLoadingView()
|
||||||
|
|
||||||
AlertDialog.Builder(activity)
|
AlertDialog.Builder(activity)
|
||||||
.setMessage(getString(R.string.encryption_export_saved_as, filename))
|
.setMessage(getString(R.string.encryption_export_saved_as, data))
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNetworkError(e: Exception) {
|
|
||||||
super.onNetworkError(e)
|
|
||||||
hideLoadingView()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMatrixError(e: MatrixError) {
|
override fun onFailure(failure: Throwable) {
|
||||||
super.onMatrixError(e)
|
onCommonDone(failure.localizedMessage)
|
||||||
hideLoadingView()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnexpectedError(e: Exception) {
|
|
||||||
super.onUnexpectedError(e)
|
|
||||||
hideLoadingView()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2651,15 +2648,42 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
notImplemented()
|
val sharedDataItems = analyseIntent(intent)
|
||||||
|
|
||||||
/*
|
|
||||||
val sharedDataItems = ArrayList(RoomMediaMessage.listRoomMediaMessages(intent))
|
|
||||||
val thisActivity = activity
|
val thisActivity = activity
|
||||||
|
|
||||||
if (sharedDataItems.isNotEmpty() && thisActivity != null) {
|
if (sharedDataItems.isNotEmpty() && thisActivity != null) {
|
||||||
val sharedDataItem = sharedDataItems[0]
|
val sharedDataItem = sharedDataItems[0]
|
||||||
|
|
||||||
|
val uri = when (sharedDataItem) {
|
||||||
|
is ExternalIntentData.IntentDataUri -> sharedDataItem.uri
|
||||||
|
is ExternalIntentData.IntentDataClipData -> sharedDataItem.clipDataItem.uri
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
val mimetype = when (sharedDataItem) {
|
||||||
|
is ExternalIntentData.IntentDataClipData -> sharedDataItem.mimeType
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val appContext = thisActivity.applicationContext
|
||||||
|
|
||||||
|
val filename = getFilenameFromUri(appContext, uri)
|
||||||
|
|
||||||
val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null)
|
val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null)
|
||||||
|
|
||||||
|
val textView = dialogLayout.findViewById<TextView>(R.id.dialog_e2e_keys_passphrase_filename)
|
||||||
|
|
||||||
|
if (filename.isNullOrBlank()) {
|
||||||
|
textView.isVisible = false
|
||||||
|
} else {
|
||||||
|
textView.isVisible = true
|
||||||
|
textView.text = getString(R.string.import_e2e_keys_from_file, filename)
|
||||||
|
}
|
||||||
|
|
||||||
val builder = AlertDialog.Builder(thisActivity)
|
val builder = AlertDialog.Builder(thisActivity)
|
||||||
.setTitle(R.string.encryption_import_room_keys)
|
.setTitle(R.string.encryption_import_room_keys)
|
||||||
.setView(dialogLayout)
|
.setView(dialogLayout)
|
||||||
@ -2667,73 +2691,39 @@ if (sharedDataItems.isNotEmpty() && thisActivity != null) {
|
|||||||
val passPhraseEditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_passphrase_edit_text)
|
val passPhraseEditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_passphrase_edit_text)
|
||||||
val importButton = dialogLayout.findViewById<Button>(R.id.dialog_e2e_keys_import_button)
|
val importButton = dialogLayout.findViewById<Button>(R.id.dialog_e2e_keys_import_button)
|
||||||
|
|
||||||
passPhraseEditText.addTextChangedListener(object : TextWatcher {
|
passPhraseEditText.addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
importButton.isEnabled = !TextUtils.isEmpty(passPhraseEditText.text)
|
importButton.isEnabled = !TextUtils.isEmpty(passPhraseEditText.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
val importDialog = builder.show()
|
val importDialog = builder.show()
|
||||||
val appContext = thisActivity.applicationContext
|
|
||||||
|
|
||||||
importButton.setOnClickListener(View.OnClickListener {
|
importButton.setOnClickListener(View.OnClickListener {
|
||||||
val password = passPhraseEditText.text.toString()
|
val password = passPhraseEditText.text.toString()
|
||||||
val resource = openResource(appContext, sharedDataItem.uri, sharedDataItem.getMimeType(appContext))
|
|
||||||
|
|
||||||
if (resource?.mContentStream == null) {
|
|
||||||
appContext.toast("Error")
|
|
||||||
|
|
||||||
return@OnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
val data: ByteArray
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = ByteArray(resource.mContentStream!!.available())
|
|
||||||
resource!!.mContentStream!!.read(data)
|
|
||||||
resource!!.mContentStream!!.close()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
try {
|
|
||||||
resource!!.mContentStream!!.close()
|
|
||||||
} catch (e2: Exception) {
|
|
||||||
Timber.e(e2, "## importKeys()")
|
|
||||||
}
|
|
||||||
|
|
||||||
appContext.toast(e.localizedMessage)
|
|
||||||
|
|
||||||
return@OnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
|
|
||||||
session.importRoomKeys(data,
|
KeysImporter(mSession)
|
||||||
|
.import(requireContext(),
|
||||||
|
uri,
|
||||||
|
mimetype,
|
||||||
password,
|
password,
|
||||||
null,
|
|
||||||
object : MatrixCallback<ImportRoomKeysResult> {
|
object : MatrixCallback<ImportRoomKeysResult> {
|
||||||
override fun onSuccess(info: ImportRoomKeysResult) {
|
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||||
if (!isAdded) {
|
if (!isAdded) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hideLoadingView()
|
hideLoadingView()
|
||||||
|
|
||||||
info?.let {
|
|
||||||
AlertDialog.Builder(thisActivity)
|
AlertDialog.Builder(thisActivity)
|
||||||
.setMessage(getString(R.string.encryption_import_room_keys_success,
|
.setMessage(getString(R.string.encryption_import_room_keys_success,
|
||||||
it.successfullyNumberOfImportedKeys,
|
data.successfullyNumberOfImportedKeys,
|
||||||
it.totalNumberOfKeys))
|
data.totalNumberOfKeys))
|
||||||
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
appContext.toast(failure.localizedMessage)
|
appContext.toast(failure.localizedMessage)
|
||||||
@ -2744,7 +2734,6 @@ if (sharedDataItems.isNotEmpty() && thisActivity != null) {
|
|||||||
importDialog.dismiss()
|
importDialog.dismiss()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================================================
|
//==============================================================================================================
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/layout_root"
|
android:id="@+id/layout_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="?dialogPreferredPadding"
|
android:paddingStart="?dialogPreferredPadding"
|
||||||
android:paddingLeft="?dialogPreferredPadding"
|
android:paddingLeft="?dialogPreferredPadding"
|
||||||
@ -13,21 +13,41 @@
|
|||||||
android:paddingBottom="12dp">
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/exportDialogText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/encryption_export_notice"
|
android:text="@string/encryption_export_notice"
|
||||||
android:textColor="?riotx_text_primary"
|
android:textColor="?riotx_text_primary"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/exportDialogShowPassword"
|
||||||
|
android:layout_width="@dimen/layout_touch_size"
|
||||||
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_eye_black"
|
||||||
|
android:tint="?attr/colorAccent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/exportDialogTil"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/exportDialogTil" />
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/exportDialogTil"
|
||||||
style="@style/VectorTextInputLayout"
|
style="@style/VectorTextInputLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:textColorHint="?attr/vctr_default_text_hint_color">
|
android:textColorHint="?attr/vctr_default_text_hint_color"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/exportDialogShowPassword"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/exportDialogText">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/dialog_e2e_keys_passphrase_edit_text"
|
android:id="@+id/exportDialogEt"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/passphrase_create_passphrase"
|
android:hint="@string/passphrase_create_passphrase"
|
||||||
@ -38,16 +58,19 @@
|
|||||||
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/dialog_e2e_keys_confirm_passphrase_til"
|
android:id="@+id/exportDialogTilConfirm"
|
||||||
style="@style/VectorTextInputLayout"
|
style="@style/VectorTextInputLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:textColorHint="?attr/vctr_default_text_hint_color"
|
android:textColorHint="?attr/vctr_default_text_hint_color"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/exportDialogTil"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/exportDialogTil">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/dialog_e2e_keys_confirm_passphrase_edit_text"
|
android:id="@+id/exportDialogEtConfirm"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/passphrase_confirm_passphrase"
|
android:hint="@string/passphrase_confirm_passphrase"
|
||||||
@ -57,10 +80,14 @@
|
|||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/dialog_e2e_keys_export_button"
|
android:id="@+id/exportDialogSubmit"
|
||||||
style="@style/VectorButtonStyle"
|
style="@style/VectorButtonStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
android:text="@string/encryption_export_export" />
|
android:text="@string/encryption_export_export"
|
||||||
</LinearLayout>
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/exportDialogTilConfirm" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/layout_root"
|
android:id="@+id/layout_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@ -11,6 +12,18 @@
|
|||||||
android:paddingRight="?dialogPreferredPadding"
|
android:paddingRight="?dialogPreferredPadding"
|
||||||
android:paddingBottom="12dp">
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_e2e_keys_passphrase_filename"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="filename.txt"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/VectorTextInputLayout"
|
style="@style/VectorTextInputLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -4,4 +4,6 @@
|
|||||||
<!-- Strings not defined in Riot -->
|
<!-- Strings not defined in Riot -->
|
||||||
|
|
||||||
|
|
||||||
|
<string name="import_e2e_keys_from_file">"Import e2e keys from file \"%1$s\"."</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue
Block a user