diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..e552f5fd43 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Updates for Github Actions used in the repo + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + # Updates for Gradle dependencies used in the app + - package-ecosystem: gradle + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 200 + reviewers: + - "bmarty" +### ignore: +### - dependency-name: com.squareup.okhttp3:logging-interceptor +### versions: "> 3.12.10" diff --git a/CHANGES.md b/CHANGES.md index 68f993f928..d09a8600b9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,34 @@ -Changes in Element 1.0.18 (2021-XX-XX) +Changes in Element 1.1.1 (2021-XX-XX) =================================================== Features ✨: - Allow changing nick colors (#2610) + +Improvements 🙌: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Test: + - + +Other changes: + - + +Changes in Element 1.1.0 (2021-02-19) +=================================================== + +Features ✨: - VoIP : support for VoIP V1 protocol, transfer call and dial-pad Improvements 🙌: @@ -15,18 +41,11 @@ Bugfix 🐛: - VoIP : fix audio devices output - Fix crash after initial sync on Dendrite - Fix crash reported by PlayStore (#2707) - -Translations 🗣: - - + - Ignore url override from credential if it is not valid (#2822) + - Fix crash when deactivating an account SDK API changes ⚠️: - - - -Build 🧱: - - - -Test: - - + - Migrate AuthenticationService API to coroutines (#2449) Other changes: - New Dev Tools panel for developers @@ -1204,7 +1223,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a ======================================================= -Changes in Element 1.X.X (2021-XX-XX) +Changes in Element 1.1.X (2021-XX-XX) =================================================== Features ✨: diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle index 5ce9f1eff6..a92eb11212 100644 --- a/attachment-viewer/build.gradle +++ b/attachment-viewer/build.gradle @@ -71,5 +71,5 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.recyclerview:recyclerview:1.1.0" - implementation 'com.google.android.material:material:1.2.1' + implementation 'com.google.android.material:material:1.3.0' } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3da87093ec..18a0564943 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ buildscript { classpath 'com.android.tools.build:gradle:4.1.2' classpath 'com.google.gms:google-services:4.3.5' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1' + classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2' // NOTE: Do not place your application dependencies here; they belong diff --git a/fastlane/metadata/android/en-US/changelogs/40101000.txt b/fastlane/metadata/android/en-US/changelogs/40101000.txt new file mode 100644 index 0000000000..ef64bd99a5 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40101000.txt @@ -0,0 +1,2 @@ +Main changes in this version: VoIP (audio and video calls in DM) improvement and bug fixes! +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.0 \ No newline at end of file diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index dca19e7755..3d2bc53637 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.1.2" + classpath "io.realm:realm-gradle-plugin:10.3.1" } } @@ -112,9 +112,9 @@ dependencies { def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.31' + def daggerVersion = '2.32' def work_version = '2.4.0' - def retrofit_version = '2.6.2' + def retrofit_version = '2.9.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" @@ -130,7 +130,7 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" - implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1")) + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1")) implementation 'com.squareup.okhttp3:okhttp' implementation 'com.squareup.okhttp3:logging-interceptor' implementation 'com.squareup.okhttp3:okhttp-urlconnection' @@ -141,7 +141,7 @@ dependencies { implementation "ru.noties.markwon:core:$markwon_version" // Image - implementation 'androidx.exifinterface:exifinterface:1.3.1' + implementation 'androidx.exifinterface:exifinterface:1.3.2' // Database implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' @@ -155,7 +155,7 @@ dependencies { implementation "io.arrow-kt:arrow-instances-core:$arrow_version" // olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm - implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2' + implementation 'org.matrix.gitlab.matrix-org:olm:3.2.1' // DI implementation "com.google.dagger:dagger:$daggerVersion" @@ -166,14 +166,14 @@ dependencies { implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.18' - testImplementation 'junit:junit:4.13' + testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.3' //testImplementation 'org.robolectric:shadows-support-v4:3.0' // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 testImplementation 'io.mockk:mockk:1.9.2.kotlin12' - testImplementation 'org.amshove.kluent:kluent-android:1.61' + testImplementation 'org.amshove.kluent:kluent-android:1.65' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt index b0df6fcb44..01c4f8ccb3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt @@ -26,15 +26,12 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth -import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse -import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.common.TestMatrixCallback import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -46,12 +43,13 @@ class DeactivateAccountTest : InstrumentedTest { @Test fun deactivateAccountTest() { - val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) + val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) // Deactivate the account commonTestHelper.runBlockingTest { session.deactivateAccount( - object : UserInteractiveAuthInterceptor { + eraseAllData = false, + userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { promise.resume( UserPasswordAuth( @@ -61,7 +59,8 @@ class DeactivateAccountTest : InstrumentedTest { ) ) } - }, false) + } + ) } // Try to login on the previous account, it will fail (M_USER_DEACTIVATED) @@ -75,23 +74,23 @@ class DeactivateAccountTest : InstrumentedTest { // Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE) val hs = commonTestHelper.createHomeServerConfig() - commonTestHelper.doSync { - commonTestHelper.matrix.authenticationService.getLoginFlow(hs, it) + commonTestHelper.runBlockingTest { + commonTestHelper.matrix.authenticationService.getLoginFlow(hs) } var accountCreationError: Throwable? = null - commonTestHelper.waitWithLatch { - commonTestHelper.matrix.authenticationService - .getRegistrationWizard() - .createAccount(session.myUserId.substringAfter("@").substringBefore(":"), - TestConstants.PASSWORD, - null, - object : TestMatrixCallback(it, false) { - override fun onFailure(failure: Throwable) { - accountCreationError = failure - super.onFailure(failure) - } - }) + commonTestHelper.runBlockingTest { + try { + commonTestHelper.matrix.authenticationService + .getRegistrationWizard() + .createAccount( + session.myUserId.substringAfter("@").substringBefore(":"), + TestConstants.PASSWORD, + null + ) + } catch (failure: Throwable) { + accountCreationError = failure + } } // Test the error diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index a4dbd70b11..c677d91f0a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -210,22 +209,21 @@ class CommonTestHelper(context: Context) { sessionTestParams: SessionTestParams): Session { val hs = createHomeServerConfig() - doSync { - matrix.authenticationService - .getLoginFlow(hs, it) + runBlockingTest { + matrix.authenticationService.getLoginFlow(hs) } - doSync(timeout = 60_000) { + runBlockingTest(timeout = 60_000) { matrix.authenticationService .getRegistrationWizard() - .createAccount(userName, password, null, it) + .createAccount(userName, password, null) } // Perform dummy step - val registrationResult = doSync(timeout = 60_000) { + val registrationResult = runBlockingTest(timeout = 60_000) { matrix.authenticationService .getRegistrationWizard() - .dummy(it) + .dummy() } assertTrue(registrationResult is RegistrationResult.Success) @@ -249,15 +247,14 @@ class CommonTestHelper(context: Context) { sessionTestParams: SessionTestParams): Session { val hs = createHomeServerConfig() - doSync { - matrix.authenticationService - .getLoginFlow(hs, it) + runBlockingTest { + matrix.authenticationService.getLoginFlow(hs) } - val session = doSync { + val session = runBlockingTest { matrix.authenticationService .getLoginWizard() - .login(userName, password, "myDevice", it) + .login(userName, password, "myDevice") } if (sessionTestParams.withInitialSync) { @@ -277,21 +274,19 @@ class CommonTestHelper(context: Context) { password: String): Throwable { val hs = createHomeServerConfig() - doSync { - matrix.authenticationService - .getLoginFlow(hs, it) + runBlockingTest { + matrix.authenticationService.getLoginFlow(hs) } var requestFailure: Throwable? = null - waitWithLatch { latch -> - matrix.authenticationService - .getLoginWizard() - .login(userName, password, "myDevice", object : TestMatrixCallback(latch, onlySuccessful = false) { - override fun onFailure(failure: Throwable) { - requestFailure = failure - super.onFailure(failure) - } - }) + runBlockingTest { + try { + matrix.authenticationService + .getLoginWizard() + .login(userName, password, "myDevice") + } catch (failure: Throwable) { + requestFailure = failure + } } assertNotNull(requestFailure) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt index ae300c936d..cadb83ca00 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt @@ -61,7 +61,7 @@ class SearchMessagesTest : InstrumentedTest { 2) run { - var lock = CountDownLatch(1) + val lock = CountDownLatch(1) val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> snapshot.count { it.root.content.toModel()?.body?.startsWith(MESSAGE).orFalse() } == 2 @@ -70,7 +70,6 @@ class SearchMessagesTest : InstrumentedTest { aliceTimeline.addListener(eventListener) commonTestHelper.await(lock) - lock = CountDownLatch(1) val data = commonTestHelper.runBlockingTest { aliceSession .searchService() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index bf21941e0c..a7f5163774 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.auth -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowResult @@ -24,7 +23,6 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.Cancelable /** * This interface defines methods to authenticate or to create an account to a matrix server. @@ -32,14 +30,14 @@ import org.matrix.android.sdk.api.util.Cancelable interface AuthenticationService { /** * Request the supported login flows for this homeserver. - * This is the first method to call to be able to get a wizard to login or the create an account + * This is the first method to call to be able to get a wizard to login or to create an account */ - fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback): Cancelable + suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult /** * Request the supported login flows for the corresponding sessionId. */ - fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback): Cancelable + suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult /** * Get a SSO url @@ -69,12 +67,12 @@ interface AuthenticationService { /** * Cancel pending login or pending registration */ - fun cancelPendingLoginOrRegistration() + suspend fun cancelPendingLoginOrRegistration() /** * Reset all pending settings, including current HomeServerConnectionConfig */ - fun reset() + suspend fun reset() /** * Check if there is an authenticated [Session]. @@ -91,24 +89,21 @@ interface AuthenticationService { /** * Create a session after a SSO successful login */ - fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, - credentials: Credentials, - callback: MatrixCallback): Cancelable + suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, + credentials: Credentials): Session /** * Perform a wellknown request, using the domain from the matrixId */ - fun getWellKnownData(matrixId: String, - homeServerConnectionConfig: HomeServerConnectionConfig?, - callback: MatrixCallback): Cancelable + suspend fun getWellKnownData(matrixId: String, + homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult /** * Authenticate with a matrixId and a password * Usually call this after a successful call to getWellKnownData() */ - fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, - matrixId: String, - password: String, - initialDeviceName: String, - callback: MatrixCallback): Cancelable + suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, + matrixId: String, + password: String, + initialDeviceName: String): Session } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt index 48705ee7b7..9c96cba40c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.auth.login -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.Cancelable @@ -29,26 +28,23 @@ interface LoginWizard { * @param callback the matrix callback on which you'll receive the result of authentication. * @return a [Cancelable] */ - fun login(login: String, - password: String, - deviceName: String, - callback: MatrixCallback): Cancelable + suspend fun login(login: String, + password: String, + deviceName: String): Session /** * Exchange a login token to an access token */ - fun loginWithToken(loginToken: String, - callback: MatrixCallback): Cancelable + suspend fun loginWithToken(loginToken: String): Session /** * Reset user password */ - fun resetPassword(email: String, - newPassword: String, - callback: MatrixCallback): Cancelable + suspend fun resetPassword(email: String, + newPassword: String) /** - * Confirm the new password, once the user has checked his email + * Confirm the new password, once the user has checked their email */ - fun resetPasswordMailConfirmed(callback: MatrixCallback): Cancelable + suspend fun resetPasswordMailConfirmed() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt index ed7b249f1e..d00c9a0c82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt @@ -16,28 +16,25 @@ package org.matrix.android.sdk.api.auth.registration -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - interface RegistrationWizard { - fun getRegistrationFlow(callback: MatrixCallback): Cancelable + suspend fun getRegistrationFlow(): RegistrationResult - fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback): Cancelable + suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult - fun performReCaptcha(response: String, callback: MatrixCallback): Cancelable + suspend fun performReCaptcha(response: String): RegistrationResult - fun acceptTerms(callback: MatrixCallback): Cancelable + suspend fun acceptTerms(): RegistrationResult - fun dummy(callback: MatrixCallback): Cancelable + suspend fun dummy(): RegistrationResult - fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback): Cancelable + suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult - fun sendAgainThreePid(callback: MatrixCallback): Cancelable + suspend fun sendAgainThreePid(): RegistrationResult - fun handleValidateThreePid(code: String, callback: MatrixCallback): Cancelable + suspend fun handleValidateThreePid(code: String): RegistrationResult - fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback): Cancelable + suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult val currentThreePid: String? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index c06cdd9e23..e0ee9f36ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -53,22 +53,24 @@ fun Throwable.isInvalidUIAAuth(): Boolean { * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible */ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { - return if (this is Failure.OtherServerError && this.httpCode == 401) { + return if (this is Failure.OtherServerError && httpCode == 401) { tryOrNull { MoshiProvider.providesMoshi() .adapter(RegistrationFlowResponse::class.java) - .fromJson(this.errorBody) + .fromJson(errorBody) } - } else if (this is Failure.ServerError && this.httpCode == 401 && this.error.code == MatrixError.M_FORBIDDEN) { + } else if (this is Failure.ServerError && httpCode == 401 && error.code == MatrixError.M_FORBIDDEN) { // This happens when the submission for this stage was bad (like bad password) - if (this.error.session != null && this.error.flows != null) { + if (error.session != null && error.flows != null) { RegistrationFlowResponse( - flows = this.error.flows, - session = this.error.session, - completedStages = this.error.completedStages, - params = this.error.params + flows = error.flows, + session = error.session, + completedStages = error.completedStages, + params = error.params ) - } else null + } else { + null + } } else { null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt index eb327dfd56..1f28dbd8af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt @@ -27,7 +27,8 @@ interface AccountService { * @param password Current password. * @param newPassword New password */ - suspend fun changePassword(password: String, newPassword: String) + suspend fun changePassword(password: String, + newPassword: String) /** * Deactivate the account. @@ -41,9 +42,10 @@ interface AccountService { * be shared with any new or unregistered users, but registered users who already have access to these messages will still * have access to their copy. * - * @param password the account password * @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see * an incomplete view of conversations + * @param userInteractiveAuthInterceptor see [UserInteractiveAuthInterceptor] */ - suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) + suspend fun deactivateAccount(eraseAllData: Boolean, + userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) } 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 fa5ea359e8..eead9b4ab7 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 @@ -56,8 +56,6 @@ interface CryptoService { fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback) - fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback) - fun getCryptoVersion(context: Context, longFormat: Boolean): String fun isCryptoEnabled(): Boolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt index 2ec8900f7c..bb62dbbfe9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt @@ -20,7 +20,9 @@ import android.content.Context import dagger.Binds import dagger.Module import dagger.Provides +import io.realm.RealmConfiguration import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.legacy.LegacySessionImporter import org.matrix.android.sdk.internal.auth.db.AuthRealmMigration import org.matrix.android.sdk.internal.auth.db.AuthRealmModule @@ -32,8 +34,6 @@ import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.di.AuthDatabase import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter import org.matrix.android.sdk.internal.wellknown.WellknownModule -import io.realm.RealmConfiguration -import org.matrix.android.sdk.api.auth.HomeServerHistoryService import java.io.File @Module(includes = [WellknownModule::class]) @@ -82,6 +82,9 @@ internal abstract class AuthModule { @Binds abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask + @Binds + abstract fun bindIsValidClientServerApiTask(task: DefaultIsValidClientServerApiTask): IsValidClientServerApiTask + @Binds abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index c99e9bd81c..4f3451cf30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -18,10 +18,7 @@ package org.matrix.android.sdk.internal.auth import android.net.Uri import dagger.Lazy -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import okhttp3.OkHttpClient -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -32,8 +29,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse @@ -50,11 +45,6 @@ import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.task.launchToCallback -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.wellknown.GetWellknownTask import javax.inject.Inject import javax.net.ssl.HttpsURLConnection @@ -63,14 +53,12 @@ internal class DefaultAuthenticationService @Inject constructor( @Unauthenticated private val okHttpClient: Lazy, private val retrofitFactory: RetrofitFactory, - private val coroutineDispatchers: MatrixCoroutineDispatchers, private val sessionParamsStore: SessionParamsStore, private val sessionManager: SessionManager, private val sessionCreator: SessionCreator, private val pendingSessionStore: PendingSessionStore, private val getWellknownTask: GetWellknownTask, - private val directLoginTask: DirectLoginTask, - private val taskExecutor: TaskExecutor + private val directLoginTask: DirectLoginTask ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -89,15 +77,11 @@ internal class DefaultAuthenticationService @Inject constructor( } } - override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback): Cancelable { + override suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult { val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig + ?: throw IllegalStateException("Session not found") - return if (homeServerConnectionConfig == null) { - callback.onFailure(IllegalStateException("Session not found")) - NoOpCancellable - } else { - getLoginFlow(homeServerConnectionConfig, callback) - } + return getLoginFlow(homeServerConnectionConfig) } override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { @@ -146,70 +130,70 @@ internal class DefaultAuthenticationService @Inject constructor( ?.trim { it == '/' } } - override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback): Cancelable { + /** + * This is the entry point of the authentication service. + * homeServerConnectionConfig contains a homeserver URL probably entered by the user, which can be a + * valid homeserver API url, the url of Element Web, or anything else. + */ + override suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { pendingSessionData = null - return taskExecutor.executorScope.launch(coroutineDispatchers.main) { - pendingSessionStore.delete() + pendingSessionStore.delete() - val result = runCatching { - getLoginFlowInternal(homeServerConnectionConfig) - } - result.fold( - { - if (it is LoginFlowResult.Success) { - // The homeserver exists and up to date, keep the config - // Homeserver url may have been changed, if it was a Riot url - val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(it.homeServerUrl) - ) - - pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) - .also { data -> pendingSessionStore.savePendingSessionData(data) } - } - callback.onSuccess(it) - }, - { - if (it is UnrecognizedCertificateException) { - callback.onFailure(Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)) - } else { - callback.onFailure(it) - } - } - ) + val result = runCatching { + getLoginFlowInternal(homeServerConnectionConfig) } - .toCancelable() + return result.fold( + { + if (it is LoginFlowResult.Success) { + // The homeserver exists and up to date, keep the config + // Homeserver url may have been changed, if it was a Riot url + val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( + homeServerUri = Uri.parse(it.homeServerUrl) + ) + + pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) + .also { data -> pendingSessionStore.savePendingSessionData(data) } + } + it + }, + { + if (it is UnrecognizedCertificateException) { + throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint) + } else { + throw it + } + } + ) } private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { - return withContext(coroutineDispatchers.io) { - val authAPI = buildAuthAPI(homeServerConnectionConfig) + val authAPI = buildAuthAPI(homeServerConnectionConfig) - // First check the homeserver version - runCatching { - executeRequest(null) { - apiCall = authAPI.versions() - } + // First check the homeserver version + return runCatching { + executeRequest(null) { + apiCall = authAPI.versions() } - .map { versions -> - // Ok, it seems that the homeserver url is valid - getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString()) - } - .fold( - { - it - }, - { - if (it is Failure.OtherServerError - && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { - // It's maybe a Riot url? - getRiotDomainLoginFlowInternal(homeServerConnectionConfig) - } else { - throw it - } - } - ) } + .map { versions -> + // Ok, it seems that the homeserver url is valid + getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString()) + } + .fold( + { + it + }, + { + if (it is Failure.OtherServerError + && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // It's maybe a Riot url? + getRiotDomainLoginFlowInternal(homeServerConnectionConfig) + } else { + throw it + } + } + ) } private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { @@ -338,12 +322,9 @@ internal class DefaultAuthenticationService @Inject constructor( ?: let { pendingSessionData?.homeServerConnectionConfig?.let { DefaultRegistrationWizard( - buildClient(it), - retrofitFactory, - coroutineDispatchers, + buildAuthAPI(it), sessionCreator, - pendingSessionStore, - taskExecutor.executorScope + pendingSessionStore ).also { currentRegistrationWizard = it } @@ -359,12 +340,9 @@ internal class DefaultAuthenticationService @Inject constructor( ?: let { pendingSessionData?.homeServerConnectionConfig?.let { DefaultLoginWizard( - buildClient(it), - retrofitFactory, - coroutineDispatchers, + buildAuthAPI(it), sessionCreator, - pendingSessionStore, - taskExecutor.executorScope + pendingSessionStore ).also { currentLoginWizard = it } @@ -372,7 +350,7 @@ internal class DefaultAuthenticationService @Inject constructor( } } - override fun cancelPendingLoginOrRegistration() { + override suspend fun cancelPendingLoginOrRegistration() { currentLoginWizard = null currentRegistrationWizard = null @@ -381,61 +359,39 @@ internal class DefaultAuthenticationService @Inject constructor( pendingSessionData = pendingSessionData?.homeServerConnectionConfig ?.let { PendingSessionData(it) } .also { - taskExecutor.executorScope.launch(coroutineDispatchers.main) { - if (it == null) { - // Should not happen - pendingSessionStore.delete() - } else { - pendingSessionStore.savePendingSessionData(it) - } + if (it == null) { + // Should not happen + pendingSessionStore.delete() + } else { + pendingSessionStore.savePendingSessionData(it) } } } - override fun reset() { + override suspend fun reset() { currentLoginWizard = null currentRegistrationWizard = null pendingSessionData = null - taskExecutor.executorScope.launch(coroutineDispatchers.main) { - pendingSessionStore.delete() - } + pendingSessionStore.delete() } - override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, - credentials: Credentials, - callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - createSessionFromSso(credentials, homeServerConnectionConfig) - } + override suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, + credentials: Credentials): Session { + return sessionCreator.createSession(credentials, homeServerConnectionConfig) } - override fun getWellKnownData(matrixId: String, - homeServerConnectionConfig: HomeServerConnectionConfig?, - callback: MatrixCallback): Cancelable { - return getWellknownTask - .configureWith(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun getWellKnownData(matrixId: String, + homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult { + return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) } - override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, - matrixId: String, - password: String, - initialDeviceName: String, - callback: MatrixCallback): Cancelable { - return directLoginTask - .configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) { - this.callback = callback - } - .executeBy(taskExecutor) - } - - private suspend fun createSessionFromSso(credentials: Credentials, - homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) { - sessionCreator.createSession(credentials, homeServerConnectionConfig) + override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, + matrixId: String, + password: String, + initialDeviceName: String): Session { + return directLoginTask.execute(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) } private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt new file mode 100644 index 0000000000..b8416d69bf --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.auth + +import dagger.Lazy +import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse +import org.matrix.android.sdk.internal.di.Unauthenticated +import org.matrix.android.sdk.internal.network.RetrofitFactory +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject +import javax.net.ssl.HttpsURLConnection + +internal interface IsValidClientServerApiTask : Task { + data class Params( + val homeServerConnectionConfig: HomeServerConnectionConfig + ) +} + +internal class DefaultIsValidClientServerApiTask @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory +) : IsValidClientServerApiTask { + + override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean { + val client = buildClient(params.homeServerConnectionConfig) + val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() + + val authAPI = retrofitFactory.create(client, homeServerUrl) + .create(AuthAPI::class.java) + + return try { + executeRequest(null) { + apiCall = authAPI.getLoginFlows() + } + // We get a response, so the API is valid + true + } catch (failure: Throwable) { + if (failure is Failure.OtherServerError + && failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // Probably not valid + false + } else { + // Other error + throw failure + } + } + } + + private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient.get() + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt index 6743e7336e..7c4a0c38ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt @@ -20,6 +20,7 @@ import android.net.Uri import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.SessionManager import timber.log.Timber @@ -32,7 +33,8 @@ internal interface SessionCreator { internal class DefaultSessionCreator @Inject constructor( private val sessionParamsStore: SessionParamsStore, private val sessionManager: SessionManager, - private val pendingSessionStore: PendingSessionStore + private val pendingSessionStore: PendingSessionStore, + private val isValidClientServerApiTask: IsValidClientServerApiTask ) : SessionCreator { /** @@ -43,16 +45,28 @@ internal class DefaultSessionCreator @Inject constructor( // We can cleanup the pending session params pendingSessionStore.delete() + val overriddenUrl = credentials.discoveryInformation?.homeServer?.baseURL + // remove trailing "/" + ?.trim { it == '/' } + ?.takeIf { it.isNotBlank() } + ?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") } + ?.let { Uri.parse(it) } + ?.takeIf { + // Validate the URL, if the configuration is wrong server side, do not override + tryOrNull { + isValidClientServerApiTask.execute( + IsValidClientServerApiTask.Params( + homeServerConnectionConfig.copy(homeServerUri = it) + ) + ) + .also { Timber.d("Overriding homeserver url: $it") } + } ?: true // In case of other error (no network, etc.), consider it is valid... + } + val sessionParams = SessionParams( credentials = credentials, homeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = credentials.discoveryInformation?.homeServer?.baseURL - // remove trailing "/" - ?.trim { it == '/' } - ?.takeIf { it.isNotBlank() } - ?.also { Timber.d("Overriding homeserver url to $it") } - ?.let { Uri.parse(it) } - ?: homeServerConnectionConfig.homeServerUri, + homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri, identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL // remove trailing "/" ?.trim { it == '/' } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt index 108d0d4a42..4167875849 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt @@ -17,13 +17,10 @@ package org.matrix.android.sdk.internal.auth.login import android.util.Patterns -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.auth.AuthAPI import org.matrix.android.sdk.internal.auth.PendingSessionStore import org.matrix.android.sdk.internal.auth.SessionCreator @@ -34,56 +31,19 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask -import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.task.launchToCallback -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient internal class DefaultLoginWizard( - okHttpClient: OkHttpClient, - retrofitFactory: RetrofitFactory, - private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val authAPI: AuthAPI, private val sessionCreator: SessionCreator, - private val pendingSessionStore: PendingSessionStore, - private val coroutineScope: CoroutineScope + private val pendingSessionStore: PendingSessionStore ) : LoginWizard { private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here") - private val authAPI = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString()) - .create(AuthAPI::class.java) - - override fun login(login: String, - password: String, - deviceName: String, - callback: MatrixCallback): Cancelable { - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - loginInternal(login, password, deviceName) - } - } - - /** - * Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint - */ - override fun loginWithToken(loginToken: String, callback: MatrixCallback): Cancelable { - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - val loginParams = TokenLoginParams( - token = loginToken - ) - val credentials = executeRequest(null) { - apiCall = authAPI.login(loginParams) - } - - sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) - } - } - - private suspend fun loginInternal(login: String, - password: String, - deviceName: String) = withContext(coroutineDispatchers.computation) { + override suspend fun login(login: String, + password: String, + deviceName: String): Session { val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) { PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, deviceName) } else { @@ -93,16 +53,24 @@ internal class DefaultLoginWizard( apiCall = authAPI.login(loginParams) } - sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) + return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) } - override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback): Cancelable { - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - resetPasswordInternal(email, newPassword) + /** + * Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint + */ + override suspend fun loginWithToken(loginToken: String): Session { + val loginParams = TokenLoginParams( + token = loginToken + ) + val credentials = executeRequest(null) { + apiCall = authAPI.login(loginParams) } + + return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) } - private suspend fun resetPasswordInternal(email: String, newPassword: String) { + override suspend fun resetPassword(email: String, newPassword: String) { val param = RegisterAddThreePidTask.Params( RegisterThreePid.Email(email), pendingSessionData.clientSecret, @@ -120,21 +88,14 @@ internal class DefaultLoginWizard( .also { pendingSessionStore.savePendingSessionData(it) } } - override fun resetPasswordMailConfirmed(callback: MatrixCallback): Cancelable { - val safeResetPasswordData = pendingSessionData.resetPasswordData ?: run { - callback.onFailure(IllegalStateException("developer error, no reset password in progress")) - return NoOpCancellable - } - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - resetPasswordMailConfirmedInternal(safeResetPasswordData) - } - } + override suspend fun resetPasswordMailConfirmed() { + val safeResetPasswordData = pendingSessionData.resetPasswordData + ?: throw IllegalStateException("developer error, no reset password in progress") - private suspend fun resetPasswordMailConfirmedInternal(resetPasswordData: ResetPasswordData) { val param = ResetPasswordMailConfirmed.create( pendingSessionData.clientSecret, - resetPasswordData.addThreePidRegistrationResponse.sid, - resetPasswordData.newPassword + safeResetPasswordData.addThreePidRegistrationResponse.sid, + safeResetPasswordData.newPassword ) executeRequest(null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt index 163009d918..91e414e689 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt @@ -16,10 +16,7 @@ package org.matrix.android.sdk.internal.auth.registration -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import okhttp3.OkHttpClient -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.auth.registration.RegistrationResult @@ -27,31 +24,22 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.toFlowResult import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.auth.AuthAPI import org.matrix.android.sdk.internal.auth.PendingSessionStore import org.matrix.android.sdk.internal.auth.SessionCreator import org.matrix.android.sdk.internal.auth.db.PendingSessionData -import org.matrix.android.sdk.internal.network.RetrofitFactory -import org.matrix.android.sdk.internal.task.launchToCallback -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers /** * This class execute the registration request and is responsible to keep the session of interactive authentication */ internal class DefaultRegistrationWizard( - private val okHttpClient: OkHttpClient, - private val retrofitFactory: RetrofitFactory, - private val coroutineDispatchers: MatrixCoroutineDispatchers, + authAPI: AuthAPI, private val sessionCreator: SessionCreator, - private val pendingSessionStore: PendingSessionStore, - private val coroutineScope: CoroutineScope + private val pendingSessionStore: PendingSessionStore ) : RegistrationWizard { private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here") - private val authAPI = buildAuthAPI() private val registerTask = DefaultRegisterTask(authAPI) private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) private val validateCodeTask = DefaultValidateCodeTask(authAPI) @@ -71,70 +59,54 @@ internal class DefaultRegistrationWizard( override val isRegistrationStarted: Boolean get() = pendingSessionData.isRegistrationStarted - override fun getRegistrationFlow(callback: MatrixCallback): Cancelable { + override suspend fun getRegistrationFlow(): RegistrationResult { val params = RegistrationParams() - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - performRegistrationRequest(params) - } + return performRegistrationRequest(params) } - override fun createAccount(userName: String, - password: String, - initialDeviceDisplayName: String?, - callback: MatrixCallback): Cancelable { + override suspend fun createAccount(userName: String, + password: String, + initialDeviceDisplayName: String?): RegistrationResult { val params = RegistrationParams( username = userName, password = password, initialDeviceDisplayName = initialDeviceDisplayName ) - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - performRegistrationRequest(params) - .also { - pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true) - .also { pendingSessionStore.savePendingSessionData(it) } - } - } + return performRegistrationRequest(params) + .also { + pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true) + .also { pendingSessionStore.savePendingSessionData(it) } + } } - override fun performReCaptcha(response: String, callback: MatrixCallback): Cancelable { - val safeSession = pendingSessionData.currentSession ?: run { - callback.onFailure(IllegalStateException("developer error, call createAccount() method first")) - return NoOpCancellable - } + override suspend fun performReCaptcha(response: String): RegistrationResult { + val safeSession = pendingSessionData.currentSession + ?: throw IllegalStateException("developer error, call createAccount() method first") + val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response)) - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - performRegistrationRequest(params) - } + return performRegistrationRequest(params) } - override fun acceptTerms(callback: MatrixCallback): Cancelable { - val safeSession = pendingSessionData.currentSession ?: run { - callback.onFailure(IllegalStateException("developer error, call createAccount() method first")) - return NoOpCancellable - } + override suspend fun acceptTerms(): RegistrationResult { + val safeSession = pendingSessionData.currentSession + ?: throw IllegalStateException("developer error, call createAccount() method first") + val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession)) - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - performRegistrationRequest(params) - } + return performRegistrationRequest(params) } - override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback): Cancelable { - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - pendingSessionData = pendingSessionData.copy(currentThreePidData = null) - .also { pendingSessionStore.savePendingSessionData(it) } + override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult { + pendingSessionData = pendingSessionData.copy(currentThreePidData = null) + .also { pendingSessionStore.savePendingSessionData(it) } - sendThreePid(threePid) - } + return sendThreePid(threePid) } - override fun sendAgainThreePid(callback: MatrixCallback): Cancelable { - val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid ?: run { - callback.onFailure(IllegalStateException("developer error, call createAccount() method first")) - return NoOpCancellable - } - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - sendThreePid(safeCurrentThreePid) - } + override suspend fun sendAgainThreePid(): RegistrationResult { + val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid + ?: throw IllegalStateException("developer error, call createAccount() method first") + + return sendThreePid(safeCurrentThreePid) } private suspend fun sendThreePid(threePid: RegisterThreePid): RegistrationResult { @@ -173,20 +145,15 @@ internal class DefaultRegistrationWizard( return performRegistrationRequest(params) } - override fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback): Cancelable { - val safeParam = pendingSessionData.currentThreePidData?.registrationParams ?: run { - callback.onFailure(IllegalStateException("developer error, no pending three pid")) - return NoOpCancellable - } - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - performRegistrationRequest(safeParam, delayMillis) - } + override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult { + val safeParam = pendingSessionData.currentThreePidData?.registrationParams + ?: throw IllegalStateException("developer error, no pending three pid") + + return performRegistrationRequest(safeParam, delayMillis) } - override fun handleValidateThreePid(code: String, callback: MatrixCallback): Cancelable { - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - validateThreePid(code) - } + override suspend fun handleValidateThreePid(code: String): RegistrationResult { + return validateThreePid(code) } private suspend fun validateThreePid(code: String): RegistrationResult { @@ -210,15 +177,12 @@ internal class DefaultRegistrationWizard( } } - override fun dummy(callback: MatrixCallback): Cancelable { - val safeSession = pendingSessionData.currentSession ?: run { - callback.onFailure(IllegalStateException("developer error, call createAccount() method first")) - return NoOpCancellable - } - return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) { - val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession)) - performRegistrationRequest(params) - } + override suspend fun dummy(): RegistrationResult { + val safeSession = pendingSessionData.currentSession + ?: throw IllegalStateException("developer error, call createAccount() method first") + + val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession)) + return performRegistrationRequest(params) } private suspend fun performRegistrationRequest(registrationParams: RegistrationParams, @@ -239,9 +203,4 @@ internal class DefaultRegistrationWizard( val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return RegistrationResult.Success(session) } - - private fun buildAuthAPI(): AuthAPI { - val retrofit = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString()) - return retrofit.create(AuthAPI::class.java) - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt index 1a0383cb22..da0866a5fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt @@ -16,14 +16,25 @@ package org.matrix.android.sdk.internal.auth.registration +import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse -import org.matrix.android.sdk.api.auth.UIABaseAuth import timber.log.Timber import kotlin.coroutines.suspendCoroutine -internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveAuthInterceptor, retryBlock: suspend (UIABaseAuth) -> Unit): Boolean { +/** + * Handle a UIA challenge + * + * @param failure the failure to handle + * @param interceptor see doc in [UserInteractiveAuthInterceptor] + * @param retryBlock called at the end of the process, in this block generally retry executing the task, with + * provided authUpdate + * @return true if UIA is handled without error + */ +internal suspend fun handleUIA(failure: Throwable, + interceptor: UserInteractiveAuthInterceptor, + retryBlock: suspend (UIABaseAuth) -> Unit): Boolean { Timber.d("## UIA: check error ${failure.message}") val flowResponse = failure.toRegistrationFlowResponse() ?: return false.also { @@ -38,16 +49,16 @@ internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveA suspendCoroutine { continuation -> interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation) } - } catch (failure: Throwable) { - Timber.w(failure, "## UIA: failed to participate") + } catch (failure2: Throwable) { + Timber.w(failure2, "## UIA: failed to participate") return false } - Timber.d("## UIA: updated auth $authUpdate") + Timber.d("## UIA: updated auth") return try { retryBlock(authUpdate) true - } catch (failure: Throwable) { - handleUIA(failure, interceptor, retryBlock) + } catch (failure3: Throwable) { + handleUIA(failure3, interceptor, retryBlock) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index a786ebd4b2..e114f86a99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -61,7 +61,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask -import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceWithUserPasswordTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask @@ -75,7 +74,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadKeysTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSignaturesTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask -import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask @@ -240,9 +238,6 @@ internal abstract class CryptoModule { @Binds abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask - @Binds - abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask - @Binds abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService 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 678bc9819f..67229a5eae 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 @@ -75,7 +75,6 @@ import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask -import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask @@ -153,9 +152,8 @@ internal class DefaultCryptoService @Inject constructor( // Repository private val megolmEncryptionFactory: MXMegolmEncryptionFactory, private val olmEncryptionFactory: MXOlmEncryptionFactory, - private val deleteDeviceTask: DeleteDeviceTask, - private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask, // Tasks + private val deleteDeviceTask: DeleteDeviceTask, private val getDevicesTask: GetDevicesTask, private val getDeviceInfoTask: GetDeviceInfoTask, private val setDeviceNameTask: SetDeviceNameTask, @@ -217,15 +215,6 @@ internal class DefaultCryptoService @Inject constructor( .executeBy(taskExecutor) } - override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback) { - deleteDeviceWithUserPasswordTask - .configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - } - .executeBy(taskExecutor) - } - override fun getCryptoVersion(context: Context, longFormat: Boolean): String { return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt index ff25ac0f66..61596bb5b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt @@ -47,12 +47,16 @@ internal class DefaultDeleteDeviceTask @Inject constructor( } } catch (throwable: Throwable) { if (params.userInteractiveAuthInterceptor == null - || !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth -> - execute(params.copy(userAuthParam = auth)) - } + || !handleUIA( + failure = throwable, + interceptor = params.userInteractiveAuthInterceptor, + retryBlock = { authUpdate -> + execute(params.copy(userAuthParam = authUpdate)) + } + ) ) { Timber.d("## UIA: propagate failure") - throw throwable + throw throwable } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt deleted file mode 100644 index dc0077425e..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.crypto.tasks - -import org.matrix.android.sdk.api.auth.data.LoginFlowTypes -import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams -import org.matrix.android.sdk.api.auth.UserPasswordAuth -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.task.Task -import javax.inject.Inject - -internal interface DeleteDeviceWithUserPasswordTask : Task { - data class Params( - val deviceId: String, - val authSession: String?, - val password: String - ) -} - -internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor( - private val cryptoApi: CryptoApi, - @UserId private val userId: String, - private val globalErrorReceiver: GlobalErrorReceiver -) : DeleteDeviceWithUserPasswordTask { - - override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) { - return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.deleteDevice(params.deviceId, - DeleteDeviceParams( - auth = UserPasswordAuth( - type = LoginFlowTypes.PASSWORD, - session = params.authSession, - user = userId, - password = params.password - ).asMap() - ) - ) - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt index ef31130f55..f8a8354e48 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt @@ -126,11 +126,16 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor( uploadSigningKeysTask.execute(uploadSigningKeysParams) } catch (failure: Throwable) { if (params.interactiveAuthInterceptor == null - || !handleUIA(failure, params.interactiveAuthInterceptor) { authUpdate -> - uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate)) - }) { + || !handleUIA( + failure = failure, + interceptor = params.interactiveAuthInterceptor, + retryBlock = { authUpdate -> + uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate)) + } + ) + ) { Timber.d("## UIA: propagate failure") - throw failure + throw failure } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt index d67b21567e..ca6b0554a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt @@ -16,10 +16,9 @@ package org.matrix.android.sdk.internal.session.account +import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.internal.auth.registration.handleUIA -import org.matrix.android.sdk.api.auth.UIABaseAuth -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.cleanup.CleanupSession @@ -30,8 +29,8 @@ import javax.inject.Inject internal interface DeactivateAccountTask : Task { data class Params( - val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, val eraseAllData: Boolean, + val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, val userAuthParam: UIABaseAuth? = null ) } @@ -39,7 +38,6 @@ internal interface DeactivateAccountTask : Task(globalErrorReceiver) { apiCall = accountAPI.deactivate(deactivateAccountParams) } + true } catch (throwable: Throwable) { - if (!handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth -> - execute(params.copy(userAuthParam = auth)) - } + if (!handleUIA( + failure = throwable, + interceptor = params.userInteractiveAuthInterceptor, + retryBlock = { authUpdate -> + execute(params.copy(userAuthParam = authUpdate)) + } + ) ) { Timber.d("## UIA: propagate failure") - throw throwable + throw throwable + } else { + false } } - // Logout from identity server if any, ignoring errors - runCatching { identityDisconnectTask.execute(Unit) } - .onFailure { Timber.w(it, "Unable to disconnect identity server") } - cleanupSession.handle() + if (canCleanup) { + // Logout from identity server if any, ignoring errors + runCatching { identityDisconnectTask.execute(Unit) } + .onFailure { Timber.w(it, "Unable to disconnect identity server") } + + cleanupSession.handle() + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt index 25b67159a9..dc77d7bffb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt @@ -27,7 +27,7 @@ internal class DefaultAccountService @Inject constructor(private val changePassw changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword)) } - override suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) { - deactivateAccountTask.execute(DeactivateAccountTask.Params(userInteractiveAuthInterceptor, eraseAllData)) + override suspend fun deactivateAccount(eraseAllData: Boolean, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { + deactivateAccountTask.execute(DeactivateAccountTask.Params(eraseAllData, userInteractiveAuthInterceptor)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt index 916a602936..c2a38af093 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt @@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase -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.task.Task @@ -47,11 +46,12 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor( private val profileAPI: ProfileAPI, @SessionDatabase private val monarchy: Monarchy, private val pendingThreePidMapper: PendingThreePidMapper, - @UserId private val userId: String, private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() { override suspend fun execute(params: Params) { - if (params.userWantsToCancel.not()) { + val canCleanup = if (params.userWantsToCancel) { + true + } else { // Get the required pending data val pendingThreePids = monarchy.fetchAllMappedSync( { it.where(PendingThreePidEntity::class.java) }, @@ -69,21 +69,30 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor( ) apiCall = profileAPI.finalizeAddThreePid(body) } + true } catch (throwable: Throwable) { if (params.userInteractiveAuthInterceptor == null - || !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth -> - execute(params.copy(userAuthParam = auth)) - } + || !handleUIA( + failure = throwable, + interceptor = params.userInteractiveAuthInterceptor, + retryBlock = { authUpdate -> + execute(params.copy(userAuthParam = authUpdate)) + } + ) ) { Timber.d("## UIA: propagate failure") - throw throwable.toRegistrationFlowResponse() + throw throwable.toRegistrationFlowResponse() ?.let { Failure.RegistrationFlowError(it) } ?: throwable + } else { + false } } } - cleanupDatabase(params) + if (canCleanup) { + cleanupDatabase(params) + } } private suspend fun cleanupDatabase(params: Params) { diff --git a/multipicker/build.gradle b/multipicker/build.gradle index 10dc18e488..5b24388aae 100644 --- a/multipicker/build.gradle +++ b/multipicker/build.gradle @@ -44,7 +44,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.fragment:fragment-ktx:1.3.0-beta01" - implementation 'androidx.exifinterface:exifinterface:1.3.1' + implementation 'androidx.exifinterface:exifinterface:1.3.2' // Log implementation 'com.jakewharton.timber:timber:4.7.1' diff --git a/vector/build.gradle b/vector/build.gradle index cc4efb4590..df8e37942e 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -12,8 +12,8 @@ kapt { // Note: 2 digits max for each value ext.versionMajor = 1 -ext.versionMinor = 0 -ext.versionPatch = 18 +ext.versionMinor = 1 +ext.versionPatch = 1 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -287,21 +287,21 @@ android { dependencies { - def epoxy_version = '4.1.0' + def epoxy_version = '4.4.0' def fragment_version = '1.3.0-beta01' def arrow_version = "0.8.2" def markwon_version = '4.1.2' - def big_image_viewer_version = '1.6.2' - def glide_version = '4.11.0' + def big_image_viewer_version = '1.7.0' + def glide_version = '4.12.0' def moshi_version = '1.11.0' - def daggerVersion = '2.31' - def autofill_version = "1.0.0" + def daggerVersion = '2.32' + def autofill_version = "1.1.0" def work_version = '2.4.0' def arch_version = '2.1.0' def lifecycle_version = '2.2.0' // Tests - def kluent_version = '1.61' + def kluent_version = '1.65' def androidxTest_version = '1.3.0' def espresso_version = '3.3.0' @@ -320,12 +320,12 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation "androidx.sharetarget:sharetarget:1.0.0" + implementation "androidx.sharetarget:sharetarget:1.1.0" implementation 'androidx.core:core-ktx:1.3.2' implementation "androidx.media:media:1.2.1" implementation "org.threeten:threetenbp:1.4.0:no-tzdb" - implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0" + implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0" implementation "com.squareup.moshi:moshi-adapters:$moshi_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" @@ -338,15 +338,15 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.5.1' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.18' // rx implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1' // RXBinding - implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0' - implementation 'com.jakewharton.rxbinding3:rxbinding-appcompat:3.0.0' + implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0' + implementation 'com.jakewharton.rxbinding3:rxbinding-appcompat:3.1.0' implementation 'com.jakewharton.rxbinding3:rxbinding-material:3.0.0' implementation("com.airbnb.android:epoxy:$epoxy_version") @@ -369,11 +369,11 @@ dependencies { // UI implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - implementation 'com.google.android.material:material:1.3.0-alpha04' + implementation 'com.google.android.material:material:1.3.0' implementation 'me.gujun.android:span:1.7' implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:html:$markwon_version" - implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.4' + implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2' implementation 'me.saket:better-link-movement-method:2.2.0' implementation 'com.google.android:flexbox:1.1.1' implementation "androidx.autofill:autofill:$autofill_version" @@ -384,7 +384,7 @@ dependencies { implementation 'androidx.browser:browser:1.2.0' // Passphrase strength helper - implementation 'com.nulab-inc:zxcvbn:1.2.7' + implementation 'com.nulab-inc:zxcvbn:1.4.0' //Alerter implementation 'com.tapadoo.android:alerter:5.1.2' @@ -412,14 +412,14 @@ dependencies { implementation 'me.leolin:ShortcutBadger:1.1.22@aar' // Chat effects - implementation 'nl.dionsegijn:konfetti:1.2.5' + implementation 'nl.dionsegijn:konfetti:1.2.6' implementation 'com.github.jetradarmobile:android-snowfall:1.2.0' // DI implementation "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" // gplay flavor only - gplayImplementation('com.google.firebase:firebase-messaging:21.0.0') { + gplayImplementation('com.google.firebase:firebase-messaging:21.0.1') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -442,7 +442,7 @@ dependencies { // QR-code // Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170 - implementation 'com.google.zxing:core:3.3.3' + implementation 'com.google.zxing:core:3.4.1' implementation 'me.dm7.barcodescanner:zxing:1.9.13' // Emoji Keyboard @@ -452,7 +452,7 @@ dependencies { implementation 'im.dlg:android-dialer:1.2.5' // TESTS - testImplementation 'junit:junit:4.13' + testImplementation 'junit:junit:4.13.2' testImplementation "org.amshove.kluent:kluent-android:$kluent_version" // Plant Timber tree for test testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' diff --git a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt index a4b9983ff4..285f40aaf3 100644 --- a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt +++ b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt @@ -23,11 +23,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout import org.junit.Assert import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.sync.SyncState @@ -47,22 +47,21 @@ abstract class VerificationTestBase { withInitialSync: Boolean): Session { val hs = createHomeServerConfig() - doSync { - matrix.authenticationService() - .getLoginFlow(hs, it) + runBlockingTest { + matrix.authenticationService().getLoginFlow(hs) } - doSync { + runBlockingTest { matrix.authenticationService() .getRegistrationWizard() - .createAccount(userName, password, null, it) + .createAccount(userName, password, null) } // Perform dummy step - val registrationResult = doSync { + val registrationResult = runBlockingTest { matrix.authenticationService() .getRegistrationWizard() - .dummy(it) + .dummy() } Assert.assertTrue(registrationResult is RegistrationResult.Success) @@ -80,6 +79,14 @@ abstract class VerificationTestBase { .build() } + protected fun runBlockingTest(timeout: Long = 20_000, block: suspend () -> T): T { + return runBlocking { + withTimeout(timeout) { + block() + } + } + } + // Transform a method with a MatrixCallback to a synchronous method inline fun doSync(block: (MatrixCallback) -> Unit): T { val lock = CountDownLatch(1) diff --git a/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt b/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt index 0385973386..ce23111a95 100644 --- a/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt +++ b/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt @@ -113,7 +113,7 @@ class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory { override fun onResume() { super.onResume() - // It's the only way we have to know if sso falback flow was successful + // It's the only way we have to know if sso fallback flow was successful withState(sharedViewModel) { if (it.ssoFallbackPageWasShown) { Timber.d("## UIA ssoFallbackPageWasShown tentative success") 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 42278cd948..fe55d81cc4 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 @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback import java.io.OutputStream import kotlin.coroutines.Continuation import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException class BootstrapSharedViewModel @AssistedInject constructor( @Assisted initialState: BootstrapViewState, @@ -421,7 +422,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( _viewEvents.post(BootstrapViewEvents.RequestReAuth(flowResponse, errCode)) } else -> { - promise.resumeWith(Result.failure(UnsupportedOperationException())) + promise.resumeWithException(UnsupportedOperationException()) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 44e02fea0b..62bdc61b63 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -52,6 +52,7 @@ import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException class HomeActivityViewModel @AssistedInject constructor( @Assisted initialState: HomeActivityViewState, @@ -228,7 +229,7 @@ class HomeActivityViewModel @AssistedInject constructor( ) ) } else { - promise.resumeWith(Result.failure(Exception("Cannot silently initialize cross signing, UIA missing"))) + promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing")) } } }, diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index 7bf0b98841..792c74e019 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -19,6 +19,7 @@ package im.vector.app.features.login import android.content.Context import android.net.Uri import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -27,8 +28,8 @@ 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.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.configureAndStart @@ -37,7 +38,8 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ensureTrailingSlash import im.vector.app.features.signout.soft.SoftLogoutActivity -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -51,7 +53,6 @@ import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.Cancelable import timber.log.Timber import java.util.concurrent.CancellationException @@ -117,7 +118,12 @@ class LoginViewModel @AssistedInject constructor( private var loginConfig: LoginConfig? = null - private var currentTask: Cancelable? = null + private var currentJob: Job? = null + set(value) { + // Cancel any previous Job + field?.cancel() + field = value + } override fun handle(action: LoginAction) { when (action) { @@ -140,7 +146,7 @@ class LoginViewModel @AssistedInject constructor( } private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) { - // It happen when we get the login flow, or during direct authentication. + // It happens when we get the login flow, or during direct authentication. // So alter the homeserver config and retrieve again the login flow when (val finalLastAction = lastAction) { is LoginAction.UpdateHomeServer -> { @@ -186,22 +192,20 @@ class LoginViewModel @AssistedInject constructor( ) } - currentTask = safeLoginWizard.loginWithToken( - action.loginToken, - object : MatrixCallback { - override fun onSuccess(data: Session) { - onSessionCreated(data) - } - - override fun onFailure(failure: Throwable) { - _viewEvents.post(LoginViewEvents.Failure(failure)) - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - } - }) + currentJob = viewModelScope.launch { + try { + safeLoginWizard.loginWithToken(action.loginToken) + } catch (failure: Throwable) { + _viewEvents.post(LoginViewEvents.Failure(failure)) + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + null + } + ?.let { onSessionCreated(it) } + } } } @@ -231,46 +235,49 @@ class LoginViewModel @AssistedInject constructor( private fun handleCheckIfEmailHasBeenValidated(action: LoginAction.CheckIfEmailHasBeenValidated) { // We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state - currentTask?.cancel() - currentTask = registrationWizard?.checkIfEmailHasBeenValidated(action.delayMillis, registrationCallback) + currentJob = executeRegistrationStep(withLoading = false) { + it.checkIfEmailHasBeenValidated(action.delayMillis) + } } private fun handleStopEmailValidationCheck() { - currentTask?.cancel() - currentTask = null + currentJob = null } private fun handleValidateThreePid(action: LoginAction.ValidateThreePid) { - setState { copy(asyncRegistration = Loading()) } - currentTask = registrationWizard?.handleValidateThreePid(action.code, registrationCallback) + currentJob = executeRegistrationStep { + it.handleValidateThreePid(action.code) + } } - private val registrationCallback = object : MatrixCallback { - override fun onSuccess(data: RegistrationResult) { - /* - // Simulate registration disabled - onFailure(Failure.ServerError(MatrixError( - code = MatrixError.FORBIDDEN, - message = "Registration is disabled" - ), 403)) - */ - - setState { - copy( - asyncRegistration = Uninitialized - ) - } - - when (data) { - is RegistrationResult.Success -> onSessionCreated(data.session) - is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) - } + private fun executeRegistrationStep(withLoading: Boolean = true, + block: suspend (RegistrationWizard) -> RegistrationResult): Job { + if (withLoading) { + setState { copy(asyncRegistration = Loading()) } } - - override fun onFailure(failure: Throwable) { - if (failure !is CancellationException) { - _viewEvents.post(LoginViewEvents.Failure(failure)) + return viewModelScope.launch { + try { + registrationWizard?.let { block(it) } + /* + // Simulate registration disabled + throw Failure.ServerError(MatrixError( + code = MatrixError.FORBIDDEN, + message = "Registration is disabled" + ), 403)) + */ + } catch (failure: Throwable) { + if (failure !is CancellationException) { + _viewEvents.post(LoginViewEvents.Failure(failure)) + } + null } + ?.let { data -> + when (data) { + is RegistrationResult.Success -> onSessionCreated(data.session) + is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) + } + } + setState { copy( asyncRegistration = Uninitialized @@ -281,78 +288,68 @@ class LoginViewModel @AssistedInject constructor( private fun handleAddThreePid(action: LoginAction.AddThreePid) { setState { copy(asyncRegistration = Loading()) } - currentTask = registrationWizard?.addThreePid(action.threePid, object : MatrixCallback { - override fun onSuccess(data: RegistrationResult) { - setState { - copy( - asyncRegistration = Uninitialized - ) - } - } - - override fun onFailure(failure: Throwable) { + currentJob = viewModelScope.launch { + try { + registrationWizard?.addThreePid(action.threePid) + } catch (failure: Throwable) { _viewEvents.post(LoginViewEvents.Failure(failure)) - setState { - copy( - asyncRegistration = Uninitialized - ) - } } - }) + setState { + copy( + asyncRegistration = Uninitialized + ) + } + } } private fun handleSendAgainThreePid() { setState { copy(asyncRegistration = Loading()) } - currentTask = registrationWizard?.sendAgainThreePid(object : MatrixCallback { - override fun onSuccess(data: RegistrationResult) { - setState { - copy( - asyncRegistration = Uninitialized - ) - } - } - - override fun onFailure(failure: Throwable) { + currentJob = viewModelScope.launch { + try { + registrationWizard?.sendAgainThreePid() + } catch (failure: Throwable) { _viewEvents.post(LoginViewEvents.Failure(failure)) - setState { - copy( - asyncRegistration = Uninitialized - ) - } } - }) + setState { + copy( + asyncRegistration = Uninitialized + ) + } + } } private fun handleAcceptTerms() { - setState { copy(asyncRegistration = Loading()) } - currentTask = registrationWizard?.acceptTerms(registrationCallback) + currentJob = executeRegistrationStep { + it.acceptTerms() + } } private fun handleRegisterDummy() { - setState { copy(asyncRegistration = Loading()) } - currentTask = registrationWizard?.dummy(registrationCallback) + currentJob = executeRegistrationStep { + it.dummy() + } } private fun handleRegisterWith(action: LoginAction.LoginOrRegister) { - setState { copy(asyncRegistration = Loading()) } reAuthHelper.data = action.password - currentTask = registrationWizard?.createAccount( - action.username, - action.password, - action.initialDeviceName, - registrationCallback - ) + currentJob = executeRegistrationStep { + it.createAccount( + action.username, + action.password, + action.initialDeviceName + ) + } } private fun handleCaptchaDone(action: LoginAction.CaptchaDone) { - setState { copy(asyncRegistration = Loading()) } - currentTask = registrationWizard?.performReCaptcha(action.captchaResponse, registrationCallback) + currentJob = executeRegistrationStep { + it.performReCaptcha(action.captchaResponse) + } } private fun handleResetAction(action: LoginAction.ResetAction) { // Cancel any request - currentTask?.cancel() - currentTask = null + currentJob = null when (action) { LoginAction.ResetHomeServerType -> { @@ -363,16 +360,17 @@ class LoginViewModel @AssistedInject constructor( } } LoginAction.ResetHomeServerUrl -> { - authenticationService.reset() - - setState { - copy( - asyncHomeServerLoginFlowRequest = Uninitialized, - homeServerUrl = null, - loginMode = LoginMode.Unknown, - serverType = ServerType.Unknown, - loginModeSupportedTypes = emptyList() - ) + viewModelScope.launch { + authenticationService.reset() + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized, + homeServerUrl = null, + loginMode = LoginMode.Unknown, + serverType = ServerType.Unknown, + loginModeSupportedTypes = emptyList() + ) + } } } LoginAction.ResetSignMode -> { @@ -386,13 +384,14 @@ class LoginViewModel @AssistedInject constructor( } } LoginAction.ResetLogin -> { - authenticationService.cancelPendingLoginOrRegistration() - - setState { - copy( - asyncLoginAction = Uninitialized, - asyncRegistration = Uninitialized - ) + viewModelScope.launch { + authenticationService.cancelPendingLoginOrRegistration() + setState { + copy( + asyncLoginAction = Uninitialized, + asyncRegistration = Uninitialized + ) + } } } LoginAction.ResetResetPassword -> { @@ -473,26 +472,27 @@ class LoginViewModel @AssistedInject constructor( ) } - currentTask = safeLoginWizard.resetPassword(action.email, action.newPassword, object : MatrixCallback { - override fun onSuccess(data: Unit) { - setState { - copy( - asyncResetPassword = Success(data), - resetPasswordEmail = action.email - ) - } - - _viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone) - } - - override fun onFailure(failure: Throwable) { + currentJob = viewModelScope.launch { + try { + safeLoginWizard.resetPassword(action.email, action.newPassword) + } catch (failure: Throwable) { setState { copy( asyncResetPassword = Fail(failure) ) } + return@launch } - }) + + setState { + copy( + asyncResetPassword = Success(Unit), + resetPasswordEmail = action.email + ) + } + + _viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone) + } } } @@ -514,26 +514,26 @@ class LoginViewModel @AssistedInject constructor( ) } - currentTask = safeLoginWizard.resetPasswordMailConfirmed(object : MatrixCallback { - override fun onSuccess(data: Unit) { - setState { - copy( - asyncResetMailConfirmed = Success(data), - resetPasswordEmail = null - ) - } - - _viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess) - } - - override fun onFailure(failure: Throwable) { + currentJob = viewModelScope.launch { + try { + safeLoginWizard.resetPasswordMailConfirmed() + } catch (failure: Throwable) { setState { copy( asyncResetMailConfirmed = Fail(failure) ) } + return@launch } - }) + setState { + copy( + asyncResetMailConfirmed = Success(Unit), + resetPasswordEmail = null + ) + } + + _viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess) + } } } @@ -553,36 +553,36 @@ class LoginViewModel @AssistedInject constructor( ) } - authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback { - override fun onSuccess(data: WellknownResult) { - when (data) { - is WellknownResult.Prompt -> - onWellknownSuccess(action, data, homeServerConnectionConfig) - is WellknownResult.FailPrompt -> - // Relax on IS discovery if home server is valid - if (data.homeServerUrl != null && data.wellKnown != null) { - onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig) - } else { - onWellKnownError() - } - is WellknownResult.InvalidMatrixId -> { - setState { - copy( - asyncLoginAction = Uninitialized - ) - } - _viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id)))) - } - else -> { + currentJob = viewModelScope.launch { + val data = try { + authenticationService.getWellKnownData(action.username, homeServerConnectionConfig) + } catch (failure: Throwable) { + onDirectLoginError(failure) + return@launch + } + when (data) { + is WellknownResult.Prompt -> + onWellknownSuccess(action, data, homeServerConnectionConfig) + is WellknownResult.FailPrompt -> + // Relax on IS discovery if home server is valid + if (data.homeServerUrl != null && data.wellKnown != null) { + onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig) + } else { onWellKnownError() } - }.exhaustive - } - - override fun onFailure(failure: Throwable) { - onDirectLoginError(failure) - } - }) + is WellknownResult.InvalidMatrixId -> { + setState { + copy( + asyncLoginAction = Uninitialized + ) + } + _viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id)))) + } + else -> { + onWellKnownError() + } + }.exhaustive + } } private fun onWellKnownError() { @@ -594,9 +594,9 @@ class LoginViewModel @AssistedInject constructor( _viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) } - private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, - wellKnownPrompt: WellknownResult.Prompt, - homeServerConnectionConfig: HomeServerConnectionConfig?) { + private suspend fun onWellknownSuccess(action: LoginAction.LoginOrRegister, + wellKnownPrompt: WellknownResult.Prompt, + homeServerConnectionConfig: HomeServerConnectionConfig?) { val alteredHomeServerConnectionConfig = homeServerConnectionConfig ?.copy( homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), @@ -607,20 +607,17 @@ class LoginViewModel @AssistedInject constructor( identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } ) - authenticationService.directAuthentication( - alteredHomeServerConnectionConfig, - action.username, - action.password, - action.initialDeviceName, - object : MatrixCallback { - override fun onSuccess(data: Session) { - onSessionCreated(data) - } - - override fun onFailure(failure: Throwable) { - onDirectLoginError(failure) - } - }) + val data = try { + authenticationService.directAuthentication( + alteredHomeServerConnectionConfig, + action.username, + action.password, + action.initialDeviceName) + } catch (failure: Throwable) { + onDirectLoginError(failure) + return + } + onSessionCreated(data) } private fun onDirectLoginError(failure: Throwable) { @@ -657,35 +654,33 @@ class LoginViewModel @AssistedInject constructor( ) } - currentTask = safeLoginWizard.login( - action.username, - action.password, - action.initialDeviceName, - object : MatrixCallback { - override fun onSuccess(data: Session) { + currentJob = viewModelScope.launch { + try { + safeLoginWizard.login( + action.username, + action.password, + action.initialDeviceName + ) + } catch (failure: Throwable) { + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + null + } + ?.let { reAuthHelper.data = action.password - onSessionCreated(data) + onSessionCreated(it) } - - override fun onFailure(failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - } - }) + } } } private fun startRegistrationFlow() { - setState { - copy( - asyncRegistration = Loading() - ) + currentJob = executeRegistrationStep { + it.getRegistrationFlow() } - - currentTask = registrationWizard?.getRegistrationFlow(registrationCallback) } private fun startAuthenticationFlow() { @@ -706,8 +701,9 @@ class LoginViewModel @AssistedInject constructor( } } - private fun onSessionCreated(session: Session) { + private suspend fun onSessionCreated(session: Session) { activeSessionHolder.setActiveSession(session) + authenticationService.reset() session.configureAndStart(applicationContext) setState { @@ -724,15 +720,17 @@ class LoginViewModel @AssistedInject constructor( // Should not happen Timber.w("homeServerConnectionConfig is null") } else { - authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials, object : MatrixCallback { - override fun onSuccess(data: Session) { - onSessionCreated(data) + currentJob = viewModelScope.launch { + try { + authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) + } catch (failure: Throwable) { + setState { + copy(asyncLoginAction = Fail(failure)) + } + null } - - override fun onFailure(failure: Throwable) = setState { - copy(asyncLoginAction = Fail(failure)) - } - }) + ?.let { onSessionCreated(it) } + } } } @@ -749,21 +747,21 @@ class LoginViewModel @AssistedInject constructor( private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) { currentHomeServerConnectionConfig = homeServerConnectionConfig - currentTask?.cancel() - currentTask = null - authenticationService.cancelPendingLoginOrRegistration() + currentJob = viewModelScope.launch { + authenticationService.cancelPendingLoginOrRegistration() - setState { - copy( - asyncHomeServerLoginFlowRequest = Loading(), - // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg - // It is also useful to set the value again in the case of a certificate error on matrix.org - serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType - ) - } + setState { + copy( + asyncHomeServerLoginFlowRequest = Loading(), + // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg + // It is also useful to set the value again in the case of a certificate error on matrix.org + serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType + ) + } - currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback { - override fun onFailure(failure: Throwable) { + val data = try { + authenticationService.getLoginFlow(homeServerConnectionConfig) + } catch (failure: Throwable) { _viewEvents.post(LoginViewEvents.Failure(failure)) setState { copy( @@ -772,47 +770,39 @@ class LoginViewModel @AssistedInject constructor( serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType ) } + null } - override fun onSuccess(data: LoginFlowResult) { + if (data is LoginFlowResult.Success) { // Valid Homeserver, add it to the history. // Note: we add what the user has input, data.homeServerUrl can be different rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString()) - when (data) { - is LoginFlowResult.Success -> { - val loginMode = when { - // SSO login is taken first - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) - && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } + val loginMode = when { + // SSO login is taken first + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) + && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password + else -> LoginMode.Unsupported + } - // FIXME We should post a view event here normally? - setState { - copy( - asyncHomeServerLoginFlowRequest = Uninitialized, - homeServerUrl = data.homeServerUrl, - loginMode = loginMode, - loginModeSupportedTypes = data.supportedLoginTypes.toList() - ) - } - if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) - || data.isOutdatedHomeserver) { - // Notify the UI - _viewEvents.post(LoginViewEvents.OutdatedHomeserver) - } - } + // FIXME We should post a view event here normally? + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized, + homeServerUrl = data.homeServerUrl, + loginMode = loginMode, + loginModeSupportedTypes = data.supportedLoginTypes.toList() + ) + } + if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) + || data.isOutdatedHomeserver) { + // Notify the UI + _viewEvents.post(LoginViewEvents.OutdatedHomeserver) } } - }) - } - - override fun onCleared() { - currentTask?.cancel() - super.onCleared() + } } fun getInitialHomeServerUrl(): String? { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/LogFormatter.kt b/vector/src/main/java/im/vector/app/features/rageshake/LogFormatter.kt new file mode 100644 index 0000000000..21fc65bd5e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/rageshake/LogFormatter.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 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.rageshake + +import java.io.PrintWriter +import java.io.StringWriter +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.TimeZone +import java.util.logging.Formatter +import java.util.logging.LogRecord + +class LogFormatter : Formatter() { + + override fun format(r: LogRecord): String { + if (!mIsTimeZoneSet) { + DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC") + mIsTimeZoneSet = true + } + + val thrown = r.thrown + if (thrown != null) { + val sw = StringWriter() + val pw = PrintWriter(sw) + sw.write(r.message) + sw.write(LINE_SEPARATOR) + thrown.printStackTrace(pw) + pw.flush() + return sw.toString() + } else { + val b = StringBuilder() + val date = DATE_FORMAT.format(Date(r.millis)) + b.append(date) + b.append("Z ") + b.append(r.message) + b.append(LINE_SEPARATOR) + return b.toString() + } + } + + companion object { + private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n" + + // private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) + private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US) + + private var mIsTimeZoneSet = false + } +} diff --git a/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt b/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt index a279c16cb1..304720dfb0 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt @@ -22,45 +22,41 @@ import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import timber.log.Timber import java.io.File import java.io.PrintWriter import java.io.StringWriter -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import java.util.TimeZone import java.util.logging.FileHandler -import java.util.logging.Formatter import java.util.logging.Level -import java.util.logging.LogRecord import java.util.logging.Logger import javax.inject.Inject import javax.inject.Singleton -private const val SIZE_20MB = 20 * 1024 * 1024 -private const val SIZE_50MB = 50 * 1024 * 1024 - @Singleton -class VectorFileLogger @Inject constructor(val context: Context, private val vectorPreferences: VectorPreferences) : Timber.Tree() { +class VectorFileLogger @Inject constructor( + context: Context, + private val vectorPreferences: VectorPreferences +) : Timber.Tree() { - private val maxLogSizeByte: Int - private val logRotationCount: Int + companion object { + private const val SIZE_20MB = 20 * 1024 * 1024 + private const val SIZE_50MB = 50 * 1024 * 1024 + } - init { - if (vectorPreferences.labAllowedExtendedLogging()) { - maxLogSizeByte = SIZE_50MB - logRotationCount = 15 - } else { - maxLogSizeByte = SIZE_20MB - logRotationCount = 7 + private val maxLogSizeByte = if (vectorPreferences.labAllowedExtendedLogging()) SIZE_50MB else SIZE_20MB + private val logRotationCount = if (vectorPreferences.labAllowedExtendedLogging()) 15 else 7 + + private val logger = Logger.getLogger(context.packageName).apply { + tryOrNull { + useParentHandlers = false + level = Level.ALL } } - private val sLogger = Logger.getLogger("im.vector.app") - private var sFileHandler: FileHandler? = null - private var sCacheDirectory: File? = null - private var sFileName = "elementLogs" + private val fileHandler: FileHandler? + private val cacheDirectory = File(context.cacheDir, "logs") + private var fileNamePrefix = "logs" private val prioPrefixes = mapOf( Log.VERBOSE to "V/ ", @@ -72,24 +68,29 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec ) init { - val logsDirectoryFile = context.cacheDir.absolutePath + "/logs" - setLogDirectory(File(logsDirectoryFile)) - try { - if (sCacheDirectory != null) { - sFileHandler = FileHandler(sCacheDirectory!!.absolutePath + "/" + sFileName + ".%g.txt", maxLogSizeByte, logRotationCount) - sFileHandler?.formatter = LogFormatter() - sLogger.useParentHandlers = false - sLogger.level = Level.ALL - sFileHandler?.let { sLogger.addHandler(it) } - } - } catch (e: Throwable) { - Timber.e(e, "Failed to initialize FileLogger") + if (!cacheDirectory.exists()) { + cacheDirectory.mkdirs() + } + + for (i in 0..15) { + val file = File(cacheDirectory, "elementLogs.$i.txt") + tryOrNull { file.delete() } + } + + fileHandler = tryOrNull("Failed to initialize FileLogger") { + FileHandler( + cacheDirectory.absolutePath + "/" + fileNamePrefix + ".%g.txt", + maxLogSizeByte, + logRotationCount + ) + .also { it.formatter = LogFormatter() } + .also { logger.addHandler(it) } } } override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + fileHandler ?: return GlobalScope.launch(Dispatchers.IO) { - if (sFileHandler == null) return@launch if (skipLog(priority)) return@launch if (t != null) { logToFile(t) @@ -107,84 +108,22 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec } } - /** - * Set the directory to put log files. - * - * @param cacheDir The directory, usually [android.content.ContextWrapper.getCacheDir] - */ - private fun setLogDirectory(cacheDir: File) { - if (!cacheDir.exists()) { - cacheDir.mkdirs() - } - sCacheDirectory = cacheDir - } - /** * Adds our own log files to the provided list of files. * - * @param files The list of files to add to. - * @return The same list with more files added. + * @return The list of files with logs. */ fun getLogFiles(): List { - val files = ArrayList() - - try { - // reported by GA - if (null != sFileHandler) { - sFileHandler!!.flush() - val absPath = sCacheDirectory?.absolutePath ?: return emptyList() - - for (i in 0..logRotationCount) { - val filepath = "$absPath/$sFileName.$i.txt" - val file = File(filepath) - if (file.exists()) { - files.add(file) + return tryOrNull("## getLogFiles() failed") { + fileHandler + ?.flush() + ?.let { 0 until logRotationCount } + ?.mapNotNull { index -> + File(cacheDirectory, "$fileNamePrefix.$index.txt") + .takeIf { it.exists() } } - } - } - } catch (e: Exception) { - Timber.e(e, "## addLogFiles() failed") - } - - return files - } - - class LogFormatter : Formatter() { - - override fun format(r: LogRecord): String { - if (!mIsTimeZoneSet) { - DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC") - mIsTimeZoneSet = true - } - - val thrown = r.thrown - if (thrown != null) { - val sw = StringWriter() - val pw = PrintWriter(sw) - sw.write(r.message) - sw.write(LINE_SEPARATOR) - thrown.printStackTrace(pw) - pw.flush() - return sw.toString() - } else { - val b = StringBuilder() - val date = DATE_FORMAT.format(Date(r.millis)) - b.append(date) - b.append("Z ") - b.append(r.message) - b.append(LINE_SEPARATOR) - return b.toString() - } - } - - companion object { - private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n" - - // private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) - private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US) - - private var mIsTimeZoneSet = false } + .orEmpty() } /** @@ -193,20 +132,15 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec * @param throwable the throwable to log */ private fun logToFile(throwable: Throwable?) { - if (null == sCacheDirectory || throwable == null) { - return - } + throwable ?: return val errors = StringWriter() throwable.printStackTrace(PrintWriter(errors)) - sLogger.info(errors.toString()) + logger.info(errors.toString()) } private fun logToFile(level: String, tag: String, content: String) { - if (null == sCacheDirectory) { - return - } val b = StringBuilder() b.append(Thread.currentThread().id) b.append(" ") @@ -215,6 +149,6 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec b.append(tag) b.append(": ") b.append(content) - sLogger.info(b.toString()) + logger.info(b.toString()) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt index 49cb75c9d6..80af64d9f3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException data class DeactivateAccountViewState( val passwordShown: Boolean = false @@ -64,7 +65,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) } else { - uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException()))) + uiaContinuation?.resumeWithException(IllegalArgumentException()) } } is DeactivateAccountAction.PasswordAuthDone -> { @@ -79,7 +80,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v } DeactivateAccountAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") - uiaContinuation?.resumeWith(Result.failure((Exception()))) + uiaContinuation?.resumeWithException(Exception()) uiaContinuation = null pendingAuth = null } @@ -98,13 +99,15 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v viewModelScope.launch { val event = try { session.deactivateAccount( + action.eraseAllData, object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { _viewEvents.post(DeactivateAccountViewEvents.RequestReAuth(flowResponse, errCode)) pendingAuth = DefaultBaseAuth(session = flowResponse.session) uiaContinuation = promise } - }, action.eraseAllData) + } + ) DeactivateAccountViewEvents.Done } catch (failure: Exception) { if (failure.isInvalidUIAAuth()) { diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 566dea2cd4..8bdf97b6ec 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -49,6 +49,7 @@ import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException class CrossSigningSettingsViewModel @AssistedInject constructor( @Assisted private val initialState: CrossSigningSettingsViewState, @@ -130,7 +131,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) } else { - uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException()))) + uiaContinuation?.resumeWithException(IllegalArgumentException()) } } is CrossSigningSettingsAction.PasswordAuthDone -> { @@ -146,7 +147,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( CrossSigningSettingsAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") _viewEvents.post(CrossSigningSettingsViewEvents.HideModalWaitingView) - uiaContinuation?.resumeWith(Result.failure((Exception()))) + uiaContinuation?.resumeWithException(Exception()) uiaContinuation = null pendingAuth = null } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index a56a7b8d48..c48b08e806 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -64,6 +64,7 @@ import java.util.concurrent.TimeUnit import javax.net.ssl.HttpsURLConnection import kotlin.coroutines.Continuation import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException data class DevicesViewState( val myDeviceId: String = "", @@ -217,7 +218,7 @@ class DevicesViewModel @AssistedInject constructor( if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) } else { - uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException()))) + uiaContinuation?.resumeWithException(IllegalArgumentException()) } Unit } @@ -235,7 +236,7 @@ class DevicesViewModel @AssistedInject constructor( DevicesAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") // _viewEvents.post(DevicesViewEvents.Loading) - uiaContinuation?.resumeWith(Result.failure((Exception()))) + uiaContinuation?.resumeWithException(Exception()) uiaContinuation = null pendingAuth = null } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index a1d4d6227b..89d632b813 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -46,6 +46,7 @@ import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException class ThreePidsSettingsViewModel @AssistedInject constructor( @Assisted initialState: ThreePidsSettingsViewState, @@ -140,7 +141,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) } else { - uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException()))) + uiaContinuation?.resumeWithException(IllegalArgumentException()) } } is ThreePidsSettingsAction.PasswordAuthDone -> { @@ -155,7 +156,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } ThreePidsSettingsAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") - uiaContinuation?.resumeWith(Result.failure((Exception()))) + uiaContinuation?.resumeWithException(Exception()) uiaContinuation = null pendingAuth = null } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index 98ec2d07de..2c0b78a546 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -25,19 +25,17 @@ 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.di.ActiveSessionHolder import im.vector.app.core.extensions.hasUnsavedKeys import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.login.LoginMode import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.Cancelable import timber.log.Timber /** @@ -76,54 +74,49 @@ class SoftLogoutViewModel @AssistedInject constructor( } } - private var currentTask: Cancelable? = null - init { // Get the supported login flow getSupportedLoginFlow() } private fun getSupportedLoginFlow() { - currentTask?.cancel() - currentTask = null - authenticationService.cancelPendingLoginOrRegistration() + viewModelScope.launch { + authenticationService.cancelPendingLoginOrRegistration() - setState { - copy( - asyncHomeServerLoginFlowRequest = Loading() - ) - } + setState { + copy( + asyncHomeServerLoginFlowRequest = Loading() + ) + } - currentTask = authenticationService.getLoginFlowOfSession(session.sessionId, object : MatrixCallback { - override fun onFailure(failure: Throwable) { + val data = try { + authenticationService.getLoginFlowOfSession(session.sessionId) + } catch (failure: Throwable) { setState { copy( asyncHomeServerLoginFlowRequest = Fail(failure) ) } + null } - override fun onSuccess(data: LoginFlowResult) { - when (data) { - is LoginFlowResult.Success -> { - val loginMode = when { - // SSO login is taken first - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) - && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } + if (data is LoginFlowResult.Success) { + val loginMode = when { + // SSO login is taken first + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) + && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password + else -> LoginMode.Unsupported + } - setState { - copy( - asyncHomeServerLoginFlowRequest = Success(loginMode) - ) - } - } + setState { + copy( + asyncHomeServerLoginFlowRequest = Success(loginMode) + ) } } - }) + } } override fun handle(action: SoftLogoutAction) { @@ -227,9 +220,4 @@ class SoftLogoutViewModel @AssistedInject constructor( ) } } - - override fun onCleared() { - currentTask?.cancel() - super.onCleared() - } } diff --git a/vector/src/main/res/layout/fragment_login_signup_signin_selection.xml b/vector/src/main/res/layout/fragment_login_signup_signin_selection.xml index 56d4e37f1e..f193a217b8 100644 --- a/vector/src/main/res/layout/fragment_login_signup_signin_selection.xml +++ b/vector/src/main/res/layout/fragment_login_signup_signin_selection.xml @@ -81,17 +81,19 @@ app:layout_constraintTop_toBottomOf="@+id/loginSignupSigninSubmit" tools:visibility="visible" /> - + + app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn" + tools:visibility="visible">