Convert KeysExporter to coroutines

This commit is contained in:
Benoit Marty 2021-06-01 16:50:10 +02:00 committed by Benoit Marty
parent f99600f115
commit 3be95ca442
8 changed files with 92 additions and 122 deletions

View File

@ -84,7 +84,7 @@ interface CryptoService {
fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>) fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) suspend fun exportRoomKeys(password: String): ByteArray
fun setRoomBlacklistUnverifiedDevices(roomId: String) fun setRoomBlacklistUnverifiedDevices(roomId: String)

View File

@ -928,14 +928,10 @@ internal class DefaultCryptoService @Inject constructor(
* Export the crypto keys * Export the crypto keys
* *
* @param password the password * @param password the password
* @param callback the exported keys * @return the exported keys
*/ */
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) { override suspend fun exportRoomKeys(password: String): ByteArray {
cryptoCoroutineScope.launch(coroutineDispatchers.main) { return exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
runCatching {
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
}.foldToCallback(callback)
}
} }
/** /**

View File

@ -32,6 +32,7 @@ import im.vector.app.features.call.conference.VectorJitsiActivity
import im.vector.app.features.call.transfer.CallTransferActivity import im.vector.app.features.call.transfer.CallTransferActivity
import im.vector.app.features.createdirect.CreateDirectRoomActivity import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.crypto.verification.VerificationBottomSheet
@ -138,6 +139,7 @@ interface ScreenComponent {
fun inject(activity: LinkHandlerActivity) fun inject(activity: LinkHandlerActivity)
fun inject(activity: MainActivity) fun inject(activity: MainActivity)
fun inject(activity: RoomDirectoryActivity) fun inject(activity: RoomDirectoryActivity)
fun inject(activity: KeysBackupSetupActivity)
fun inject(activity: BugReportActivity) fun inject(activity: BugReportActivity)
fun inject(activity: FilteredRoomsActivity) fun inject(activity: FilteredRoomsActivity)
fun inject(activity: CreateRoomActivity) fun inject(activity: CreateRoomActivity)

View File

@ -18,35 +18,24 @@ package im.vector.app.features.crypto.keys
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.extensions.foldToCallback import javax.inject.Inject
import org.matrix.android.sdk.internal.util.awaitCallback
class KeysExporter(private val session: Session) {
class KeysExporter @Inject constructor(
private val session: Session,
private val context: Context
) {
/** /**
* Export keys and return the file path with the callback * Export keys and write them to the provided uri
*/ */
fun export(context: Context, password: String, uri: Uri, callback: MatrixCallback<Boolean>) { suspend fun export(password: String, uri: Uri) {
session.coroutineScope.launch(Dispatchers.Main) { return withContext(Dispatchers.IO) {
runCatching { val data = session.cryptoService().exportRoomKeys(password)
withContext(Dispatchers.IO) { context.contentResolver.openOutputStream(uri)
val data = awaitCallback<ByteArray> { session.cryptoService().exportRoomKeys(password, it) } ?.use { it.write(data) }
val os = context.contentResolver?.openOutputStream(uri) ?: throw IllegalStateException("Unable to open file for writting")
if (os == null) {
false
} else {
os.write(data)
os.flush()
true
}
}
}.foldToCallback(callback)
} }
} }
} }

View File

@ -18,10 +18,13 @@ package im.vector.app.features.crypto.keysbackup.setup
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.dialogs.ExportKeysDialog import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.queryExportKeys import im.vector.app.core.extensions.queryExportKeys
@ -30,7 +33,8 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.features.crypto.keys.KeysExporter import im.vector.app.features.crypto.keys.KeysExporter
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.launch
import javax.inject.Inject
class KeysBackupSetupActivity : SimpleFragmentActivity() { class KeysBackupSetupActivity : SimpleFragmentActivity() {
@ -38,6 +42,13 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
private lateinit var viewModel: KeysBackupSetupSharedViewModel private lateinit var viewModel: KeysBackupSetupSharedViewModel
@Inject lateinit var keysExporter: KeysExporter
override fun injectWith(injector: ScreenComponent) {
super.injectWith(injector)
injector.inject(this)
}
override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()
if (isFirstCreation()) { if (isFirstCreation()) {
@ -132,30 +143,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener { ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) { override fun onPassphrase(passphrase: String) {
showWaitingView() showWaitingView()
export(passphrase, uri)
KeysExporter(session)
.export(this@KeysBackupSetupActivity,
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
toast(getString(R.string.encryption_exported_successfully))
Intent().apply {
putExtra(MANUAL_EXPORT, true)
}.let {
setResult(Activity.RESULT_OK, it)
finish()
}
}
hideWaitingView()
}
override fun onFailure(failure: Throwable) {
toast(failure.localizedMessage ?: getString(R.string.unexpected_error))
hideWaitingView()
}
})
} }
}) })
} else { } else {
@ -165,6 +153,20 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
} }
} }
private fun export(passphrase: String, uri: Uri) {
lifecycleScope.launch {
try {
keysExporter.export(passphrase, uri)
toast(getString(R.string.encryption_exported_successfully))
setResult(Activity.RESULT_OK, Intent().apply { putExtra(MANUAL_EXPORT, true) })
finish()
} catch (failure: Throwable) {
toast(failure.localizedMessage ?: getString(R.string.unexpected_error))
}
hideWaitingView()
}
}
override fun onBackPressed() { override fun onBackPressed() {
if (viewModel.shouldPromptOnBack) { if (viewModel.shouldPromptOnBack) {
if (waitingView?.isVisible == true) { if (waitingView?.isVisible == true) {

View File

@ -20,6 +20,7 @@ package im.vector.app.features.settings
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -60,6 +61,7 @@ import im.vector.app.features.raw.wellknown.isE2EByDefault
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import kotlinx.coroutines.launch
import me.gujun.android.span.span import me.gujun.android.span.span
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
@ -75,6 +77,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val pinCodeStore: PinCodeStore, private val pinCodeStore: PinCodeStore,
private val keysExporter: KeysExporter,
private val navigator: Navigator private val navigator: Navigator
) : VectorSettingsBaseFragment() { ) : VectorSettingsBaseFragment() {
@ -320,29 +323,24 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
override fun onPassphrase(passphrase: String) { override fun onPassphrase(passphrase: String) {
displayLoadingView() displayLoadingView()
KeysExporter(session) export(passphrase, uri)
.export(requireContext(),
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} else {
requireActivity().toast(getString(R.string.unexpected_error))
}
hideLoadingView()
}
override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}
})
} }
}) })
} }
} }
private fun export(passphrase: String, uri: Uri) {
lifecycleScope.launch {
try {
keysExporter.export(passphrase, uri)
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} catch (failure: Throwable) {
requireActivity().toast(errorFormatter.toHumanReadable(failure))
}
hideLoadingView()
}
}
private val pinActivityResultLauncher = registerStartForActivityResult { private val pinActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) { if (it.resultCode == Activity.RESULT_OK) {
doOpenPinCodePreferenceScreen() doOpenPinCodePreferenceScreen()

View File

@ -42,9 +42,7 @@ import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.recover.SetupMode
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
// TODO this needs to be refactored to current standard and remove legacy // TODO this needs to be refactored to current standard and remove legacy
@ -113,31 +111,6 @@ class SignOutBottomSheetDialogFragment :
views.setupMegolmBackupButton.action = { views.setupMegolmBackupButton.action = {
setupBackupActivityResultLauncher.launch(KeysBackupSetupActivity.intent(requireContext(), true)) setupBackupActivityResultLauncher.launch(KeysBackupSetupActivity.intent(requireContext(), true))
} }
viewModel.observeViewEvents {
when (it) {
is SignoutCheckViewModel.ViewEvents.ExportKeys -> {
it.exporter
.export(requireContext(),
it.passphrase,
it.uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
viewModel.handle(SignoutCheckViewModel.Actions.KeySuccessfullyManuallyExported)
} else {
viewModel.handle(SignoutCheckViewModel.Actions.KeyExportFailed)
}
}
override fun onFailure(failure: Throwable) {
Timber.e("## Failed to export manually keys ${failure.localizedMessage}")
viewModel.handle(SignoutCheckViewModel.Actions.KeyExportFailed)
}
})
}
}
}
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->

View File

@ -17,6 +17,7 @@
package im.vector.app.features.workers.signout package im.vector.app.features.workers.signout
import android.net.Uri import android.net.Uri
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
@ -27,13 +28,14 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.crypto.keys.KeysExporter import im.vector.app.features.crypto.keys.KeysExporter
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
@ -41,6 +43,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_S
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
import timber.log.Timber
data class SignoutCheckViewState( data class SignoutCheckViewState(
val userId: String = "", val userId: String = "",
@ -50,18 +53,15 @@ data class SignoutCheckViewState(
val hasBeenExportedToFile: Async<Boolean> = Uninitialized val hasBeenExportedToFile: Async<Boolean> = Uninitialized
) : MvRxState ) : MvRxState
class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState: SignoutCheckViewState, class SignoutCheckViewModel @AssistedInject constructor(
private val session: Session) @Assisted initialState: SignoutCheckViewState,
: VectorViewModel<SignoutCheckViewState, SignoutCheckViewModel.Actions, SignoutCheckViewModel.ViewEvents>(initialState), KeysBackupStateListener { private val session: Session,
private val keysExporter: KeysExporter
) : VectorViewModel<SignoutCheckViewState, SignoutCheckViewModel.Actions, EmptyViewEvents>(initialState), KeysBackupStateListener {
sealed class Actions : VectorViewModelAction { sealed class Actions : VectorViewModelAction {
data class ExportKeys(val passphrase: String, val uri: Uri) : Actions() data class ExportKeys(val passphrase: String, val uri: Uri) : Actions()
object KeySuccessfullyManuallyExported : Actions() object KeySuccessfullyManuallyExported : Actions()
object KeyExportFailed : Actions()
}
sealed class ViewEvents : VectorViewEvents {
data class ExportKeys(val exporter: KeysExporter, val passphrase: String, val uri: Uri) : ViewEvents()
} }
@AssistedFactory @AssistedFactory
@ -128,22 +128,32 @@ class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState:
override fun handle(action: Actions) { override fun handle(action: Actions) {
when (action) { when (action) {
is Actions.ExportKeys -> { is Actions.ExportKeys -> handleExportKeys(action)
setState {
copy(hasBeenExportedToFile = Loading())
}
_viewEvents.post(ViewEvents.ExportKeys(KeysExporter(session), action.passphrase, action.uri))
}
Actions.KeySuccessfullyManuallyExported -> { Actions.KeySuccessfullyManuallyExported -> {
setState { setState {
copy(hasBeenExportedToFile = Success(true)) copy(hasBeenExportedToFile = Success(true))
} }
} }
Actions.KeyExportFailed -> {
setState {
copy(hasBeenExportedToFile = Uninitialized)
}
}
}.exhaustive }.exhaustive
} }
private fun handleExportKeys(action: Actions.ExportKeys) {
setState {
copy(hasBeenExportedToFile = Loading())
}
viewModelScope.launch {
val newState = try {
keysExporter.export(action.passphrase, action.uri)
Success(true)
} catch (failure: Throwable) {
Timber.e("## Failed to export manually keys ${failure.localizedMessage}")
Uninitialized
}
setState {
copy(hasBeenExportedToFile = newState)
}
}
}
} }