diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 1b7a5243e2..e3f00a24b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody -import kotlin.jvm.Throws interface CryptoService { @@ -82,9 +81,11 @@ interface CryptoService { fun getDeviceTrackingStatus(userId: String): Int - fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback) + suspend fun importRoomKeys(roomKeysAsArray: ByteArray, + password: String, + progressListener: ProgressListener?): ImportRoomKeysResult - fun exportRoomKeys(password: String, callback: MatrixCallback) + suspend fun exportRoomKeys(password: String): ByteArray fun setRoomBlacklistUnverifiedDevices(roomId: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index cbd1ee00a9..d170ae3dd3 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -928,14 +928,10 @@ internal class DefaultCryptoService @Inject constructor( * Export the crypto keys * * @param password the password - * @param callback the exported keys + * @return the exported keys */ - override fun exportRoomKeys(password: String, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT) - }.foldToCallback(callback) - } + override suspend fun exportRoomKeys(password: String): ByteArray { + return exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT) } /** @@ -963,42 +959,37 @@ internal class DefaultCryptoService @Inject constructor( * @param roomKeysAsArray the room keys as array. * @param password the password * @param progressListener the progress listener - * @param callback the asynchronous callback. + * @return the result ImportRoomKeysResult */ - override fun importRoomKeys(roomKeysAsArray: ByteArray, - password: String, - progressListener: ProgressListener?, - callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - withContext(coroutineDispatchers.crypto) { - Timber.v("## CRYPTO | importRoomKeys starts") + override suspend fun importRoomKeys(roomKeysAsArray: ByteArray, + password: String, + progressListener: ProgressListener?): ImportRoomKeysResult { + return withContext(coroutineDispatchers.crypto) { + Timber.v("## CRYPTO | importRoomKeys starts") - val t0 = System.currentTimeMillis() - val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) - val t1 = System.currentTimeMillis() + val t0 = System.currentTimeMillis() + val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) + val t1 = System.currentTimeMillis() - Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms") + Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms") - val importedSessions = MoshiProvider.providesMoshi() - .adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) - .fromJson(roomKeys) + val importedSessions = MoshiProvider.providesMoshi() + .adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) + .fromJson(roomKeys) - val t2 = System.currentTimeMillis() + val t2 = System.currentTimeMillis() - Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms") + Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms") - if (importedSessions == null) { - throw Exception("Error") - } + if (importedSessions == null) { + throw Exception("Error") + } - megolmSessionDataImporter.handle( - megolmSessionsData = importedSessions, - fromBackup = false, - progressListener = progressListener - ) - } - }.foldToCallback(callback) + megolmSessionDataImporter.handle( + megolmSessionsData = importedSessions, + fromBackup = false, + progressListener = progressListener + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index e230599f8f..8cc5d943b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -24,7 +24,6 @@ import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType @@ -168,9 +167,7 @@ internal class DefaultTimeline( timelineEvents.addChangeListener(eventsChangeListener) handleInitialLoad() loadRoomMembersTask - .configureWith(LoadRoomMembersTask.Params(roomId)) { - this.callback = NoOpMatrixCallback() - } + .configureWith(LoadRoomMembersTask.Params(roomId)) .executeBy(taskExecutor) // Ensure ReadReceipt from init sync are loaded diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt index 39b7967bc1..b76829e893 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt @@ -18,13 +18,13 @@ package org.matrix.android.sdk.internal.session.room.typing import android.os.SystemClock import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory -import org.matrix.android.sdk.api.MatrixCallback +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.room.typing.TypingService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import timber.log.Timber /** @@ -35,7 +35,6 @@ import timber.log.Timber */ internal class DefaultTypingService @AssistedInject constructor( @Assisted private val roomId: String, - private val taskExecutor: TaskExecutor, private val sendTypingTask: SendTypingTask ) : TypingService { @@ -44,8 +43,8 @@ internal class DefaultTypingService @AssistedInject constructor( fun create(roomId: String): DefaultTypingService } - private var currentTask: Cancelable? = null - private var currentAutoStopTask: Cancelable? = null + private val coroutineScope = CoroutineScope(Job()) + private var currentTask: Job? = null // What the homeserver knows private var userIsTyping = false @@ -53,26 +52,24 @@ internal class DefaultTypingService @AssistedInject constructor( // Last time the user is typing event has been sent private var lastRequestTimestamp: Long = 0 + /** + * Notify to the server that the user is typing and schedule the auto typing off + */ override fun userIsTyping() { - scheduleAutoStop() - val now = SystemClock.elapsedRealtime() - - if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) { - Timber.d("Typing: Skip start request") - return - } - - Timber.d("Typing: Send start request") - userIsTyping = true - lastRequestTimestamp = now - currentTask?.cancel() - - val params = SendTypingTask.Params(roomId, true) - currentTask = sendTypingTask - .configureWith(params) - .executeBy(taskExecutor) + currentTask = coroutineScope.launch { + if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) { + Timber.d("Typing: Skip start request") + } else { + Timber.d("Typing: Send start request") + lastRequestTimestamp = now + sendRequest(true) + } + delay(MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS) + Timber.d("Typing: auto stop") + sendRequest(false) + } } override fun userStopsTyping() { @@ -82,35 +79,22 @@ internal class DefaultTypingService @AssistedInject constructor( } Timber.d("Typing: Send stop request") - userIsTyping = false lastRequestTimestamp = 0 - currentAutoStopTask?.cancel() currentTask?.cancel() - - val params = SendTypingTask.Params(roomId, false) - currentTask = sendTypingTask - .configureWith(params) - .executeBy(taskExecutor) + currentTask = coroutineScope.launch { + sendRequest(false) + } } - private fun scheduleAutoStop() { - Timber.d("Typing: Schedule auto stop") - currentAutoStopTask?.cancel() - - val params = SendTypingTask.Params( - roomId, - false, - delay = MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS) - currentAutoStopTask = sendTypingTask - .configureWith(params) { - callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - userIsTyping = false - } - } - } - .executeBy(taskExecutor) + private suspend fun sendRequest(isTyping: Boolean) { + try { + sendTypingTask.execute(SendTypingTask.Params(roomId, isTyping)) + userIsTyping = isTyping + } catch (failure: Throwable) { + // Ignore network error, etc... + Timber.w(failure, "Unable to send typing request") + } } companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt index 0b0df74311..0bdceb9ade 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt @@ -17,11 +17,10 @@ package org.matrix.android.sdk.internal.session.room.typing import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import kotlinx.coroutines.delay -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import javax.inject.Inject internal interface SendTypingTask : Task { @@ -29,9 +28,7 @@ internal interface SendTypingTask : Task { data class Params( val roomId: String, val isTyping: Boolean, - val typingTimeoutMillis: Int? = 30_000, - // Optional delay before sending the request to the homeserver - val delay: Long? = null + val typingTimeoutMillis: Int? = 30_000 ) } @@ -42,8 +39,6 @@ internal class DefaultSendTypingTask @Inject constructor( ) : SendTypingTask { override suspend fun execute(params: SendTypingTask.Params) { - delay(params.delay ?: -1) - executeRequest(globalErrorReceiver) { roomAPI.sendTypingState( params.roomId, diff --git a/newsfragment/2449.feature b/newsfragment/2449.feature new file mode 100644 index 0000000000..7fca1730b6 --- /dev/null +++ b/newsfragment/2449.feature @@ -0,0 +1 @@ +Migrate DefaultTypingService, KeysImporter and KeysExporter to coroutines \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 9dbfa8fe30..3c11bfcd13 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -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.createdirect.CreateDirectRoomActivity 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.recover.BootstrapBottomSheet import im.vector.app.features.crypto.verification.VerificationBottomSheet @@ -138,6 +139,7 @@ interface ScreenComponent { fun inject(activity: LinkHandlerActivity) fun inject(activity: MainActivity) fun inject(activity: RoomDirectoryActivity) + fun inject(activity: KeysBackupSetupActivity) fun inject(activity: BugReportActivity) fun inject(activity: FilteredRoomsActivity) fun inject(activity: CreateRoomActivity) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index c7e4c26385..2c66a14cb4 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -18,35 +18,24 @@ package im.vector.app.features.crypto.keys import android.content.Context import android.net.Uri -import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.extensions.foldToCallback -import org.matrix.android.sdk.internal.util.awaitCallback - -class KeysExporter(private val session: Session) { +import javax.inject.Inject +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) { - session.coroutineScope.launch(Dispatchers.Main) { - runCatching { - withContext(Dispatchers.IO) { - val data = awaitCallback { session.cryptoService().exportRoomKeys(password, it) } - val os = context.contentResolver?.openOutputStream(uri) - if (os == null) { - false - } else { - os.write(data) - os.flush() - true - } - } - }.foldToCallback(callback) + suspend fun export(password: String, uri: Uri) { + return withContext(Dispatchers.IO) { + val data = session.cryptoService().exportRoomKeys(password) + context.contentResolver.openOutputStream(uri) + ?.use { it.write(data) } + ?: throw IllegalStateException("Unable to open file for writting") } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt index 3d93b26edd..50c85c3e5f 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt @@ -20,49 +20,27 @@ import android.content.Context import android.net.Uri import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.resources.openResource -import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.extensions.foldToCallback -import org.matrix.android.sdk.internal.util.awaitCallback -import timber.log.Timber - -class KeysImporter(private val session: Session) { +import javax.inject.Inject +class KeysImporter @Inject constructor( + private val context: Context, + private val session: Session +) { /** * Import keys from provided Uri */ - fun import(context: Context, - uri: Uri, - mimetype: String?, - password: String, - callback: MatrixCallback) { - session.coroutineScope.launch(Dispatchers.Main) { - runCatching { - withContext(Dispatchers.IO) { - val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri)) - - if (resource?.mContentStream == null) { - throw Exception("Error") - } - - val data: ByteArray - try { - data = resource.mContentStream!!.use { it.readBytes() } - } catch (e: Exception) { - Timber.e(e, "## importKeys()") - throw e - } - - awaitCallback { - session.cryptoService().importRoomKeys(data, password, null, it) - } - } - }.foldToCallback(callback) + suspend fun import(uri: Uri, + mimetype: String?, + password: String): ImportRoomKeysResult { + return withContext(Dispatchers.IO) { + val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri)) + val stream = resource?.mContentStream ?: throw Exception("Error") + val data = stream.use { it.readBytes() } + session.cryptoService().importRoomKeys(data, password, null) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt index 586c461fac..7cc46ef62c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt @@ -18,10 +18,13 @@ package im.vector.app.features.crypto.keysbackup.setup import android.app.Activity import android.content.Context import android.content.Intent +import android.net.Uri import androidx.core.view.isVisible import androidx.fragment.app.FragmentManager +import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R +import im.vector.app.core.di.ScreenComponent import im.vector.app.core.dialogs.ExportKeysDialog import im.vector.app.core.extensions.observeEvent 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.utils.toast 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() { @@ -38,6 +42,13 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() { private lateinit var viewModel: KeysBackupSetupSharedViewModel + @Inject lateinit var keysExporter: KeysExporter + + override fun injectWith(injector: ScreenComponent) { + super.injectWith(injector) + injector.inject(this) + } + override fun initUiAndData() { super.initUiAndData() if (isFirstCreation()) { @@ -132,30 +143,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() { ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener { override fun onPassphrase(passphrase: String) { showWaitingView() - - KeysExporter(session) - .export(this@KeysBackupSetupActivity, - passphrase, - uri, - object : MatrixCallback { - 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() - } - }) + export(passphrase, uri) } }) } 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() { if (viewModel.shouldPromptOnBack) { if (waitingView?.isVisible == true) { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 4fd2c72b33..bb946ace7d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -20,6 +20,7 @@ package im.vector.app.features.settings import android.annotation.SuppressLint import android.app.Activity import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup @@ -60,11 +61,11 @@ import im.vector.app.features.raw.wellknown.isE2EByDefault import im.vector.app.features.themes.ThemeUtils import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable +import kotlinx.coroutines.launch import me.gujun.android.span.span import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.rx.SecretsSynchronisationInfo @@ -75,6 +76,8 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( private val vectorPreferences: VectorPreferences, private val activeSessionHolder: ActiveSessionHolder, private val pinCodeStore: PinCodeStore, + private val keysExporter: KeysExporter, + private val keysImporter: KeysImporter, private val navigator: Navigator ) : VectorSettingsBaseFragment() { @@ -320,29 +323,24 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( override fun onPassphrase(passphrase: String) { displayLoadingView() - KeysExporter(session) - .export(requireContext(), - passphrase, - uri, - object : MatrixCallback { - 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) - } - }) + export(passphrase, uri) } }) } } + 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 { if (it.resultCode == Activity.RESULT_OK) { doOpenPinCodePreferenceScreen() @@ -474,34 +472,25 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( displayLoadingView() - KeysImporter(session) - .import(requireContext(), - uri, - mimetype, - password, - object : MatrixCallback { - override fun onSuccess(data: ImportRoomKeysResult) { - if (!isAdded) { - return - } - - hideLoadingView() - - MaterialAlertDialogBuilder(thisActivity) - .setMessage(resources.getQuantityString(R.plurals.encryption_import_room_keys_success, - data.successfullyNumberOfImportedKeys, - data.successfullyNumberOfImportedKeys, - data.totalNumberOfKeys)) - .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } - .show() - } - - override fun onFailure(failure: Throwable) { - appContext.toast(failure.localizedMessage ?: getString(R.string.unexpected_error)) - hideLoadingView() - } - }) + lifecycleScope.launch { + val data = try { + keysImporter.import(uri, mimetype, password) + } catch (failure: Throwable) { + appContext.toast(errorFormatter.toHumanReadable(failure)) + null + } + hideLoadingView() + if (data != null) { + MaterialAlertDialogBuilder(thisActivity) + .setMessage(resources.getQuantityString(R.plurals.encryption_import_room_keys_success, + data.successfullyNumberOfImportedKeys, + data.successfullyNumberOfImportedKeys, + data.totalNumberOfKeys)) + .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } + .show() + } + } importDialog.dismiss() } } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt index 389d0f0cfc..48459cdd9e 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt @@ -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.SetupMode -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState -import timber.log.Timber import javax.inject.Inject // TODO this needs to be refactored to current standard and remove legacy @@ -113,31 +111,6 @@ class SignOutBottomSheetDialogFragment : views.setupMegolmBackupButton.action = { setupBackupActivityResultLauncher.launch(KeysBackupSetupActivity.intent(requireContext(), true)) } - - viewModel.observeViewEvents { - when (it) { - is SignoutCheckViewModel.ViewEvents.ExportKeys -> { - it.exporter - .export(requireContext(), - it.passphrase, - it.uri, - object : MatrixCallback { - 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 -> diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index 21c0c7481a..df7a826b48 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.workers.signout import android.net.Uri +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -27,13 +28,14 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject 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.VectorViewModelAction 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.crypto.crosssigning.MASTER_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.KeysBackupStateListener import org.matrix.android.sdk.rx.rx +import timber.log.Timber data class SignoutCheckViewState( val userId: String = "", @@ -50,18 +53,15 @@ data class SignoutCheckViewState( val hasBeenExportedToFile: Async = Uninitialized ) : MvRxState -class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState: SignoutCheckViewState, - private val session: Session) - : VectorViewModel(initialState), KeysBackupStateListener { +class SignoutCheckViewModel @AssistedInject constructor( + @Assisted initialState: SignoutCheckViewState, + private val session: Session, + private val keysExporter: KeysExporter +) : VectorViewModel(initialState), KeysBackupStateListener { sealed class Actions : VectorViewModelAction { data class ExportKeys(val passphrase: String, val uri: Uri) : 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 @@ -128,22 +128,32 @@ class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState: override fun handle(action: Actions) { when (action) { - is Actions.ExportKeys -> { - setState { - copy(hasBeenExportedToFile = Loading()) - } - _viewEvents.post(ViewEvents.ExportKeys(KeysExporter(session), action.passphrase, action.uri)) - } + is Actions.ExportKeys -> handleExportKeys(action) Actions.KeySuccessfullyManuallyExported -> { setState { copy(hasBeenExportedToFile = Success(true)) } } - Actions.KeyExportFailed -> { - setState { - copy(hasBeenExportedToFile = Uninitialized) - } - } }.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) + } + } + } }