diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index 3700bbf46f..3c205d5013 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.handler import androidx.work.BackoffPolicy import androidx.work.ExistingWorkPolicy import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorkerDataRepository import org.matrix.android.sdk.internal.di.SessionId @@ -81,7 +82,9 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( } } if (hasUpdate) { - updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats)) + tryOrNull("Unable to update user account data") { + updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats)) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt index 92ebb41ad9..bc49682565 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt @@ -20,6 +20,7 @@ import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmList import io.realm.kotlin.where +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.InitialSyncRequestReason import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent @@ -122,7 +123,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( val updateUserAccountParams = UpdateUserAccountDataTask.DirectChatParams( directMessages = directChats ) - updateUserAccountDataTask.execute(updateUserAccountParams) + tryOrNull("Unable to update user account data") { updateUserAccountDataTask.execute(updateUserAccountParams) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt index 395b4d0475..fd61bdd5dd 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt @@ -20,6 +20,7 @@ import im.vector.app.core.platform.VectorViewModelAction import java.io.OutputStream sealed class BootstrapActions : VectorViewModelAction { + object Retry : BootstrapActions() // Navigation diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt index be02737ef8..586f60beaf 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt @@ -212,6 +212,11 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment { + views.bootstrapIcon.isVisible = true + views.bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title) + showFragment(BootstrapErrorFragment::class) + } } super.invalidate() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapErrorFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapErrorFragment.kt new file mode 100644 index 0000000000..26b29d449a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapErrorFragment.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 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.app.features.crypto.recover + +import android.view.LayoutInflater +import android.view.ViewGroup +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentBootstrapErrorBinding + +@AndroidEntryPoint +class BootstrapErrorFragment : + VectorBaseFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapErrorBinding { + return FragmentBootstrapErrorBinding.inflate(inflater, container, false) + } + + val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() + + override fun invalidate() = withState(sharedViewModel) { state -> + when (state.step) { + is BootstrapStep.Error -> { + views.bootstrapDescriptionText.setTextOrHide(errorFormatter.toHumanReadable(state.step.error)) + } + else -> { + // Should not happen, show a generic error + views.bootstrapDescriptionText.setTextOrHide(getString(R.string.unknown_error)) + } + } + views.bootstrapRetryButton.onClick { + sharedViewModel.handle(BootstrapActions.Retry) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index b3d83b948b..c9c2c5ce9a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -54,6 +54,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromR import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth +import timber.log.Timber import java.io.OutputStream import kotlin.coroutines.Continuation import kotlin.coroutines.resumeWithException @@ -118,37 +119,48 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } SetupMode.NORMAL -> { - // need to check if user have an existing keybackup - setState { - copy(step = BootstrapStep.CheckingMigration) - } + checkMigration() + } + } + } - // We need to check if there is an existing backup - viewModelScope.launch(Dispatchers.IO) { - val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }?.toKeysVersionResult() - if (version == null) { - // we just resume plain bootstrap - doesKeyBackupExist = false + private fun checkMigration() { + // need to check if user have an existing keybackup + setState { + copy(step = BootstrapStep.CheckingMigration) + } + + // We need to check if there is an existing backup + viewModelScope.launch(Dispatchers.IO) { + try { + val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }?.toKeysVersionResult() + if (version == null) { + // we just resume plain bootstrap + doesKeyBackupExist = false + setState { + copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist, methods = this.secureBackupMethod)) + } + } else { + // we need to get existing backup passphrase/key and convert to SSSS + val keyVersion = tryOrNull { + session.cryptoService().keysBackupService().getVersion(version.version) + } + if (keyVersion == null) { + // strange case... just finish? + _viewEvents.post(BootstrapViewEvents.Dismiss(false)) + } else { + doesKeyBackupExist = true + isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null setState { copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist, methods = this.secureBackupMethod)) } - } else { - // we need to get existing backup passphrase/key and convert to SSSS - val keyVersion = tryOrNull { - session.cryptoService().keysBackupService().getVersion(version.version) - } - if (keyVersion == null) { - // strange case... just finish? - _viewEvents.post(BootstrapViewEvents.Dismiss(false)) - } else { - doesKeyBackupExist = true - isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null - setState { - copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist, methods = this.secureBackupMethod)) - } - } } } + } catch (failure: Throwable) { + Timber.e(failure, "Error while checking key backup") + setState { + copy(step = BootstrapStep.Error(failure)) + } } } } @@ -268,6 +280,9 @@ class BootstrapSharedViewModel @AssistedInject constructor( copy(step = BootstrapStep.AccountReAuth(stringProvider.getString(R.string.authentication_error))) } } + BootstrapActions.Retry -> { + checkMigration() + } } } @@ -568,6 +583,12 @@ class BootstrapSharedViewModel @AssistedInject constructor( ) } } + is BootstrapStep.Error -> { + // do we let you cancel from here? + if (state.canLeave) { + _viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null)) + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapStep.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapStep.kt index 3975f0e9a2..b807acc0ba 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapStep.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapStep.kt @@ -105,6 +105,8 @@ sealed class BootstrapStep { object Initializing : BootstrapStep() data class SaveRecoveryKey(val isSaved: Boolean) : BootstrapStep() object DoneSuccess : BootstrapStep() + + data class Error(val error: Throwable) : BootstrapStep() } fun BootstrapStep.GetBackupSecretForMigration.useKey(): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index 1fa07329f3..dceb92f6e2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -71,6 +71,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerC import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap +import timber.log.Timber import java.io.File import java.net.URL import java.util.UUID @@ -265,7 +266,17 @@ class VectorSettingsGeneralFragment : // Disable it while updating the state, will be re-enabled by the account data listener. it.isEnabled = false lifecycleScope.launch { - session.integrationManagerService().setIntegrationEnabled(newValue as Boolean) + try { + session.integrationManagerService().setIntegrationEnabled(newValue as Boolean) + } catch (failure: Throwable) { + Timber.e(failure, "Failed to update integration manager state") + activity?.let { activity -> + Toast.makeText(activity, errorFormatter.toHumanReadable(failure), Toast.LENGTH_SHORT).show() + } + // Restore the previous state + it.isChecked = !it.isChecked + it.isEnabled = true + } } true } diff --git a/vector/src/main/res/layout/fragment_bootstrap_error.xml b/vector/src/main/res/layout/fragment_bootstrap_error.xml new file mode 100644 index 0000000000..02c944fed2 --- /dev/null +++ b/vector/src/main/res/layout/fragment_bootstrap_error.xml @@ -0,0 +1,33 @@ + + + + + +