From 08420e15f0c9ddc8d09a3609574caffc8201ee86 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 14 Dec 2021 16:54:53 +0100 Subject: [PATCH] Import v1.3.10 from Element Android --- dependencies.gradle | 6 +- dependencies_groups.gradle | 201 +++++++++++++++ matrix-sdk-android/build.gradle | 10 +- .../android/sdk/common/CommonTestHelper.kt | 161 +++++++----- .../android/sdk/common/CryptoTestHelper.kt | 179 ++++++------- .../TestBackgroundDetectionObserver.kt} | 19 +- .../{api/Matrix.kt => common/TestMatrix.kt} | 29 ++- .../android/sdk/common/TestMatrixComponent.kt | 5 +- .../matrix/android/sdk/common/TestModule.kt | 29 +++ .../android/sdk/common/TestNetworkModule.kt | 39 --- .../sdk/internal/crypto/PreShareKeysTest.kt | 22 +- .../sdk/internal/crypto/UnwedgingTest.kt | 22 +- .../crypto/crosssigning/XSigningTest.kt | 46 ++-- .../crypto/encryption/EncryptionTest.kt | 19 +- .../crypto/gossiping/KeyShareTests.kt | 104 ++++---- .../crypto/gossiping/WithHeldTests.kt | 76 +++--- .../crypto/keysbackup/KeysBackupTest.kt | 206 ++++++++------- .../crypto/keysbackup/KeysBackupTestHelper.kt | 22 +- .../sdk/internal/crypto/ssss/QuadSTests.kt | 151 +++++------ .../internal/crypto/verification/SASTest.kt | 84 +++---- .../verification/qrcode/VerificationTest.kt | 14 +- .../session/room/timeline/RoomDataHelper.kt | 4 +- .../timeline/TimelineForwardPaginationTest.kt | 10 +- .../sdk/session/search/SearchMessagesTest.kt | 17 -- .../sdk/session/space/SpaceCreationTest.kt | 60 ++--- .../sdk/session/space/SpaceHierarchyTest.kt | 237 ++++++------------ .../java/org/matrix/android/sdk/api/Matrix.kt | 11 +- .../sdk/api/session/events/model/EventType.kt | 2 + .../session/room/model/PollSummaryContent.kt | 25 +- .../model/message/MessageEndPollContent.kt | 29 +++ .../model/message/MessageOptionsContent.kt | 40 --- .../room/model/message/MessagePollContent.kt | 14 +- .../message/MessagePollResponseContent.kt | 14 +- .../session/room/model/message/MessageType.kt | 9 +- .../{OptionItem.kt => PollResponse.kt} | 10 +- .../sdk/api/session/room/send/SendService.kt | 12 +- .../room/summary/RoomSummaryConstants.kt | 3 +- .../session/room/timeline/TimelineEvent.kt | 9 +- .../api/session/sync/model/SyncResponse.kt | 8 + .../sdk/api/session/terms/TermsService.kt | 8 + .../crypto/CancelGossipRequestWorker.kt | 6 +- .../internal/crypto/DefaultCryptoService.kt | 11 +- .../sdk/internal/crypto/MXOlmDevice.kt | 45 ++++ .../internal/crypto/OneTimeKeysUploader.kt | 135 +++++++--- .../crypto/SendGossipRequestWorker.kt | 6 +- .../sdk/internal/crypto/SendGossipWorker.kt | 6 +- .../crypto/crosssigning/UpdateTrustWorker.kt | 6 +- .../crypto/model/rest/KeysUploadBody.kt | 9 +- .../internal/crypto/tasks/UploadKeysTask.kt | 6 +- .../SendVerificationMessageWorker.kt | 6 +- .../sdk/internal/di/MatrixComponent.kt | 3 + .../android/sdk/internal/di/MoshiProvider.kt | 4 +- .../android/sdk/internal/di/NoOpTestModule.kt | 9 + .../sdk/internal/di/WorkManagerProvider.kt | 37 ++- .../internal/session/call/model/MxCallImpl.kt | 7 +- .../session/content/UploadContentWorker.kt | 5 +- .../session/group/GetGroupDataWorker.kt | 5 +- .../notification/ProcessEventForPushTask.kt | 1 + .../session/pushers/AddPusherWorker.kt | 5 +- .../EventRelationsAggregationProcessor.kt | 125 +++++++-- .../room/prune/RedactionEventProcessor.kt | 3 +- .../session/room/send/DefaultSendService.kt | 10 +- .../room/send/LocalEchoEventFactory.kt | 69 +++-- .../MultipleEventSendingDispatcherWorker.kt | 5 +- .../session/room/send/RedactEventWorker.kt | 5 +- .../session/room/send/SendEventWorker.kt | 6 +- .../internal/session/sync/SyncTokenStore.kt | 5 +- .../internal/session/sync/job/SyncWorker.kt | 8 +- .../session/terms/DefaultTermsService.kt | 26 +- .../sdk/internal/session/terms/TermsAPI.kt | 10 + .../util/BackgroundDetectionObserver.kt | 33 +-- .../internal/worker/MatrixWorkerFactory.kt | 85 ++++++- .../worker/SessionSafeCoroutineWorker.kt | 4 +- .../android/sdk/internal/worker/Worker.kt | 25 -- 74 files changed, 1591 insertions(+), 1106 deletions(-) create mode 100644 dependencies_groups.gradle rename matrix-sdk-android/src/{main/java/org/matrix/android/sdk/internal/worker/DelegateWorkerFactory.kt => androidTest/java/org/matrix/android/sdk/common/TestBackgroundDetectionObserver.kt} (53%) rename matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/{api/Matrix.kt => common/TestMatrix.kt} (80%) delete mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/{OptionItem.kt => PollResponse.kt} (75%) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Worker.kt diff --git a/dependencies.gradle b/dependencies.gradle index 1a04fe07..4a076a23 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,11 +7,11 @@ ext.versions = [ 'targetCompat' : JavaVersion.VERSION_11, ] -def gradle = "7.0.3" +def gradle = "7.0.4" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.5.31" def kotlinCoroutines = "1.5.2" -def dagger = "2.40.3" +def dagger = "2.40.5" def retrofit = "2.9.0" def arrow = "0.8.2" def markwon = "4.6.2" @@ -19,7 +19,7 @@ def moshi = "1.12.0" def lifecycle = "2.4.0" def flowBinding = "1.2.0" def epoxy = "4.6.2" -def mavericks = "2.4.0" +def mavericks = "2.5.0" def glide = "4.12.0" def bigImageViewer = "1.8.1" def jjwt = "0.11.2" diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle new file mode 100644 index 00000000..25a78bc0 --- /dev/null +++ b/dependencies_groups.gradle @@ -0,0 +1,201 @@ +ext.groups = [ + jitpack : [ + regex: [ + ], + group: [ + 'com.github.Armen101', + 'com.github.BillCarsonFr', + 'com.github.chrisbanes', + 'com.github.hyuwah', + 'com.github.jetradarmobile', + 'com.github.tapadoo', + 'com.github.vector-im', + 'com.github.yalantis', + 'com.github.Zhuinden', + ] + ], + olm : [ + regex: [ + ], + group: [ + 'org.matrix.android', + ] + ], + jitsi : [ + regex: [ + ], + group: [ + 'com.facebook.react', + 'org.jitsi.react', + 'org.webkit', + ] + ], + google : [ + regex: [ + 'androidx\\..*', + 'com\\.android\\.tools\\..*', + 'com\\.google\\.android\\..*', + ], + group: [ + 'com.google.firebase', + 'com.android', + 'com.android.tools', + ] + ], + mavenCentral: [ + regex: [ + ], + group: [ + 'com.adevinta.android', + 'com.airbnb.android', + 'com.almworks.sqlite4java', + 'com.arthenica', + 'com.atlassian.commonmark', + 'com.atlassian.pom', + 'com.beust', + 'com.davemorrissey.labs', + 'com.dropbox.core', + 'com.facebook.fresco', + 'com.facebook.infer.annotation', + 'com.facebook.soloader', + 'com.facebook.stetho', + 'com.fasterxml', + 'com.fasterxml.jackson', + 'com.fasterxml.jackson.core', + 'com.gabrielittner.threetenbp', + 'com.getkeepsafe.relinker', + 'com.github.bumptech.glide', + 'com.github.filippudak', + 'com.github.filippudak.progresspieview', + 'com.github.javaparser', + 'com.github.piasy', + 'com.github.shyiko.klob', + 'com.google', + 'com.google.auto.service', + 'com.google.auto.value', + 'com.google.code.findbugs', + 'com.google.code.gson', + 'com.google.dagger', + 'com.google.devtools.ksp', + 'com.google.errorprone', + 'com.google.googlejavaformat', + 'com.google.guava', + 'com.google.j2objc', + 'com.google.jimfs', + 'com.google.protobuf', + 'com.google.zxing', + 'com.googlecode.htmlcompressor', + 'com.googlecode.json-simple', + 'com.googlecode.libphonenumber', + 'com.ibm.icu', + 'com.jakewharton.android.repackaged', + 'com.jakewharton.timber', + 'com.linkedin.dexmaker', + 'com.nulab-inc', + 'com.otaliastudios.opengl', + 'com.parse.bolts', + 'com.pinterest', + 'com.pinterest.ktlint', + 'com.posthog.android', + 'com.squareup', + 'com.squareup.duktape', + 'com.squareup.moshi', + 'com.squareup.okhttp3', + 'com.squareup.okio', + 'com.squareup.retrofit2', + 'com.sun.activation', + 'com.sun.istack', + 'com.sun.xml.bind', + 'com.sun.xml.bind.mvn', + 'com.sun.xml.fastinfoset', + 'com.thoughtworks.qdox', + 'com.vanniktech', + 'commons-cli', + 'commons-codec', + 'commons-io', + 'commons-logging', + 'info.picocli', + 'io.arrow-kt', + 'io.github.detekt.sarif4k', + 'io.github.reactivecircus.flowbinding', + 'io.jsonwebtoken', + 'io.kindedj', + 'io.mockk', + 'io.noties.markwon', + 'io.reactivex.rxjava2', + 'io.realm', + 'it.unimi.dsi', + 'jakarta.activation', + 'jakarta.xml.bind', + 'javax.annotation', + 'javax.inject', + 'jline', + 'jp.wasabeef', + 'junit', + 'me.leolin', + 'me.saket', + 'net.bytebuddy', + 'net.java', + 'net.java.dev.jna', + 'net.lachlanmckee', + 'net.ltgt.gradle.incap', + 'net.sf.jopt-simple', + 'net.sf.kxml', + 'nl.dionsegijn', + 'org.amshove.kluent', + 'org.apache', + 'org.apache.ant', + 'org.apache.commons', + 'org.apache.httpcomponents', + 'org.apache.sanselan', + 'org.bouncycastle', + 'org.checkerframework', + 'org.codehaus', + 'org.codehaus.groovy', + 'org.codehaus.mojo', + 'org.eclipse.ee4j', + 'org.ec4j.core', + 'org.glassfish.jaxb', + 'org.hamcrest', + 'org.jetbrains', + 'org.jetbrains.intellij.deps', + 'org.jetbrains.kotlin', + 'org.jetbrains.kotlinx', + 'org.jsoup', + 'org.junit', + 'org.junit.jupiter', + 'org.junit.platform', + 'org.jvnet.staxex', + 'org.mockito', + 'org.mongodb', + 'org.objenesis', + 'org.opentest4j', + 'org.ow2', + 'org.ow2.asm', + 'org.ow2.asm', + 'org.reactivestreams', + 'org.robolectric', + 'org.slf4j', + 'org.sonatype.oss', + 'org.testng', + 'org.threeten', + 'xerces', + 'xml-apis', + ] + ], + jcenter : [ + regex: [ + ], + group: [ + 'com.amulyakhare', + 'com.otaliastudios', + 'com.yqritc', + // https://github.com/cmelchior/realmfieldnameshelper/issues/42 + 'dk.ilios', + 'im.dlg', + 'me.dm7.barcodescanner', + 'me.gujun.android', + ] + ] +] + diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 3c59e345..b572b176 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -11,7 +11,7 @@ buildscript { mavenCentral() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.8.1" + classpath "io.realm:realm-gradle-plugin:10.9.0" } } @@ -143,8 +143,8 @@ dependencies { implementation libs.arrow.core implementation libs.arrow.instances - // olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm - implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4' + // olm lib is now hosted by maven at https://gitlab.matrix.org/api/v4/projects/27/packages/maven + implementation 'org.matrix.android:olm:3.2.7' // DI implementation libs.dagger.dagger @@ -161,10 +161,10 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.38' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39' testImplementation libs.tests.junit - testImplementation 'org.robolectric:robolectric:4.7.2' + testImplementation 'org.robolectric:robolectric:4.7.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 libs.mockk.mockk 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 cf9b8f87..8e218285 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 @@ -20,9 +20,10 @@ import android.content.Context import android.net.Uri import androidx.lifecycle.Observer import androidx.test.internal.runner.junit4.statement.UiThreadStatement -import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -30,7 +31,6 @@ import kotlinx.coroutines.withTimeout import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue -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 @@ -45,7 +45,7 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.sync.SyncState -import java.util.ArrayList +import timber.log.Timber import java.util.UUID import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -56,13 +56,14 @@ import java.util.concurrent.TimeUnit */ class CommonTestHelper(context: Context) { - val matrix: Matrix + internal val matrix: TestMatrix + val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) - fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor + fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor init { UiThreadStatement.runOnUiThread { - Matrix.initialize( + TestMatrix.initialize( context, MatrixConfiguration( applicationFlavor = "TestFlavor", @@ -70,7 +71,7 @@ class CommonTestHelper(context: Context) { ) ) } - matrix = Matrix.getInstance(context) + matrix = TestMatrix.getInstance(context) } fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session { @@ -95,31 +96,45 @@ class CommonTestHelper(context: Context) { * * @param session the session to sync */ - @Suppress("EXPERIMENTAL_API_USAGE") - fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) { + fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) { val lock = CountDownLatch(1) - - val job = GlobalScope.launch(Dispatchers.Main) { - session.open() + coroutineScope.launch { + session.startSync(true) + val syncLiveData = session.getSyncStateLive() + val syncObserver = object : Observer<SyncState> { + override fun onChanged(t: SyncState?) { + if (session.hasAlreadySynced()) { + lock.countDown() + syncLiveData.removeObserver(this) + } + } + } + syncLiveData.observeForever(syncObserver) } - runBlocking { job.join() } - - session.startSync(true) + await(lock, timeout) + } - val syncLiveData = runBlocking(Dispatchers.Main) { - session.getSyncStateLive() - } - val syncObserver = object : Observer<SyncState> { - override fun onChanged(t: SyncState?) { - if (session.hasAlreadySynced()) { - lock.countDown() - syncLiveData.removeObserver(this) + /** + * This methods clear the cache and waits for initialSync + * + * @param session the session to sync + */ + fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) { + waitWithLatch(timeout) { latch -> + session.clearCache() + val syncLiveData = session.getSyncStateLive() + val syncObserver = object : Observer<SyncState> { + override fun onChanged(t: SyncState?) { + if (session.hasAlreadySynced()) { + Timber.v("Clear cache and synced") + syncLiveData.removeObserver(this) + latch.countDown() + } } } + syncLiveData.observeForever(syncObserver) + session.startSync(true) } - GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) } - - await(lock, timeout) } /** @@ -130,46 +145,57 @@ class CommonTestHelper(context: Context) { * @param nbOfMessages the number of time the message will be sent */ fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> { - val timeline = room.createTimeline(null, TimelineSettings(10)) val sentEvents = ArrayList<TimelineEvent>(nbOfMessages) - val latch = CountDownLatch(1) - val timelineListener = object : Timeline.Listener { - override fun onTimelineFailure(throwable: Throwable) { - } + val timeline = room.createTimeline(null, TimelineSettings(10)) + timeline.start() + waitWithLatch(timeout + 1_000L * nbOfMessages) { latch -> + val timelineListener = object : Timeline.Listener { + override fun onTimelineFailure(throwable: Throwable) { + } - override fun onNewTimelineEvents(eventIds: List<String>) { - // noop - } + override fun onNewTimelineEvents(eventIds: List<String>) { + // noop + } - override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { - val newMessages = snapshot - .filter { it.root.sendState == SendState.SYNCED } - .filter { it.root.getClearType() == EventType.MESSAGE } - .filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true } - - if (newMessages.size == nbOfMessages) { - sentEvents.addAll(newMessages) - // Remove listener now, if not at the next update sendEvents could change - timeline.removeListener(this) - latch.countDown() + override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { + val newMessages = snapshot + .filter { it.root.sendState == SendState.SYNCED } + .filter { it.root.getClearType() == EventType.MESSAGE } + .filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true } + + Timber.v("New synced message size: ${newMessages.size}") + if (newMessages.size == nbOfMessages) { + sentEvents.addAll(newMessages) + // Remove listener now, if not at the next update sendEvents could change + timeline.removeListener(this) + latch.countDown() + } } } + timeline.addListener(timelineListener) + sendTextMessagesBatched(room, message, nbOfMessages) } - timeline.start() - timeline.addListener(timelineListener) - for (i in 0 until nbOfMessages) { - room.sendTextMessage(message + " #" + (i + 1)) - } - // Wait 3 second more per message - await(latch, timeout = timeout + 3_000L * nbOfMessages) timeline.dispose() - // Check that all events has been created assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong()) - return sentEvents } + /** + * Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync + */ + private fun sendTextMessagesBatched(room: Room, message: String, count: Int) { + (1 until count + 1) + .map { "$message #$it" } + .chunked(10) + .forEach { batchedMessages -> + batchedMessages.forEach { formattedMessage -> + room.sendTextMessage(formattedMessage) + } + Thread.sleep(1_000L) + } + } + // PRIVATE METHODS ***************************************************************************** /** @@ -239,10 +265,10 @@ class CommonTestHelper(context: Context) { assertTrue(registrationResult is RegistrationResult.Success) val session = (registrationResult as RegistrationResult.Success).session + session.open() if (sessionTestParams.withInitialSync) { syncSession(session, 60_000) } - return session } @@ -267,7 +293,7 @@ class CommonTestHelper(context: Context) { .getLoginWizard() .login(userName, password, "myDevice") } - + session.open() if (sessionTestParams.withInitialSync) { syncSession(session) } @@ -332,22 +358,21 @@ class CommonTestHelper(context: Context) { assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) } - @Suppress("EXPERIMENTAL_API_USAGE") - fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { - GlobalScope.launch { - while (true) { - delay(1000) - if (condition()) { - latch.countDown() - return@launch - } + suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { + while (true) { + delay(1000) + if (condition()) { + latch.countDown() + return } } } - fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) { + fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, dispatcher: CoroutineDispatcher = Dispatchers.Main, block: suspend (CountDownLatch) -> Unit) { val latch = CountDownLatch(1) - block(latch) + coroutineScope.launch(dispatcher) { + block(latch) + } await(latch, timeout) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index a8cbc160..ccea6f53 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -19,10 +19,6 @@ package org.matrix.android.sdk.common import android.os.SystemClock import android.util.Log import androidx.lifecycle.Observer -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -31,6 +27,7 @@ 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.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction @@ -44,16 +41,16 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo import java.util.UUID -import java.util.concurrent.CountDownLatch import kotlin.coroutines.Continuation import kotlin.coroutines.resume -class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { +class CryptoTestHelper(private val testHelper: CommonTestHelper) { private val messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!") private val messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.") @@ -64,27 +61,33 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { * @return alice session */ fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) - val roomId = mTestHelper.runBlockingTest { + val roomId = testHelper.runBlockingTest { aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }) } - if (encryptedRoom) { - val room = aliceSession.getRoom(roomId)!! - - mTestHelper.runBlockingTest { + testHelper.waitWithLatch { latch -> + val room = aliceSession.getRoom(roomId)!! room.enableEncryption() + val roomSummaryLive = room.getRoomSummaryLive() + val roomSummaryObserver = object : Observer<Optional<RoomSummary>> { + override fun onChanged(roomSummary: Optional<RoomSummary>) { + if (roomSummary.getOrNull()?.isEncrypted.orFalse()) { + roomSummaryLive.removeObserver(this) + latch.countDown() + } + } + } + roomSummaryLive.observeForever(roomSummaryObserver) } } - return CryptoTestData(roomId, listOf(aliceSession)) } /** * @return alice and bob sessions */ - @Suppress("EXPERIMENTAL_API_USAGE") fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData { val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom) val aliceSession = cryptoTestData.firstSession @@ -92,54 +95,37 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val aliceRoom = aliceSession.getRoom(aliceRoomId)!! - val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) - - val lock1 = CountDownLatch(1) + val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) - val bobRoomSummariesLive = runBlocking(Dispatchers.Main) { - bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) - } - - val newRoomObserver = object : Observer<List<RoomSummary>> { - override fun onChanged(t: List<RoomSummary>?) { - if (t?.isNotEmpty() == true) { - lock1.countDown() - bobRoomSummariesLive.removeObserver(this) + testHelper.waitWithLatch { latch -> + val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) + val newRoomObserver = object : Observer<List<RoomSummary>> { + override fun onChanged(t: List<RoomSummary>?) { + if (t?.isNotEmpty() == true) { + bobRoomSummariesLive.removeObserver(this) + latch.countDown() + } } } - } - - GlobalScope.launch(Dispatchers.Main) { bobRoomSummariesLive.observeForever(newRoomObserver) - } - - mTestHelper.runBlockingTest { aliceRoom.invite(bobSession.myUserId) } - mTestHelper.await(lock1) - - val lock = CountDownLatch(1) - - val roomJoinedObserver = object : Observer<List<RoomSummary>> { - override fun onChanged(t: List<RoomSummary>?) { - if (bobSession.getRoom(aliceRoomId) - ?.getRoomMember(aliceSession.myUserId) - ?.membership == Membership.JOIN) { - lock.countDown() - bobRoomSummariesLive.removeObserver(this) + testHelper.waitWithLatch { latch -> + val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) + val roomJoinedObserver = object : Observer<List<RoomSummary>> { + override fun onChanged(t: List<RoomSummary>?) { + if (bobSession.getRoom(aliceRoomId) + ?.getRoomMember(bobSession.myUserId) + ?.membership == Membership.JOIN) { + bobRoomSummariesLive.removeObserver(this) + latch.countDown() + } } } - } - - GlobalScope.launch(Dispatchers.Main) { bobRoomSummariesLive.observeForever(roomJoinedObserver) + bobSession.joinRoom(aliceRoomId) } - - mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) } - - mTestHelper.await(lock) - // Ensure bob can send messages to the room // val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! // assertNotNull(roomFromBobPOV.powerLevels) @@ -171,13 +157,13 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { * @Return Sam session */ fun createSamAccountAndInviteToTheRoom(room: Room): Session { - val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) + val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { room.invite(samSession.myUserId, null) } - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { samSession.joinRoom(room.roomId, null, emptyList()) } @@ -194,23 +180,20 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val bobSession = cryptoTestData.secondSession!! bobSession.cryptoService().setWarnOnUnknownDevices(false) - aliceSession.cryptoService().setWarnOnUnknownDevices(false) val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! // Alice sends a message - mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1) -// roomFromAlicePOV.sendTextMessage(messagesFromAlice[0]) + testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1) // Bob send 3 messages - mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1) - mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1) - mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1) + testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1) + testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1) + testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1) // Alice sends a message - mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1) - + testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1) return cryptoTestData } @@ -256,60 +239,44 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { ) } - @Suppress("EXPERIMENTAL_API_USAGE") fun createDM(alice: Session, bob: Session): String { - val roomId = mTestHelper.runBlockingTest { - alice.createDirectRoom(bob.myUserId) - } - - mTestHelper.waitWithLatch { latch -> - val bobRoomSummariesLive = runBlocking(Dispatchers.Main) { - bob.getRoomSummariesLive(roomSummaryQueryParams { }) - } - + var roomId: String = "" + testHelper.waitWithLatch { latch -> + roomId = alice.createDirectRoom(bob.myUserId) + val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { }) val newRoomObserver = object : Observer<List<RoomSummary>> { override fun onChanged(t: List<RoomSummary>?) { val indexOfFirst = t?.indexOfFirst { it.roomId == roomId } ?: -1 if (indexOfFirst != -1) { - latch.countDown() bobRoomSummariesLive.removeObserver(this) + latch.countDown() } } } - - GlobalScope.launch(Dispatchers.Main) { - bobRoomSummariesLive.observeForever(newRoomObserver) - } + bobRoomSummariesLive.observeForever(newRoomObserver) } - mTestHelper.waitWithLatch { latch -> - val bobRoomSummariesLive = runBlocking(Dispatchers.Main) { - bob.getRoomSummariesLive(roomSummaryQueryParams { }) - } - + testHelper.waitWithLatch { latch -> + val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { }) val newRoomObserver = object : Observer<List<RoomSummary>> { override fun onChanged(t: List<RoomSummary>?) { if (bob.getRoom(roomId) ?.getRoomMember(bob.myUserId) ?.membership == Membership.JOIN) { - latch.countDown() bobRoomSummariesLive.removeObserver(this) + latch.countDown() } } } - - GlobalScope.launch(Dispatchers.Main) { - bobRoomSummariesLive.observeForever(newRoomObserver) - } - - mTestHelper.runBlockingTest { bob.joinRoom(roomId) } + bobRoomSummariesLive.observeForever(newRoomObserver) + bob.joinRoom(roomId) } return roomId } fun initializeCrossSigning(session: Session) { - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { session.cryptoService().crossSigningService() .initializeCrossSigning( object : UserInteractiveAuthInterceptor { @@ -346,8 +313,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { var bobPovTx: IncomingSasVerificationTransaction? = null // wait for alice to get the ready - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}") if (bobPovTx?.state == VerificationTxState.OnStarted) { @@ -359,16 +326,16 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } } - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}") alicePovTx?.state == VerificationTxState.ShortCodeReady } } // wait for alice to get the ready - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}") if (bobPovTx?.state == VerificationTxState.OnStarted) { @@ -383,38 +350,38 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { bobPovTx!!.userHasVerifiedShortCode() alicePovTx!!.userHasVerifiedShortCode() - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId) } } - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId) } } } fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) aliceSession.cryptoService().setWarnOnUnknownDevices(false) - val roomId = mTestHelper.runBlockingTest { + val roomId = testHelper.runBlockingTest { aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }) } val room = aliceSession.getRoom(roomId)!! - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { room.enableEncryption() } val sessions = mutableListOf(aliceSession) for (index in 1 until numberOfMembers) { - val session = mTestHelper.createAccount("User_$index", defaultSessionParams) - mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } + val session = testHelper.createAccount("User_$index", defaultSessionParams) + testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } println("TEST -> " + session.myUserId + " invited") - mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) } + testHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) } println("TEST -> " + session.myUserId + " joined") sessions.add(session) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/DelegateWorkerFactory.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestBackgroundDetectionObserver.kt similarity index 53% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/DelegateWorkerFactory.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestBackgroundDetectionObserver.kt index 68e1953e..c1596c28 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/DelegateWorkerFactory.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestBackgroundDetectionObserver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 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. @@ -14,13 +14,18 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.worker +package org.matrix.android.sdk.common -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerParameters +import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver -interface DelegateWorkerFactory { +/** + * Force foreground for testing + */ +internal class TestBackgroundDetectionObserver : BackgroundDetectionObserver { + + override val isInBackground: Boolean = false + + override fun register(listener: BackgroundDetectionObserver.Listener) = Unit - fun create(context: Context, params: WorkerParameters): ListenableWorker + override fun unregister(listener: BackgroundDetectionObserver.Listener) = Unit } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt similarity index 80% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt index 8b9b6efa..e92232a7 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api +package org.matrix.android.sdk.common import android.content.Context import android.os.Handler @@ -24,27 +24,27 @@ import androidx.work.Configuration import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.BuildConfig +import org.matrix.android.sdk.api.MatrixConfiguration 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.api.network.ApiInterceptorListener import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.raw.RawService -import org.matrix.android.sdk.common.DaggerTestMatrixComponent import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.network.ApiInterceptor import org.matrix.android.sdk.internal.network.UserAgentHolder import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver +import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import org.matrix.olm.OlmManager import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject /** - * This is the main entry point to the matrix sdk. - * To get the singleton instance, use getInstance static method. + * This mimics the Matrix class but using TestMatrixComponent internally instead of regular MatrixComponent. */ -class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) { +internal class TestMatrix constructor(context: Context, matrixConfiguration: MatrixConfiguration) { @Inject internal lateinit var legacySessionImporter: LegacySessionImporter @Inject internal lateinit var authenticationService: AuthenticationService @@ -55,15 +55,18 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService @Inject internal lateinit var apiInterceptor: ApiInterceptor + @Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory private val uiHandler = Handler(Looper.getMainLooper()) init { Monarchy.init(context) DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this) - if (context.applicationContext !is Configuration.Provider) { - WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build()) - } + val configuration = Configuration.Builder() + .setExecutor(Executors.newCachedThreadPool()) + .setWorkerFactory(matrixWorkerFactory) + .build() + WorkManager.initialize(context, configuration) uiHandler.post { ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) } @@ -93,21 +96,21 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo companion object { - private lateinit var instance: Matrix + private lateinit var instance: TestMatrix private val isInit = AtomicBoolean(false) fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) { if (isInit.compareAndSet(false, true)) { - instance = Matrix(context.applicationContext, matrixConfiguration) + instance = TestMatrix(context.applicationContext, matrixConfiguration) } } - fun getInstance(context: Context): Matrix { + fun getInstance(context: Context): TestMatrix { if (isInit.compareAndSet(false, true)) { val appContext = context.applicationContext if (appContext is MatrixConfiguration.Provider) { val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration() - instance = Matrix(appContext, matrixConfiguration) + instance = TestMatrix(appContext, matrixConfiguration) } else { throw IllegalStateException("Matrix is not initialized properly." + " You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.") diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt index 1d05e655..d0f0e231 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt @@ -34,12 +34,13 @@ import org.matrix.android.sdk.internal.util.system.SystemModule NetworkModule::class, AuthModule::class, RawModule::class, - SystemModule::class, - TestNetworkModule::class + SystemModule::class ]) @MatrixScope internal interface TestMatrixComponent : MatrixComponent { + fun inject(matrix: TestMatrix) + @Component.Factory interface Factory { fun create(@BindsInstance context: Context, diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt index 3e4d5fe0..d82bac37 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt @@ -18,10 +18,39 @@ package org.matrix.android.sdk.common import dagger.Binds import dagger.Module +import dagger.Provides import org.matrix.android.sdk.internal.di.MatrixComponent +import org.matrix.android.sdk.internal.di.MatrixScope +import org.matrix.android.sdk.internal.session.MockHttpInterceptor +import org.matrix.android.sdk.internal.session.TestInterceptor +import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver @Module internal abstract class TestModule { @Binds abstract fun providesMatrixComponent(testMatrixComponent: TestMatrixComponent): MatrixComponent + + @Module + companion object { + + val interceptors = ArrayList<TestInterceptor>() + + fun interceptorForSession(sessionId: String): TestInterceptor? = interceptors.firstOrNull { it.sessionId == sessionId } + + @Provides + @JvmStatic + @MockHttpInterceptor + fun providesTestInterceptor(): TestInterceptor? { + return MockOkHttpInterceptor().also { + interceptors.add(it) + } + } + + @Provides + @JvmStatic + @MatrixScope + fun providesBackgroundDetectionObserver(): BackgroundDetectionObserver { + return TestBackgroundDetectionObserver() + } + } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt deleted file mode 100644 index 4cd92ca2..00000000 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt +++ /dev/null @@ -1,39 +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.common - -import dagger.Module -import dagger.Provides -import org.matrix.android.sdk.internal.session.MockHttpInterceptor -import org.matrix.android.sdk.internal.session.TestInterceptor - -@Module -internal object TestNetworkModule { - - val interceptors = ArrayList<TestInterceptor>() - - fun interceptorForSession(sessionId: String): TestInterceptor? = interceptors.firstOrNull { it.sessionId == sessionId } - - @Provides - @JvmStatic - @MockHttpInterceptor - fun providesTestInterceptor(): TestInterceptor? { - return MockOkHttpInterceptor().also { - interceptors.add(it) - } - } -} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt index 825fba57..d0f63227 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt @@ -36,12 +36,12 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent @FixMethodOrder(MethodSorters.JVM) class PreShareKeysTest : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) @Test fun ensure_outbound_session_happy_path() { - val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val e2eRoomID = testData.roomId val aliceSession = testData.firstSession val bobSession = testData.secondSession!! @@ -58,12 +58,12 @@ class PreShareKeysTest : InstrumentedTest { Log.d("#Test", "Room Key Received from alice $preShareCount") // Force presharing of new outbound key - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it) } - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { val newGossipCount = bobSession.cryptoService().getGossipingEvents().count { it.senderId == aliceSession.myUserId && it.getClearType() == EventType.ROOM_KEY @@ -88,16 +88,16 @@ class PreShareKeysTest : InstrumentedTest { assertEquals("The session received by bob should match what alice sent", 0, sharedIndex) // Just send a real message as test - val sentEvent = mTestHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first() + val sentEvent = testHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first() assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session") - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { bobSession.getRoom(e2eRoomID)?.getTimeLineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE } } - mTestHelper.signOutAndClose(aliceSession) - mTestHelper.signOutAndClose(bobSession) + testHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(bobSession) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt index cf31294e..458eae6a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt @@ -62,8 +62,8 @@ import kotlin.coroutines.resume class UnwedgingTest : InstrumentedTest { private lateinit var messagesReceivedByBob: List<TimelineEvent> - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) @Before fun init() { @@ -85,7 +85,7 @@ class UnwedgingTest : InstrumentedTest { */ @Test fun testUnwedging() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId @@ -133,7 +133,7 @@ class UnwedgingTest : InstrumentedTest { roomFromAlicePOV.sendTextMessage("First message") // Wait for the message to be received by Bob - mTestHelper.await(latch) + testHelper.await(latch) bobTimeline.removeListener(bobEventsListener) messagesReceivedByBob.size shouldBe 1 @@ -161,7 +161,7 @@ class UnwedgingTest : InstrumentedTest { roomFromAlicePOV.sendTextMessage("Second message") // Wait for the message to be received by Bob - mTestHelper.await(latch) + testHelper.await(latch) bobTimeline.removeListener(bobEventsListener) messagesReceivedByBob.size shouldBe 2 @@ -179,7 +179,7 @@ class UnwedgingTest : InstrumentedTest { aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId) // Wait for the message to be received by Bob - mTestHelper.waitWithLatch { + testHelper.waitWithLatch { bobEventsListener = createEventListener(it, 3) bobTimeline.addListener(bobEventsListener) messagesReceivedByBob = emptyList() @@ -201,11 +201,11 @@ class UnwedgingTest : InstrumentedTest { Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[1].root.getClearType()) Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[2].root.getClearType()) // Bob Should not be able to decrypt last message, because session could not be sent as the olm channel was wedged - mTestHelper.await(bobFinalLatch) + testHelper.await(bobFinalLatch) bobTimeline.removeListener(bobHasThreeDecryptedEventsListener) // It's a trick to force key request on fail to decrypt - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService() .initializeCrossSigning( object : UserInteractiveAuthInterceptor { @@ -222,8 +222,8 @@ class UnwedgingTest : InstrumentedTest { } // Wait until we received back the key - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { // we should get back the key and be able to decrypt val result = tryOrNull { bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "") @@ -235,7 +235,7 @@ class UnwedgingTest : InstrumentedTest { bobTimeline.dispose() - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index 44af87bc..d9cc7a8a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -45,14 +45,14 @@ import kotlin.coroutines.resume @FixMethodOrder(MethodSorters.NAME_ASCENDING) class XSigningTest : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) @Test fun test_InitializeAndStoreKeys() { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService() .initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { @@ -79,12 +79,12 @@ class XSigningTest : InstrumentedTest { assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified()) - mTestHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(aliceSession) } @Test fun test_CrossSigningCheckBobSeesTheKeys() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -98,21 +98,21 @@ class XSigningTest : InstrumentedTest { password = TestConstants.PASSWORD ) - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(aliceAuthParams) } }, it) } - mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(bobAuthParams) } }, it) } // Check that alice can see bob keys - mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) } + testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) } val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId) assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey()) @@ -124,13 +124,13 @@ class XSigningTest : InstrumentedTest { assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) - mTestHelper.signOutAndClose(aliceSession) - mTestHelper.signOutAndClose(bobSession) + testHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(bobSession) } @Test fun test_CrossSigningTestAliceTrustBobNewDevice() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -144,12 +144,12 @@ class XSigningTest : InstrumentedTest { password = TestConstants.PASSWORD ) - mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(aliceAuthParams) } }, it) } - mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(bobAuthParams) } @@ -157,21 +157,21 @@ class XSigningTest : InstrumentedTest { // Check that alice can see bob keys val bobUserId = bobSession.myUserId - mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) } + testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) } val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId) assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false) - mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) } + testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) } // Now bobs logs in on a new device and verifies it // We will want to test that in alice POV, this new device would be trusted by cross signing - val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true)) + val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true)) val bobSecondDeviceId = bobSession2.sessionParams.deviceId!! // Check that bob first session sees the new login - val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { + val data = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) } @@ -183,12 +183,12 @@ class XSigningTest : InstrumentedTest { assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) // Manually mark it as trusted from first session - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it) } // Now alice should cross trust bob's second device - val data2 = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { + val data2 = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) } @@ -200,8 +200,8 @@ class XSigningTest : InstrumentedTest { val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null) assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified()) - mTestHelper.signOutAndClose(aliceSession) - mTestHelper.signOutAndClose(bobSession) - mTestHelper.signOutAndClose(bobSession2) + testHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(bobSession) + testHelper.signOutAndClose(bobSession2) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt index da5e90ab..189fc405 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt @@ -40,8 +40,9 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class EncryptionTest : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) @Test fun test_EncryptionEvent() { @@ -69,7 +70,7 @@ class EncryptionTest : InstrumentedTest { } private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false) val aliceSession = cryptoTestData.firstSession val room = aliceSession.getRoom(cryptoTestData.roomId)!! @@ -101,12 +102,12 @@ class EncryptionTest : InstrumentedTest { timeline.addListener(timelineListener) action.invoke(room) - - mTestHelper.await(latch) + testHelper.await(latch) timeline.dispose() - - room.isEncrypted() shouldBe roomShouldBeEncrypted - - cryptoTestData.cleanUp(mTestHelper) + testHelper.waitWithLatch { + room.isEncrypted() shouldBe roomShouldBeEncrypted + it.countDown() + } + cryptoTestData.cleanUp(testHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index 40659cef..975d4816 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -44,7 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.internal.crypto.GossipingRequestState @@ -55,7 +54,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent -import java.util.concurrent.CountDownLatch import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -63,15 +61,14 @@ import kotlin.coroutines.resume @FixMethodOrder(MethodSorters.JVM) class KeyShareTests : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + private val commonTestHelper = CommonTestHelper(context()) @Test fun test_DoNotSelfShareIfNotTrusted() { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) // Create an encrypted room and add a message - val roomId = mTestHelper.runBlockingTest { + val roomId = commonTestHelper.runBlockingTest { aliceSession.createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE @@ -83,11 +80,11 @@ class KeyShareTests : InstrumentedTest { assertNotNull(room) Thread.sleep(4_000) assertTrue(room?.isEncrypted() == true) - val sentEventId = mTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId + val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId // Open a new sessionx - val aliceSession2 = mTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) val roomSecondSessionPOV = aliceSession2.getRoom(roomId) @@ -105,25 +102,24 @@ class KeyShareTests : InstrumentedTest { // Try to request aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root) - val waitLatch = CountDownLatch(1) val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId var outGoingRequestId: String? = null - mTestHelper.retryPeriodicallyWithLatch(waitLatch) { - aliceSession2.cryptoService().getOutgoingRoomKeyRequests() - .filter { req -> - // filter out request that was known before - !outgoingRequestsBefore.any { req.requestId == it.requestId } - } - .let { - val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId } - outGoingRequestId = outgoing?.requestId - outgoing != null - } + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + aliceSession2.cryptoService().getOutgoingRoomKeyRequests() + .filter { req -> + // filter out request that was known before + !outgoingRequestsBefore.any { req.requestId == it.requestId } + } + .let { + val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId } + outGoingRequestId = outgoing?.requestId + outgoing != null + } + } } - mTestHelper.await(waitLatch) - Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId") val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequests() @@ -134,8 +130,8 @@ class KeyShareTests : InstrumentedTest { // The first session should see an incoming request // the request should be refused, because the device is not trusted - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { // DEBUG LOGS aliceSession.cryptoService().getIncomingRoomKeyRequests().let { Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)") @@ -164,8 +160,8 @@ class KeyShareTests : InstrumentedTest { // Re request aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root) - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { aliceSession.cryptoService().getIncomingRoomKeyRequests().let { Log.v("TEST", "Incoming request Session 1") Log.v("TEST", "=========================") @@ -180,8 +176,8 @@ class KeyShareTests : InstrumentedTest { } Thread.sleep(6_000) - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let { it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED } } @@ -194,15 +190,15 @@ class KeyShareTests : InstrumentedTest { fail("should have been able to decrypt") } - mTestHelper.signOutAndClose(aliceSession) - mTestHelper.signOutAndClose(aliceSession2) + commonTestHelper.signOutAndClose(aliceSession) + commonTestHelper.signOutAndClose(aliceSession2) } @Test fun test_ShareSSSSSecret() { - val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - mTestHelper.doSync<Unit> { + commonTestHelper.doSync<Unit> { aliceSession1.cryptoService().crossSigningService() .initializeCrossSigning( object : UserInteractiveAuthInterceptor { @@ -218,25 +214,25 @@ class KeyShareTests : InstrumentedTest { } // Also bootstrap keybackup on first session - val creationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { + val creationInfo = commonTestHelper.doSync<MegolmBackupCreationInfo> { aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) } - val version = mTestHelper.doSync<KeysVersion> { + val version = commonTestHelper.doSync<KeysVersion> { aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it) } // Save it for gossiping aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) - val aliceSession2 = mTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true)) + val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true)) val aliceVerificationService1 = aliceSession1.cryptoService().verificationService() val aliceVerificationService2 = aliceSession2.cryptoService().verificationService() // force keys download - mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { + commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it) } - mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { + commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it) } @@ -276,8 +272,8 @@ class KeyShareTests : InstrumentedTest { aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId ?: "", txId) - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true } } @@ -290,31 +286,31 @@ class KeyShareTests : InstrumentedTest { // SSK and USK private keys should have been shared - mTestHelper.waitWithLatch(60_000) { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + commonTestHelper.waitWithLatch(60_000) { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}") aliceSession2.cryptoService().crossSigningService().canCrossSign() } } // Test that key backup key has been shared to - mTestHelper.waitWithLatch(60_000) { latch -> + commonTestHelper.waitWithLatch(60_000) { latch -> val keysBackupService = aliceSession2.cryptoService().keysBackupService() - mTestHelper.retryPeriodicallyWithLatch(latch) { + commonTestHelper.retryPeriodicallyWithLatch(latch) { Log.d("#TEST", "Recovery :${keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}") keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey } } - mTestHelper.signOutAndClose(aliceSession1) - mTestHelper.signOutAndClose(aliceSession2) + commonTestHelper.signOutAndClose(aliceSession1) + commonTestHelper.signOutAndClose(aliceSession2) } @Test fun test_ImproperKeyShareBug() { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - mTestHelper.doSync<Unit> { + commonTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService() .initializeCrossSigning( object : UserInteractiveAuthInterceptor { @@ -331,7 +327,7 @@ class KeyShareTests : InstrumentedTest { } // Create an encrypted room and send a couple of messages - val roomId = mTestHelper.runBlockingTest { + val roomId = commonTestHelper.runBlockingTest { aliceSession.createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE @@ -343,12 +339,12 @@ class KeyShareTests : InstrumentedTest { assertNotNull(roomAlicePov) Thread.sleep(1_000) assertTrue(roomAlicePov?.isEncrypted() == true) - val secondEventId = mTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId + val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId // Create bob session - val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true)) - mTestHelper.doSync<Unit> { + val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true)) + commonTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService() .initializeCrossSigning( object : UserInteractiveAuthInterceptor { @@ -365,11 +361,11 @@ class KeyShareTests : InstrumentedTest { } // Let alice invite bob - mTestHelper.runBlockingTest { + commonTestHelper.runBlockingTest { roomAlicePov.invite(bobSession.myUserId, null) } - mTestHelper.runBlockingTest { + commonTestHelper.runBlockingTest { bobSession.joinRoom(roomAlicePov.roomId, null, emptyList()) } @@ -377,7 +373,7 @@ class KeyShareTests : InstrumentedTest { aliceSession.cryptoService().discardOutboundSession(roomAlicePov.roomId) // and now resend a new message to reset index to 0 - mTestHelper.sendTextMessage(roomAlicePov, "After", 1) + commonTestHelper.sendTextMessage(roomAlicePov, "After", 1) val roomRoomBobPov = aliceSession.getRoom(roomId) val beforeJoin = roomRoomBobPov!!.getTimeLineEvent(secondEventId) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index c939952d..c835c2d4 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -41,8 +41,8 @@ import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode @FixMethodOrder(MethodSorters.JVM) class WithHeldTests : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) @Test fun test_WithHeldUnverifiedReason() { @@ -50,19 +50,19 @@ class WithHeldTests : InstrumentedTest { // ARRANGE // ============================= - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true)) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val bobSession = testHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true)) // Initialize cross signing on both - mCryptoTestHelper.initializeCrossSigning(aliceSession) - mCryptoTestHelper.initializeCrossSigning(bobSession) + cryptoTestHelper.initializeCrossSigning(aliceSession) + cryptoTestHelper.initializeCrossSigning(bobSession) - val roomId = mCryptoTestHelper.createDM(aliceSession, bobSession) - mCryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, roomId) + val roomId = cryptoTestHelper.createDM(aliceSession, bobSession) + cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, roomId) val roomAlicePOV = aliceSession.getRoom(roomId)!! - val bobUnverifiedSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) + val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) // ============================= // ACT @@ -71,11 +71,11 @@ class WithHeldTests : InstrumentedTest { // Alice decide to not send to unverified sessions aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true) - val timelineEvent = mTestHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first() + val timelineEvent = testHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first() // await for bob unverified session to get the message - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId) != null } } @@ -101,10 +101,10 @@ class WithHeldTests : InstrumentedTest { // enable back sending to unverified aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false) - val secondEvent = mTestHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first() + val secondEvent = testHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first() - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { val ev = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(secondEvent.eventId) // wait until it's decrypted ev?.root?.getClearType() == EventType.MESSAGE @@ -123,17 +123,17 @@ class WithHeldTests : InstrumentedTest { Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage) } - mTestHelper.signOutAndClose(aliceSession) - mTestHelper.signOutAndClose(bobSession) - mTestHelper.signOutAndClose(bobUnverifiedSession) + testHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(bobSession) + testHelper.signOutAndClose(bobUnverifiedSession) } @Test fun test_WithHeldNoOlm() { - val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession val bobSession = testData.secondSession!! - val aliceInterceptor = mTestHelper.getTestInterceptor(aliceSession) + val aliceInterceptor = testHelper.getTestInterceptor(aliceSession) // Simulate no OTK aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule( @@ -147,11 +147,11 @@ class WithHeldTests : InstrumentedTest { val roomAlicePov = aliceSession.getRoom(testData.roomId)!! - val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId + val eventId = testHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId // await for bob session to get the message - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId) != null } } @@ -177,14 +177,14 @@ class WithHeldTests : InstrumentedTest { // Add a new device for bob aliceInterceptor.clearRules() - val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(withInitialSync = true)) + val bobSecondSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(withInitialSync = true)) // send a second message - val secondMessageId = mTestHelper.sendTextMessage(roomAlicePov, "second message", 1).first().eventId + val secondMessageId = testHelper.sendTextMessage(roomAlicePov, "second message", 1).first().eventId // Check that the // await for bob SecondSession session to get the message - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(secondMessageId) != null } } @@ -194,27 +194,27 @@ class WithHeldTests : InstrumentedTest { Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2) aliceInterceptor.clearRules() - testData.cleanUp(mTestHelper) - mTestHelper.signOutAndClose(bobSecondSession) + testData.cleanUp(testHelper) + testHelper.signOutAndClose(bobSecondSession) } @Test fun test_WithHeldKeyRequest() { - val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession val bobSession = testData.secondSession!! val roomAlicePov = aliceSession.getRoom(testData.roomId)!! - val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId + val eventId = testHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId - mTestHelper.signOutAndClose(bobSession) + testHelper.signOutAndClose(bobSession) // Create a new session for bob - val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) + val bobSecondSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) // initialize to force request keys if missing - mCryptoTestHelper.initializeCrossSigning(bobSecondSession) + cryptoTestHelper.initializeCrossSigning(bobSecondSession) // Trust bob second device from Alice POV aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback()) @@ -223,8 +223,8 @@ class WithHeldTests : InstrumentedTest { var sessionId: String? = null // Check that the // await for bob SecondSession session to get the message - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also { // try to decrypt and force key request tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") } @@ -235,8 +235,8 @@ class WithHeldTests : InstrumentedTest { } // Check that bob second session requested the key - mTestHelper.waitWithLatch { latch -> - mTestHelper.retryPeriodicallyWithLatch(latch) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!) wc?.code == WithHeldCode.UNAUTHORISED } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index 0785dba8..2a07b741 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -22,7 +22,6 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue -import org.junit.Assert.fail import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -43,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreat import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import java.util.ArrayList import java.util.Collections import java.util.concurrent.CountDownLatch @@ -51,9 +49,9 @@ import java.util.concurrent.CountDownLatch @FixMethodOrder(MethodSorters.JVM) class KeysBackupTest : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) - private val mKeysBackupTestHelper = KeysBackupTestHelper(mTestHelper, mCryptoTestHelper) + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) + private val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) /** * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys @@ -62,7 +60,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun roomKeysTest_testBackupStore_ok() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store @@ -92,7 +90,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } /** @@ -100,7 +98,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun prepareKeysBackupVersionTest() { - val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) + val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) assertNotNull(bobSession.cryptoService().keysBackupService()) @@ -110,7 +108,7 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) - val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { + val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> { keysBackup.prepareKeysBackupVersion(null, null, it) } @@ -120,7 +118,7 @@ class KeysBackupTest : InstrumentedTest { assertNotNull(megolmBackupCreationInfo.recoveryKey) stateObserver.stopAndCheckStates(null) - mTestHelper.signOutAndClose(bobSession) + testHelper.signOutAndClose(bobSession) } /** @@ -128,7 +126,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun createKeysBackupVersionTest() { - val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) + val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) val keysBackup = bobSession.cryptoService().keysBackupService() @@ -136,14 +134,14 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) - val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { + val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> { keysBackup.prepareKeysBackupVersion(null, null, it) } assertFalse(keysBackup.isEnabled) // Create the version - mTestHelper.doSync<KeysVersion> { + testHelper.doSync<KeysVersion> { keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) } @@ -151,7 +149,7 @@ class KeysBackupTest : InstrumentedTest { assertTrue(keysBackup.isEnabled) stateObserver.stopAndCheckStates(null) - mTestHelper.signOutAndClose(bobSession) + testHelper.signOutAndClose(bobSession) } /** @@ -160,8 +158,9 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun backupAfterCreateKeysBackupVersionTest() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + keysBackupTestHelper.waitForKeybackUpBatching() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val latch = CountDownLatch(1) @@ -171,9 +170,9 @@ class KeysBackupTest : InstrumentedTest { val stateObserver = StateObserver(keysBackup, latch, 5) - mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) + keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) - mTestHelper.await(latch) + testHelper.await(latch) val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false) val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) @@ -191,7 +190,7 @@ class KeysBackupTest : InstrumentedTest { KeysBackupState.ReadyToBackUp ) ) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } /** @@ -199,13 +198,13 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun backupAllGroupSessionsTest() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val stateObserver = StateObserver(keysBackup) - mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) + keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) // Check that backupAllGroupSessions returns valid data val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false) @@ -214,7 +213,7 @@ class KeysBackupTest : InstrumentedTest { var lastBackedUpKeysProgress = 0 - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { keysBackup.backupAllGroupSessions(object : ProgressListener { override fun onProgress(progress: Int, total: Int) { assertEquals(nbOfKeys, total) @@ -230,7 +229,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } /** @@ -243,7 +242,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testEncryptAndDecryptKeysBackupData() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService @@ -252,7 +251,7 @@ class KeysBackupTest : InstrumentedTest { // - Pick a megolm key val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0] - val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo + val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo // - Check encryptGroupSession() returns stg val keyBackupData = keysBackup.encryptGroupSession(session) @@ -270,10 +269,10 @@ class KeysBackupTest : InstrumentedTest { decryption!!) assertNotNull(sessionData) // - Compare the decrypted megolm key with the original one - mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) + keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } /** @@ -284,10 +283,10 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun restoreKeysBackupTest() { - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) // - Restore the e2e backup from the homeserver - val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> { + val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> { testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null, @@ -297,9 +296,9 @@ class KeysBackupTest : InstrumentedTest { ) } - mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) + keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -369,7 +368,7 @@ class KeysBackupTest : InstrumentedTest { fun trustKeyBackupVersionTest() { // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) @@ -379,7 +378,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, true, @@ -388,21 +387,21 @@ class KeysBackupTest : InstrumentedTest { } // Wait for backup state to be ReadyToBackUp - mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) // - Backup must be enabled on the new device, on the same version assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { + val keysVersionResult = testHelper.doSync<KeysVersionResult?> { testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { + val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> { testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) } @@ -411,7 +410,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -428,7 +427,7 @@ class KeysBackupTest : InstrumentedTest { fun trustKeyBackupVersionWithRecoveryKeyTest() { // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) @@ -438,7 +437,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device with the recovery key - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, @@ -447,21 +446,21 @@ class KeysBackupTest : InstrumentedTest { } // Wait for backup state to be ReadyToBackUp - mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) // - Backup must be enabled on the new device, on the same version assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { + val keysVersionResult = testHelper.doSync<KeysVersionResult?> { testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { + val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> { testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) } @@ -470,7 +469,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -485,7 +484,7 @@ class KeysBackupTest : InstrumentedTest { fun trustKeyBackupVersionWithWrongRecoveryKeyTest() { // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) @@ -501,7 +500,7 @@ class KeysBackupTest : InstrumentedTest { "Bad recovery key", TestMatrixCallback(latch, false) ) - mTestHelper.await(latch) + testHelper.await(latch) // - The new device must still see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) @@ -509,7 +508,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) stateObserver.stopAndCheckStates(null) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -528,7 +527,7 @@ class KeysBackupTest : InstrumentedTest { // - Do an e2e backup to the homeserver with a password // - And log Alice on a new device - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) @@ -538,7 +537,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device with the password - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, password, @@ -547,21 +546,21 @@ class KeysBackupTest : InstrumentedTest { } // Wait for backup state to be ReadyToBackUp - mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) // - Backup must be enabled on the new device, on the same version assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { + val keysVersionResult = testHelper.doSync<KeysVersionResult?> { testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { + val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> { testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) } @@ -570,7 +569,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -588,7 +587,7 @@ class KeysBackupTest : InstrumentedTest { // - Do an e2e backup to the homeserver with a password // - And log Alice on a new device - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) @@ -604,7 +603,7 @@ class KeysBackupTest : InstrumentedTest { badPassword, TestMatrixCallback(latch, false) ) - mTestHelper.await(latch) + testHelper.await(latch) // - The new device must still see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) @@ -612,7 +611,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) stateObserver.stopAndCheckStates(null) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -623,7 +622,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun restoreKeysBackupWithAWrongRecoveryKeyTest() { - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) // - Try to restore the e2e backup with a wrong recovery key val latch2 = CountDownLatch(1) @@ -640,12 +639,12 @@ class KeysBackupTest : InstrumentedTest { } } ) - mTestHelper.await(latch2) + testHelper.await(latch2) // onSuccess may not have been called assertNull(importRoomKeysResult) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -658,12 +657,12 @@ class KeysBackupTest : InstrumentedTest { fun testBackupWithPassword() { val password = "password" - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) // - Restore the e2e backup with the password val steps = ArrayList<StepProgressListener.Step>() - val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> { + val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> { testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, password, null, @@ -698,9 +697,9 @@ class KeysBackupTest : InstrumentedTest { assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress) assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress) - mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) + keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -714,7 +713,7 @@ class KeysBackupTest : InstrumentedTest { val password = "password" val wrongPassword = "passw0rd" - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) // - Try to restore the e2e backup with a wrong password val latch2 = CountDownLatch(1) @@ -731,12 +730,12 @@ class KeysBackupTest : InstrumentedTest { } } ) - mTestHelper.await(latch2) + testHelper.await(latch2) // onSuccess may not have been called assertNull(importRoomKeysResult) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -749,10 +748,10 @@ class KeysBackupTest : InstrumentedTest { fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() { val password = "password" - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) // - Restore the e2e backup with the recovery key. - val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> { + val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> { testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null, @@ -762,9 +761,9 @@ class KeysBackupTest : InstrumentedTest { ) } - mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) + keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -775,7 +774,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() { - val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) // - Try to restore the e2e backup with a password val latch2 = CountDownLatch(1) @@ -792,12 +791,12 @@ class KeysBackupTest : InstrumentedTest { } } ) - mTestHelper.await(latch2) + testHelper.await(latch2) // onSuccess may not have been called assertNull(importRoomKeysResult) - testData.cleanUp(mTestHelper) + testData.cleanUp(testHelper) } /** @@ -807,22 +806,22 @@ class KeysBackupTest : InstrumentedTest { @Test fun testIsKeysBackupTrusted() { // - Create a backup version - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val stateObserver = StateObserver(keysBackup) // - Do an e2e backup to the homeserver - mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) + keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) // Get key backup version from the homeserver - val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { + val keysVersionResult = testHelper.doSync<KeysVersionResult?> { keysBackup.getCurrentVersion(it) } // - Check the returned KeyBackupVersion is trusted - val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { + val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> { keysBackup.getKeysBackupTrust(keysVersionResult!!, it) } @@ -837,7 +836,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } /** @@ -849,9 +848,8 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() { - fail("This test still fail. To investigate") // - Create a backup version - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() @@ -859,15 +857,15 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) - val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) + val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) assertTrue(keysBackup.isEnabled) // - Restart alice session // - Log Alice on a new device - val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) + val aliceSession2 = testHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) val keysBackup2 = aliceSession2.cryptoService().keysBackupService() @@ -891,13 +889,13 @@ class KeysBackupTest : InstrumentedTest { } } }) - mTestHelper.await(latch) + testHelper.await(latch) assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion) stateObserver.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null) - mTestHelper.signOutAndClose(aliceSession2) + testHelper.signOutAndClose(aliceSession2) } /** @@ -911,7 +909,7 @@ class KeysBackupTest : InstrumentedTest { @Test fun testBackupWhenAnotherBackupWasCreated() { // - Create a backup version - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() @@ -939,15 +937,15 @@ class KeysBackupTest : InstrumentedTest { }) // - Make alice back up her keys to her homeserver - mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) + keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) assertTrue(keysBackup.isEnabled) - mTestHelper.await(latch0) + testHelper.await(latch0) // - Create a new backup with fake data on the homeserver, directly using the rest client - val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo() - mTestHelper.doSync<KeysVersion> { + val megolmBackupCreationInfo = cryptoTestHelper.createFakeMegolmBackupCreationInfo() + testHelper.doSync<KeysVersion> { (keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it) } @@ -957,14 +955,14 @@ class KeysBackupTest : InstrumentedTest { // - Make alice back up all her keys again val latch2 = CountDownLatch(1) keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false)) - mTestHelper.await(latch2) + testHelper.await(latch2) // -> That must fail and her backup state must be WrongBackUpVersion assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state) assertFalse(keysBackup.isEnabled) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } /** @@ -982,17 +980,17 @@ class KeysBackupTest : InstrumentedTest { @Test fun testBackupAfterVerifyingADevice() { // - Create a backup version - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val stateObserver = StateObserver(keysBackup) // - Make alice back up her keys to her homeserver - mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) + keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) // Wait for keys backup to finish by asking again to backup keys. - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { keysBackup.backupAllGroupSessions(null, it) } @@ -1001,14 +999,14 @@ class KeysBackupTest : InstrumentedTest { val aliceUserId = cryptoTestData.firstSession.myUserId // - Log Alice on a new device - val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) + val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) // - Post a message to have a new megolm session aliceSession2.cryptoService().setWarnOnUnknownDevices(false) val room2 = aliceSession2.getRoom(cryptoTestData.roomId)!! - mTestHelper.sendTextMessage(room2, "New key", 1) + testHelper.sendTextMessage(room2, "New key", 1) // - Try to backup all in aliceSession2, it must fail val keysBackup2 = aliceSession2.cryptoService().keysBackupService() @@ -1025,7 +1023,7 @@ class KeysBackupTest : InstrumentedTest { super.onSuccess(data) } }) - mTestHelper.await(latch2) + testHelper.await(latch2) assertFalse(isSuccessful) @@ -1049,12 +1047,12 @@ class KeysBackupTest : InstrumentedTest { } } }) - mTestHelper.await(latch4) + testHelper.await(latch4) // -> It must use the same backup version assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion) - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it) } @@ -1063,8 +1061,8 @@ class KeysBackupTest : InstrumentedTest { stateObserver.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null) - mTestHelper.signOutAndClose(aliceSession2) - cryptoTestData.cleanUp(mTestHelper) + testHelper.signOutAndClose(aliceSession2) + cryptoTestData.cleanUp(testHelper) } /** @@ -1074,7 +1072,7 @@ class KeysBackupTest : InstrumentedTest { @Test fun deleteKeysBackupTest() { // - Create a backup version - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() @@ -1082,17 +1080,17 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) - val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) + val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) assertTrue(keysBackup.isEnabled) // Delete the backup - mTestHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) } + testHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) } // Backup is now disabled assertFalse(keysBackup.isEnabled) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt index a625ffc0..592b798b 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt @@ -32,8 +32,12 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion import java.util.concurrent.CountDownLatch class KeysBackupTestHelper( - private val mTestHelper: CommonTestHelper, - private val mCryptoTestHelper: CryptoTestHelper) { + private val testHelper: CommonTestHelper, + private val cryptoTestHelper: CryptoTestHelper) { + + fun waitForKeybackUpBatching() { + Thread.sleep(400) + } /** * Common initial condition @@ -43,7 +47,9 @@ class KeysBackupTestHelper( * @param password optional password */ fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + waitForKeybackUpBatching() val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() @@ -57,7 +63,7 @@ class KeysBackupTestHelper( var lastProgress = 0 var lastTotal = 0 - mTestHelper.doSync<Unit> { + testHelper.doSync<Unit> { keysBackup.backupAllGroupSessions(object : ProgressListener { override fun onProgress(progress: Int, total: Int) { lastProgress = progress @@ -72,7 +78,7 @@ class KeysBackupTestHelper( val aliceUserId = cryptoTestData.firstSession.myUserId // - Log Alice on a new device - val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) + val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) // Test check: aliceSession2 has no keys at login Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false)) @@ -92,7 +98,7 @@ class KeysBackupTestHelper( password: String? = null): PrepareKeysBackupDataResult { val stateObserver = StateObserver(keysBackup) - val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { + val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> { keysBackup.prepareKeysBackupVersion(password, null, it) } @@ -101,7 +107,7 @@ class KeysBackupTestHelper( Assert.assertFalse(keysBackup.isEnabled) // Create the version - val keysVersion = mTestHelper.doSync<KeysVersion> { + val keysVersion = testHelper.doSync<KeysVersion> { keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) } @@ -136,7 +142,7 @@ class KeysBackupTestHelper( } }) - mTestHelper.await(latch) + testHelper.await(latch) } fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt index b343d733..43f8dc07 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt @@ -18,10 +18,6 @@ package org.matrix.android.sdk.internal.crypto.ssss import androidx.lifecycle.Observer import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -36,6 +32,7 @@ import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent import org.matrix.android.sdk.api.session.securestorage.KeySigner import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.api.session.securestorage.SecretStorageKeyContent +import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageError import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo import org.matrix.android.sdk.api.util.Optional @@ -45,13 +42,12 @@ import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService -import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) class QuadSTests : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) + private val testHelper = CommonTestHelper(context()) private val emptyKeySigner = object : KeySigner { override fun sign(canonicalJson: String): Map<String, Map<String, String>>? { @@ -60,35 +56,29 @@ class QuadSTests : InstrumentedTest { } @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun test_Generate4SKey() { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val quadS = aliceSession.sharedSecretStorageService val TEST_KEY_ID = "my.test.Key" - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) } - // Assert Account data is updated - val accountDataLock = CountDownLatch(1) var accountData: UserAccountDataEvent? = null - - val liveAccountData = runBlocking(Dispatchers.Main) { - aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") - } - val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> - if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") { - accountData = t.getOrNull() - accountDataLock.countDown() + // Assert Account data is updated + testHelper.waitWithLatch { + val liveAccountData = aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") + val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> + if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") { + accountData = t.getOrNull() + } + it.countDown() } + liveAccountData.observeForever(accountDataObserver) } - GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) } - - mTestHelper.await(accountDataLock) - assertNotNull("Key should be stored in account data", accountData) val parsed = SecretStorageKeyContent.fromJson(accountData!!.content) assertNotNull("Key Content cannot be parsed", parsed) @@ -96,36 +86,29 @@ class QuadSTests : InstrumentedTest { assertEquals("Unexpected key name", "Test Key", parsed.name) assertNull("Key was not generated from passphrase", parsed.passphrase) + var defaultKeyAccountData: UserAccountDataEvent? = null // Set as default key - GlobalScope.launch { + testHelper.waitWithLatch { latch -> quadS.setDefaultKey(TEST_KEY_ID) - } - - var defaultKeyAccountData: UserAccountDataEvent? = null - val defaultDataLock = CountDownLatch(1) - - val liveDefAccountData = runBlocking(Dispatchers.Main) { - aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) - } - val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> - if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) { - defaultKeyAccountData = t.getOrNull()!! - defaultDataLock.countDown() + val liveDefAccountData = + aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) + val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> + if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) { + defaultKeyAccountData = t.getOrNull()!! + latch.countDown() + } } + liveDefAccountData.observeForever(accountDefDataObserver) } - GlobalScope.launch(Dispatchers.Main) { liveDefAccountData.observeForever(accountDefDataObserver) } - - mTestHelper.await(defaultDataLock) - assertNotNull(defaultKeyAccountData?.content) assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key")) - mTestHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(aliceSession) } @Test fun test_StoreSecret() { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId = "My.Key" val info = generatedSecret(aliceSession, keyId, true) @@ -133,7 +116,7 @@ class QuadSTests : InstrumentedTest { // Store a secret val clearSecret = "42".toByteArray().toBase64NoPadding() - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "secret.of.life", clearSecret, @@ -154,7 +137,7 @@ class QuadSTests : InstrumentedTest { // Try to decrypt?? - val decryptedSecret = mTestHelper.runBlockingTest { + val decryptedSecret = testHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret( "secret.of.life", null, // default key @@ -163,32 +146,32 @@ class QuadSTests : InstrumentedTest { } assertEquals("Secret mismatch", clearSecret, decryptedSecret) - mTestHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(aliceSession) } @Test fun test_SetDefaultLocalEcho() { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val quadS = aliceSession.sharedSecretStorageService val TEST_KEY_ID = "my.test.Key" - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) } // Test that we don't need to wait for an account data sync to access directly the keyid from DB - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { quadS.setDefaultKey(TEST_KEY_ID) } - mTestHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(aliceSession) } @Test fun test_StoreSecretWithMultipleKey() { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId1 = "Key.1" val key1Info = generatedSecret(aliceSession, keyId1, true) val keyId2 = "Key2" @@ -196,7 +179,7 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), @@ -216,33 +199,33 @@ class QuadSTests : InstrumentedTest { assertNotNull(encryptedContent?.get(keyId2)) // Assert that can decrypt with both keys - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!! ) } - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! ) } - mTestHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(aliceSession) } @Test fun test_GetSecretWithBadPassphrase() { - val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId1 = "Key.1" val passphrase = "The good pass phrase" val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true) val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), @@ -250,19 +233,23 @@ class QuadSTests : InstrumentedTest { ) } - mTestHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret("my.secret", - keyId1, - RawBytesKeySpec.fromPassphrase( - "A bad passphrase", - key1Info.content?.passphrase?.salt ?: "", - key1Info.content?.passphrase?.iterations ?: 0, - null) - ) + testHelper.runBlockingTest { + try { + aliceSession.sharedSecretStorageService.getSecret("my.secret", + keyId1, + RawBytesKeySpec.fromPassphrase( + "A bad passphrase", + key1Info.content?.passphrase?.salt ?: "", + key1Info.content?.passphrase?.iterations ?: 0, + null) + ) + } catch (throwable: Throwable) { + assert(throwable is SharedSecretStorageError.BadMac) + } } // Now try with correct key - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, RawBytesKeySpec.fromPassphrase( @@ -273,42 +260,36 @@ class QuadSTests : InstrumentedTest { ) } - mTestHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(aliceSession) } - @Suppress("EXPERIMENTAL_API_USAGE") private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { - val accountDataLock = CountDownLatch(1) var accountData: UserAccountDataEvent? = null - - val liveAccountData = runBlocking(Dispatchers.Main) { - session.accountDataService().getLiveUserAccountDataEvent(type) - } - val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> - if (t?.getOrNull()?.type == type) { - accountData = t.getOrNull() - accountDataLock.countDown() + testHelper.waitWithLatch { + val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type) + val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> + if (t?.getOrNull()?.type == type) { + accountData = t.getOrNull() + it.countDown() + } } + liveAccountData.observeForever(accountDataObserver) } - GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) } - mTestHelper.await(accountDataLock) - assertNotNull("Account Data type:$type should be found", accountData) - return accountData!! } private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService - val creationInfo = mTestHelper.runBlockingTest { + val creationInfo = testHelper.runBlockingTest { quadS.generateKey(keyId, null, keyId, emptyKeySigner) } assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { quadS.setDefaultKey(keyId) } assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) @@ -320,7 +301,7 @@ class QuadSTests : InstrumentedTest { private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService - val creationInfo = mTestHelper.runBlockingTest { + val creationInfo = testHelper.runBlockingTest { quadS.generateKeyWithPassphrase( keyId, keyId, @@ -331,7 +312,7 @@ class QuadSTests : InstrumentedTest { assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { - mTestHelper.runBlockingTest { + testHelper.runBlockingTest { quadS.setDefaultKey(keyId) } assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index e0d49b3f..c914da6f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -53,12 +53,12 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class SASTest : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) @Test fun test_aliceStartThenAliceCancel() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -83,7 +83,7 @@ class SASTest : InstrumentedTest { val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!) assertNotNull("Alice should have a started transaction", aliceKeyTx) - mTestHelper.await(bobTxCreatedLatch) + testHelper.await(bobTxCreatedLatch) bobVerificationService.removeListener(bobListener) val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID) @@ -116,7 +116,7 @@ class SASTest : InstrumentedTest { bobVerificationService.addListener(bobListener2) aliceSasTx.cancel(CancelCode.User) - mTestHelper.await(cancelLatch) + testHelper.await(cancelLatch) assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled) assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled) @@ -133,13 +133,13 @@ class SASTest : InstrumentedTest { assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)) assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID)) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } @Test fun test_key_agreement_protocols_must_include_curve25519() { fail("Not passing for the moment") - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val bobSession = cryptoTestData.secondSession!! @@ -186,17 +186,17 @@ class SASTest : InstrumentedTest { fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols) - mTestHelper.await(cancelLatch) + testHelper.await(cancelLatch) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } @Test fun test_key_agreement_macs_Must_include_hmac_sha256() { fail("Not passing for the moment") - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val bobSession = cryptoTestData.secondSession!! @@ -223,18 +223,18 @@ class SASTest : InstrumentedTest { fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac) - mTestHelper.await(cancelLatch) + testHelper.await(cancelLatch) val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } @Test fun test_key_agreement_short_code_include_decimal() { fail("Not passing for the moment") - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val bobSession = cryptoTestData.secondSession!! @@ -261,12 +261,12 @@ class SASTest : InstrumentedTest { fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes) - mTestHelper.await(cancelLatch) + testHelper.await(cancelLatch) val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } private fun fakeBobStart(bobSession: Session, @@ -303,7 +303,7 @@ class SASTest : InstrumentedTest { // If a device has two verifications in progress with the same device, then it should cancel both verifications. @Test fun test_aliceStartTwoRequests() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -332,10 +332,10 @@ class SASTest : InstrumentedTest { aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) - mTestHelper.await(aliceCreatedLatch) - mTestHelper.await(aliceCancelledLatch) + testHelper.await(aliceCreatedLatch) + testHelper.await(aliceCancelledLatch) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } /** @@ -343,7 +343,7 @@ class SASTest : InstrumentedTest { */ @Test fun test_aliceAndBobAgreement() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -383,7 +383,7 @@ class SASTest : InstrumentedTest { val bobUserId = bobSession.myUserId val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) - mTestHelper.await(aliceAcceptedLatch) + testHelper.await(aliceAcceptedLatch) assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false) @@ -397,12 +397,12 @@ class SASTest : InstrumentedTest { assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it)) } - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } @Test fun test_aliceAndBobSASCode() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -444,8 +444,8 @@ class SASTest : InstrumentedTest { val bobUserId = bobSession.myUserId val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) - mTestHelper.await(aliceSASLatch) - mTestHelper.await(bobSASLatch) + testHelper.await(aliceSASLatch) + testHelper.await(bobSASLatch) val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction @@ -453,12 +453,12 @@ class SASTest : InstrumentedTest { assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), bobTx.getShortCodeRepresentation(SasMode.DECIMAL)) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } @Test fun test_happyPath() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -520,8 +520,8 @@ class SASTest : InstrumentedTest { val bobUserId = bobSession.myUserId val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) - mTestHelper.await(aliceSASLatch) - mTestHelper.await(bobSASLatch) + testHelper.await(aliceSASLatch) + testHelper.await(bobSASLatch) // Assert that devices are verified val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId) @@ -532,12 +532,12 @@ class SASTest : InstrumentedTest { assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } @Test fun test_ConcurrentStart() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession @@ -553,8 +553,8 @@ class SASTest : InstrumentedTest { var requestID: String? = null - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull() requestID = prAlicePOV?.transactionId Log.v("TEST", "== alicePOV is $prAlicePOV") @@ -564,8 +564,8 @@ class SASTest : InstrumentedTest { Log.v("TEST", "== requestID is $requestID") - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull() Log.v("TEST", "== prBobPOV is $prBobPOV") prBobPOV?.transactionId == requestID @@ -579,8 +579,8 @@ class SASTest : InstrumentedTest { ) // wait for alice to get the ready - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull() Log.v("TEST", "== prAlicePOV is $prAlicePOV") prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null @@ -606,22 +606,22 @@ class SASTest : InstrumentedTest { var alicePovTx: SasVerificationTransaction? var bobPovTx: SasVerificationTransaction? - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction Log.v("TEST", "== alicePovTx is $alicePovTx") alicePovTx?.state == VerificationTxState.ShortCodeReady } } // wait for alice to get the ready - mTestHelper.waitWithLatch { - mTestHelper.retryPeriodicallyWithLatch(it) { + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction Log.v("TEST", "== bobPovTx is $bobPovTx") bobPovTx?.state == VerificationTxState.ShortCodeReady } } - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt index 397f7f94..36306aa3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt @@ -40,8 +40,8 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) class VerificationTest : InstrumentedTest { - private val mTestHelper = CommonTestHelper(context()) - private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) data class ExpectedResult( val sasIsSupported: Boolean = false, @@ -155,12 +155,12 @@ class VerificationTest : InstrumentedTest { bobSupportedMethods: List<VerificationMethod>, expectedResultForAlice: ExpectedResult, expectedResultForBob: ExpectedResult) { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession!! - mTestHelper.doSync<Unit> { callback -> + testHelper.doSync<Unit> { callback -> aliceSession.cryptoService().crossSigningService() .initializeCrossSigning( object : UserInteractiveAuthInterceptor { @@ -176,7 +176,7 @@ class VerificationTest : InstrumentedTest { }, callback) } - mTestHelper.doSync<Unit> { callback -> + testHelper.doSync<Unit> { callback -> bobSession.cryptoService().crossSigningService() .initializeCrossSigning( object : UserInteractiveAuthInterceptor { @@ -234,7 +234,7 @@ class VerificationTest : InstrumentedTest { val bobUserId = bobSession.myUserId // Step 1: Alice starts a verification request aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId) - mTestHelper.await(latch) + testHelper.await(latch) aliceReadyPendingVerificationRequest!!.let { pr -> pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported @@ -248,6 +248,6 @@ class VerificationTest : InstrumentedTest { pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode } - cryptoTestData.cleanUp(mTestHelper) + cryptoTestData.cleanUp(testHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt index 1adf31be..8a4429db 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt @@ -45,7 +45,7 @@ object RoomDataHelper { content: Content? = null, prevContent: Content? = null, sender: String = FAKE_TEST_SENDER, - stateKey: String = FAKE_TEST_SENDER + stateKey: String? = null ): Event { return Event( type = type, @@ -64,6 +64,6 @@ object RoomDataHelper { private fun createFakeRoomMemberEvent(): Event { val roomMember = RoomMemberContent(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent() - return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember) + return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember, stateKey = FAKE_TEST_SENDER) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt index dfa6ec10..bc9722c9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt @@ -65,14 +65,8 @@ class TimelineForwardPaginationTest : InstrumentedTest { message, numberOfMessagesToSend) - // Alice clear the cache - commonTestHelper.runBlockingTest { - aliceSession.clearCache() - } - - // And restarts the sync - aliceSession.startSync(true) - + // Alice clear the cache and restart the sync + commonTestHelper.clearCacheAndSync(aliceSession) val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30)) aliceTimeline.start() 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 1baf490d..45e4b53c 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 @@ -24,14 +24,10 @@ import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.message.MessageContent -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestData import org.matrix.android.sdk.common.CryptoTestHelper -import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @@ -84,24 +80,12 @@ class SearchMessagesTest : InstrumentedTest { val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! - val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10)) - aliceTimeline.start() - - val lock = CountDownLatch(1) - - val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> - snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2 - } - - aliceTimeline.addListener(eventListener) commonTestHelper.sendTextMessage( roomFromAlicePOV, MESSAGE, 2) - commonTestHelper.await(lock) - val data = commonTestHelper.runBlockingTest { block.invoke(cryptoTestData) } @@ -114,7 +98,6 @@ class SearchMessagesTest : InstrumentedTest { }.orFalse() ) - aliceTimeline.removeAllListeners() cryptoTestData.cleanUp(commonTestHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index 5911414c..d7be1929 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -16,9 +16,7 @@ package org.matrix.android.sdk.session.space -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -50,18 +48,15 @@ class SpaceCreationTest : InstrumentedTest { private val commonTestHelper = CommonTestHelper(context()) @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun createSimplePublicSpace() { val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true)) val roomName = "My Space" val topic = "A public space for test" var spaceId: String = "" commonTestHelper.waitWithLatch { - GlobalScope.launch { - spaceId = session.spaceService().createSpace(roomName, topic, null, true) - // wait a bit to let the summary update it self :/ - it.countDown() - } + spaceId = session.spaceService().createSpace(roomName, topic, null, true) + // wait a bit to let the summary update it self :/ + it.countDown() } val syncedSpace = session.spaceService().getSpace(spaceId) @@ -134,7 +129,6 @@ class SpaceCreationTest : InstrumentedTest { } @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun testSimplePublicSpaceWithChildren() { val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true)) @@ -148,50 +142,40 @@ class SpaceCreationTest : InstrumentedTest { // create a room var firstChild: String? = null commonTestHelper.waitWithLatch { - GlobalScope.launch { - firstChild = aliceSession.createRoom(CreateRoomParams().apply { - this.name = "FirstRoom" - this.topic = "Description of first room" - this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT - }) - it.countDown() - } + firstChild = aliceSession.createRoom(CreateRoomParams().apply { + this.name = "FirstRoom" + this.topic = "Description of first room" + this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT + }) + it.countDown() } commonTestHelper.waitWithLatch { - GlobalScope.launch { - syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true) - it.countDown() - } + syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true) + it.countDown() } var secondChild: String? = null commonTestHelper.waitWithLatch { - GlobalScope.launch { - secondChild = aliceSession.createRoom(CreateRoomParams().apply { - this.name = "SecondRoom" - this.topic = "Description of second room" - this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT - }) - it.countDown() - } + secondChild = aliceSession.createRoom(CreateRoomParams().apply { + this.name = "SecondRoom" + this.topic = "Description of second room" + this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT + }) + it.countDown() } commonTestHelper.waitWithLatch { - GlobalScope.launch { - syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true) - it.countDown() - } + syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true) + it.countDown() } // Try to join from bob, it's a public space no need to invite var joinResult: JoinSpaceResult? = null commonTestHelper.waitWithLatch { - GlobalScope.launch { - joinResult = bobSession.spaceService().joinSpace(spaceId) - // wait a bit to let the summary update it self :/ - it.countDown() - } + joinResult = bobSession.spaceService().joinSpace(spaceId) + // wait a bit to let the summary update it self :/ + it.countDown() } assertEquals(JoinSpaceResult.Success, joinResult) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 436daf00..1c38edbb 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -18,9 +18,6 @@ package org.matrix.android.sdk.session.space import android.util.Log import androidx.lifecycle.Observer -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -56,43 +53,34 @@ class SpaceHierarchyTest : InstrumentedTest { private val commonTestHelper = CommonTestHelper(context()) @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun createCanonicalChildRelation() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceName = "My Space" val topic = "A public space for test" - var spaceId: String = "" + var spaceId = "" commonTestHelper.waitWithLatch { - GlobalScope.launch { - spaceId = session.spaceService().createSpace(spaceName, topic, null, true) - it.countDown() - } + spaceId = session.spaceService().createSpace(spaceName, topic, null, true) + it.countDown() } val syncedSpace = session.spaceService().getSpace(spaceId) - var roomId: String = "" + var roomId = "" commonTestHelper.waitWithLatch { - GlobalScope.launch { - roomId = session.createRoom(CreateRoomParams().apply { name = "General" }) - it.countDown() - } + roomId = session.createRoom(CreateRoomParams().apply { name = "General" }) + it.countDown() } val viaServers = listOf(session.sessionParams.homeServerHost ?: "") commonTestHelper.waitWithLatch { - GlobalScope.launch { - syncedSpace!!.addChildren(roomId, viaServers, null, true) - it.countDown() - } + syncedSpace!!.addChildren(roomId, viaServers, null, true) + it.countDown() } commonTestHelper.waitWithLatch { - GlobalScope.launch { - session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers) - it.countDown() - } + session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers) + it.countDown() } Thread.sleep(9000) @@ -181,7 +169,6 @@ class SpaceHierarchyTest : InstrumentedTest { // } @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun testFilteringBySpace() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) @@ -205,29 +192,23 @@ class SpaceHierarchyTest : InstrumentedTest { val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") commonTestHelper.waitWithLatch { - GlobalScope.launch { - spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) - session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) - it.countDown() - } + spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) + session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) + it.countDown() } // Create orphan rooms var orphan1 = "" commonTestHelper.waitWithLatch { - GlobalScope.launch { - orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" }) - it.countDown() - } + orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" }) + it.countDown() } var orphan2 = "" commonTestHelper.waitWithLatch { - GlobalScope.launch { - orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" }) - it.countDown() - } + orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" }) + it.countDown() } val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) @@ -250,11 +231,9 @@ class SpaceHierarchyTest : InstrumentedTest { // Add a non canonical child and check that it does not appear as orphan commonTestHelper.waitWithLatch { - GlobalScope.launch { - val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" }) - spaceA!!.addChildren(a3, viaServers, null, false) - it.countDown() - } + val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" }) + spaceA!!.addChildren(a3, viaServers, null, false) + it.countDown() } Thread.sleep(2_000) @@ -265,7 +244,6 @@ class SpaceHierarchyTest : InstrumentedTest { } @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun testBreakCycle() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) @@ -283,20 +261,16 @@ class SpaceHierarchyTest : InstrumentedTest { val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") commonTestHelper.waitWithLatch { - GlobalScope.launch { - spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) - session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) - it.countDown() - } + spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) + session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) + it.countDown() } // add back A as subspace of C commonTestHelper.waitWithLatch { - GlobalScope.launch { - val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId) - spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true) - it.countDown() - } + val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId) + spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true) + it.countDown() } Thread.sleep(1000) @@ -313,7 +287,6 @@ class SpaceHierarchyTest : InstrumentedTest { } @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun testLiveFlatChildren() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) @@ -336,12 +309,14 @@ class SpaceHierarchyTest : InstrumentedTest { session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers) } - val flatAChildren = runBlocking(Dispatchers.Main) { - session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) - } + val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + )) commonTestHelper.waitWithLatch { latch -> + val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) val childObserver = object : Observer<List<RoomSummary>> { override fun onChanged(children: List<RoomSummary>?) { // Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}") @@ -354,20 +329,13 @@ class SpaceHierarchyTest : InstrumentedTest { } } - val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - )) - // add C as subspace of B - runBlocking { - val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId) - spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) - } + val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId) + spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) // C1 and C2 should be in flatten child of A now - GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) } + flatAChildren.observeForever(childObserver) } // Test part one of the rooms @@ -376,7 +344,7 @@ class SpaceHierarchyTest : InstrumentedTest { val bRoom = session.getRoom(bRoomId) commonTestHelper.waitWithLatch { latch -> - + val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) val childObserver = object : Observer<List<RoomSummary>> { override fun onChanged(children: List<RoomSummary>?) { System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}") @@ -389,13 +357,10 @@ class SpaceHierarchyTest : InstrumentedTest { } // part from b room - runBlocking { - bRoom!!.leave(null) - } + bRoom!!.leave(null) // The room should have disapear from flat children - GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) } + flatAChildren.observeForever(childObserver) } - commonTestHelper.signOutAndClose(session) } @@ -404,94 +369,66 @@ class SpaceHierarchyTest : InstrumentedTest { val roomIds: List<String> ) - @Suppress("EXPERIMENTAL_API_USAGE") private fun createPublicSpace(session: Session, spaceName: String, childInfo: List<Triple<String, Boolean, Boolean?>> /** Name, auto-join, canonical*/ ): TestSpaceCreationResult { var spaceId = "" - commonTestHelper.waitWithLatch { - GlobalScope.launch { - spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true) - it.countDown() - } - } - - val syncedSpace = session.spaceService().getSpace(spaceId) - val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - - val roomIds = - childInfo.map { entry -> - var roomId = "" - commonTestHelper.waitWithLatch { - GlobalScope.launch { - roomId = session.createRoom(CreateRoomParams().apply { name = entry.first }) - it.countDown() - } - } - roomId - } + var roomIds: List<String> = emptyList() + commonTestHelper.waitWithLatch { latch -> + spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true) + val syncedSpace = session.spaceService().getSpace(spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - roomIds.forEachIndexed { index, roomId -> - runBlocking { + roomIds = childInfo.map { entry -> + session.createRoom(CreateRoomParams().apply { name = entry.first }) + } + roomIds.forEachIndexed { index, roomId -> syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) val canonical = childInfo[index].third if (canonical != null) { session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) } } + latch.countDown() } return TestSpaceCreationResult(spaceId, roomIds) } - @Suppress("EXPERIMENTAL_API_USAGE") private fun createPrivateSpace(session: Session, spaceName: String, childInfo: List<Triple<String, Boolean, Boolean?>> /** Name, auto-join, canonical*/ ): TestSpaceCreationResult { var spaceId = "" - commonTestHelper.waitWithLatch { - GlobalScope.launch { - spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false) - it.countDown() - } - } - - val syncedSpace = session.spaceService().getSpace(spaceId) - val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - - val roomIds = - childInfo.map { entry -> - var roomId = "" - commonTestHelper.waitWithLatch { - GlobalScope.launch { - val homeServerCapabilities = session - .getHomeServerCapabilities() - roomId = session.createRoom(CreateRoomParams().apply { - name = entry.first - this.featurePreset = RestrictedRoomPreset( - homeServerCapabilities, - listOf( - RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) - ) - ) - }) - it.countDown() - } + var roomIds: List<String> = emptyList() + commonTestHelper.waitWithLatch { latch -> + spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false) + val syncedSpace = session.spaceService().getSpace(spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + roomIds = + childInfo.map { entry -> + val homeServerCapabilities = session + .getHomeServerCapabilities() + session.createRoom(CreateRoomParams().apply { + name = entry.first + this.featurePreset = RestrictedRoomPreset( + homeServerCapabilities, + listOf( + RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) + ) + ) + }) } - roomId - } - - roomIds.forEachIndexed { index, roomId -> - runBlocking { + roomIds.forEachIndexed { index, roomId -> syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) val canonical = childInfo[index].third if (canonical != null) { session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) } } + latch.countDown() } return TestSpaceCreationResult(spaceId, roomIds) } @@ -559,11 +496,9 @@ class SpaceHierarchyTest : InstrumentedTest { var bobRoomId = "" commonTestHelper.waitWithLatch { - GlobalScope.launch { - bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" }) - bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId) - it.countDown() - } + bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" }) + bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId) + it.countDown() } commonTestHelper.runBlockingTest { @@ -577,10 +512,8 @@ class SpaceHierarchyTest : InstrumentedTest { } commonTestHelper.waitWithLatch { - GlobalScope.launch { - bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: "")) - it.countDown() - } + bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: "")) + it.countDown() } commonTestHelper.waitWithLatch { latch -> @@ -600,19 +533,17 @@ class SpaceHierarchyTest : InstrumentedTest { // Let's now try to make alice admin of the room commonTestHelper.waitWithLatch { - GlobalScope.launch { - val room = bobSession.getRoom(bobRoomId)!! - val currentPLContent = room - .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) - ?.let { it.content.toModel<PowerLevelsContent>() } + val room = bobSession.getRoom(bobRoomId)!! + val currentPLContent = room + .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) + ?.let { it.content.toModel<PowerLevelsContent>() } - val newPowerLevelsContent = currentPLContent - ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value) - ?.toContent() + val newPowerLevelsContent = currentPLContent + ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value) + ?.toContent() - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!) - it.countDown() - } + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!) + it.countDown() } commonTestHelper.waitWithLatch { latch -> @@ -627,10 +558,8 @@ class SpaceHierarchyTest : InstrumentedTest { } commonTestHelper.waitWithLatch { - GlobalScope.launch { - aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: "")) - it.countDown() - } + aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: "")) + it.countDown() } commonTestHelper.waitWithLatch { latch -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt index 8a4526a5..901ba75d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt @@ -20,6 +20,7 @@ import android.content.Context import androidx.lifecycle.ProcessLifecycleOwner import androidx.work.Configuration import androidx.work.WorkManager +import androidx.work.WorkerFactory import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.auth.AuthenticationService @@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.di.DaggerMatrixComponent import org.matrix.android.sdk.internal.network.ApiInterceptor import org.matrix.android.sdk.internal.network.UserAgentHolder import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver +import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import org.matrix.olm.OlmManager import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean @@ -53,12 +55,17 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService @Inject internal lateinit var apiInterceptor: ApiInterceptor + @Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory init { Monarchy.init(context) DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this) if (context.applicationContext !is Configuration.Provider) { - WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build()) + val configuration = Configuration.Builder() + .setExecutor(Executors.newCachedThreadPool()) + .setWorkerFactory(matrixWorkerFactory) + .build() + WorkManager.initialize(context, configuration) } ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) } @@ -77,6 +84,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return legacySessionImporter } + fun workerFactory(): WorkerFactory = matrixWorkerFactory + fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { apiInterceptor.addListener(path, listener) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index a39ca5b4..0c77b574 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -104,6 +104,8 @@ object EventType { // Poll const val POLL_START = "org.matrix.msc3381.poll.start" + const val POLL_RESPONSE = "org.matrix.msc3381.poll.response" + const val POLL_END = "org.matrix.msc3381.poll.end" // Unwedging internal const val DUMMY = "m.dummy" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt index 844ef6c1..f1e43543 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt @@ -24,25 +24,24 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class PollSummaryContent( - // Index of my vote - var myVote: Int? = null, + var myVote: String? = null, // Array of VoteInfo, list is constructed so that there is only one vote by user // And that optionIndex is valid - var votes: List<VoteInfo>? = null -) { - - fun voteCount(): Int { - return votes?.size ?: 0 - } + var votes: List<VoteInfo>? = null, + var votesSummary: Map<String, VoteSummary>? = null, + var totalVotes: Int = 0, + var winnerVoteCount: Int = 0 +) - fun voteCountForOption(optionIndex: Int): Int { - return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0 - } -} +@JsonClass(generateAdapter = true) +data class VoteSummary( + val total: Int = 0, + val percentage: Double = 0.0 +) @JsonClass(generateAdapter = true) data class VoteInfo( val userId: String, - val optionIndex: Int, + val option: String, val voteTimestamp: Long ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt new file mode 100644 index 00000000..491b7147 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt @@ -0,0 +1,29 @@ +/* + * Copyright 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.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +/** + * Class representing the org.matrix.msc3381.poll.end event content + */ +@JsonClass(generateAdapter = true) +data class MessageEndPollContent( + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt deleted file mode 100644 index 7a1a99bd..00000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt +++ /dev/null @@ -1,40 +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.api.session.room.model.message - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent - -// Possible values for optionType -const val OPTION_TYPE_POLL = "org.matrix.poll" -const val OPTION_TYPE_BUTTONS = "org.matrix.buttons" - -/** - * Polls and bot buttons are m.room.message events with a msgtype of m.options, - * Ref: https://github.com/matrix-org/matrix-doc/pull/2192 - */ -@JsonClass(generateAdapter = true) -data class MessageOptionsContent( - @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_OPTIONS, - @Json(name = "type") val optionType: String? = null, - @Json(name = "body") override val body: String, - @Json(name = "label") val label: String?, - @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, - @Json(name = "options") val options: List<OptionItem>? = null, - @Json(name = "m.new_content") override val newContent: Content? = null -) : MessageContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt index ef2fd186..a4e13172 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt @@ -18,8 +18,18 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent @JsonClass(generateAdapter = true) data class MessagePollContent( - @Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null -) + /** + * Local message type, not from server + */ + @Transient + override val msgType: String = MessageType.MSGTYPE_POLL_START, + @Json(name = "body") override val body: String = "", + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, + @Json(name = "m.new_content") override val newContent: Content? = null, + @Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null +) : MessageContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt index 9edfe118..f3b4e3dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt @@ -21,13 +21,15 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -/** - * Ref: https://github.com/matrix-org/matrix-doc/pull/2192 - */ @JsonClass(generateAdapter = true) data class MessagePollResponseContent( - @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_RESPONSE, - @Json(name = "body") override val body: String, + /** + * Local message type, not from server + */ + @Transient + override val msgType: String = MessageType.MSGTYPE_POLL_RESPONSE, + @Json(name = "body") override val body: String = "", @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, - @Json(name = "m.new_content") override val newContent: Content? = null + @Json(name = "m.new_content") override val newContent: Content? = null, + @Json(name = "org.matrix.msc3381.poll.response") val response: PollResponse? = null ) : MessageContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index 1e8959af..2a6138ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -25,15 +25,18 @@ object MessageType { const val MSGTYPE_VIDEO = "m.video" const val MSGTYPE_LOCATION = "m.location" const val MSGTYPE_FILE = "m.file" - const val MSGTYPE_OPTIONS = "org.matrix.options" - const val MSGTYPE_RESPONSE = "org.matrix.response" - const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed" + const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request" // Add, in local, a fake message type in order to StickerMessage can inherit Message class // Because sticker isn't a message type but a event type without msgtype field const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker" + // Fake message types for poll events to be able to inherit them from MessageContent + // Because poll events are not message events and they don't hanve msgtype field + const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start" + const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response" + const val MSGTYPE_CONFETTI = "nic.custom.confetti" const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/OptionItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollResponse.kt similarity index 75% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/OptionItem.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollResponse.kt index 625043df..ddeec5cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/OptionItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollResponse.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 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. @@ -19,11 +19,7 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -/** - * Ref: https://github.com/matrix-org/matrix-doc/pull/2192 - */ @JsonClass(generateAdapter = true) -data class OptionItem( - @Json(name = "label") val label: String?, - @Json(name = "value") val value: String? +data class PollResponse( + @Json(name = "answers") val answers: List<String>? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index a2b38b66..5b387c34 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -91,11 +91,17 @@ interface SendService { /** * Method to send a poll response. * @param pollEventId the poll currently replied to - * @param optionIndex The reply index - * @param optionValue The option value (for compatibility) + * @param answerId The id of the answer * @return a [Cancelable] */ - fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable + fun voteToPoll(pollEventId: String, answerId: String): Cancelable + + /** + * End a poll in the room. + * @param pollEventId event id of the poll + * @return a [Cancelable] + */ + fun endPoll(pollEventId: String): Cancelable /** * Redact (delete) the given event. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt index ef6300ea..3bba2dea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt @@ -32,6 +32,7 @@ object RoomSummaryConstants { EventType.CALL_ANSWER, EventType.ENCRYPTED, EventType.STICKER, - EventType.REACTION + EventType.REACTION, + EventType.POLL_START ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 86cb10bf..932439c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent @@ -126,10 +127,10 @@ fun TimelineEvent.getEditedEventId(): String? { * Get last MessageContent, after a possible edition */ fun TimelineEvent.getLastMessageContent(): MessageContent? { - return if (root.getClearType() == EventType.STICKER) { - root.getClearContent().toModel<MessageStickerContent>() - } else { - (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + return when (root.getClearType()) { + EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>() + EventType.POLL_START -> root.getClearContent().toModel<MessagePollContent>() + else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt index 876e99da..d7dff722 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt @@ -58,6 +58,14 @@ data class SyncResponse( @Json(name = "device_one_time_keys_count") val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null, + /** + * The key algorithms for which the server has an unused fallback key for the device. + * If the client wants the server to have a fallback key for a given key algorithm, + * but that algorithm is not listed in device_unused_fallback_key_types, the client will upload a new key. + */ + @Json(name = "org.matrix.msc2732.device_unused_fallback_key_types") + val deviceUnusedFallbackKeyTypes: List<String>? = null, + /** * List of groups. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt index 10ce0829..e64cf187 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.session.terms +import org.matrix.android.sdk.internal.session.terms.TermsResponse + interface TermsService { enum class ServiceType { IntegrationManager, @@ -28,4 +30,10 @@ interface TermsService { baseUrl: String, agreedUrls: List<String>, token: String?) + + /** + * Get the homeserver terms, from the register API. + * Will be updated once https://github.com/matrix-org/matrix-doc/pull/3012 will be implemented. + */ + suspend fun getHomeserverTerms(baseUrl: String): TermsResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index c11d0027..3a5f8e76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -34,9 +35,8 @@ import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject -internal class CancelGossipRequestWorker(context: Context, - params: WorkerParameters) : - SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, Params::class.java) { +internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( 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 18f34488..7d9c3514 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 @@ -67,6 +67,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult +import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent @@ -431,6 +432,14 @@ internal class DefaultCryptoService @Inject constructor( if (isStarted()) { // Make sure we process to-device messages before generating new one-time-keys #2782 deviceListManager.refreshOutdatedDeviceLists() + // The presence of device_unused_fallback_key_types indicates that the server supports fallback keys. + // If there's no unused signed_curve25519 fallback key we need a new one. + if (syncResponse.deviceUnusedFallbackKeyTypes != null && + // Generate a fallback key only if the server does not already have an unused fallback key. + !syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) { + oneTimeKeysUploader.needsNewFallback() + } + oneTimeKeysUploader.maybeUploadOneTimeKeys() incomingGossipingRequestManager.processReceivedGossipingRequests() } @@ -928,7 +937,7 @@ internal class DefaultCryptoService @Inject constructor( signatures = objectSigner.signObject(canonicalJson) ) - val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null) + val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null) uploadKeysTask.execute(uploadDeviceKeysParams) cryptoStore.setDeviceKeysUploaded(true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 441dfe4a..e1a706df 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -136,6 +136,51 @@ internal class MXOlmDevice @Inject constructor( return store.getOlmAccount().maxOneTimeKeys() } + /** + * Returns an unpublished fallback key + * A call to markKeysAsPublished will mark it as published and this + * call will return null (until a call to generateFallbackKey is made) + */ + fun getFallbackKey(): MutableMap<String, MutableMap<String, String>>? { + try { + return store.getOlmAccount().fallbackKey() + } catch (e: Exception) { + Timber.e("## getFallbackKey() : failed") + } + return null + } + + /** + * Generates a new fallback key if there is not already + * an unpublished one. + * @return true if a new key was generated + */ + fun generateFallbackKeyIfNeeded(): Boolean { + try { + if (!hasUnpublishedFallbackKey()) { + store.getOlmAccount().generateFallbackKey() + store.saveOlmAccount() + return true + } + } catch (e: Exception) { + Timber.e("## generateFallbackKey() : failed") + } + return false + } + + internal fun hasUnpublishedFallbackKey(): Boolean { + return getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty().isNotEmpty() + } + + fun forgetFallbackKey() { + try { + store.getOlmAccount().forgetFallbackKey() + store.saveOlmAccount() + } catch (e: Exception) { + Timber.e("## forgetFallbackKey() : failed") + } + } + /** * Release the instance */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt index c4b62fe9..4aebe091 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto +import android.content.Context import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse @@ -28,11 +29,16 @@ import javax.inject.Inject import kotlin.math.floor import kotlin.math.min +// The spec recommend a 5mn delay, but due to federation +// or server downtime we give it a bit more time (1 hour) +const val FALLBACK_KEY_FORGET_DELAY = 60 * 60_000L + @SessionScope internal class OneTimeKeysUploader @Inject constructor( private val olmDevice: MXOlmDevice, private val objectSigner: ObjectSigner, - private val uploadKeysTask: UploadKeysTask + private val uploadKeysTask: UploadKeysTask, + context: Context ) { // tell if there is a OTK check in progress private var oneTimeKeyCheckInProgress = false @@ -41,6 +47,9 @@ internal class OneTimeKeysUploader @Inject constructor( private var lastOneTimeKeyCheck: Long = 0 private var oneTimeKeyCount: Int? = null + // Simple storage to remember when was uploaded the last fallback key + private val storage = context.getSharedPreferences("OneTimeKeysUploader_${olmDevice.deviceEd25519Key.hashCode()}", Context.MODE_PRIVATE) + /** * Stores the current one_time_key count which will be handled later (in a call of * _onSyncCompleted). The count is e.g. coming from a /sync response. @@ -51,6 +60,15 @@ internal class OneTimeKeysUploader @Inject constructor( oneTimeKeyCount = currentCount } + fun needsNewFallback() { + if (olmDevice.generateFallbackKeyIfNeeded()) { + // As we generated a new one, it's already forgetting one + // so we can clear the last publish time + // (in case the network calls fails after to avoid calling forgetKey) + saveLastFallbackKeyPublishTime(0L) + } + } + /** * Check if the OTK must be uploaded. */ @@ -65,9 +83,19 @@ internal class OneTimeKeysUploader @Inject constructor( return } - lastOneTimeKeyCheck = System.currentTimeMillis() oneTimeKeyCheckInProgress = true + val oneTimeKeyCountFromSync = oneTimeKeyCount + ?: fetchOtkCount() // we don't have count from sync so get from server + ?: return Unit.also { + oneTimeKeyCheckInProgress = false + Timber.w("maybeUploadOneTimeKeys: Failed to get otk count from server") + } + + Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}") + + lastOneTimeKeyCheck = System.currentTimeMillis() + // We then check how many keys we can store in the Account object. val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys() @@ -78,37 +106,37 @@ internal class OneTimeKeysUploader @Inject constructor( // discard the oldest private keys first. This will eventually clean // out stale private keys that won't receive a message. val keyLimit = floor(maxOneTimeKeys / 2.0).toInt() - if (oneTimeKeyCount == null) { - // Ask the server how many otk he has - oneTimeKeyCount = fetchOtkCount() - } - val oneTimeKeyCountFromSync = oneTimeKeyCount - if (oneTimeKeyCountFromSync != null) { - // We need to keep a pool of one time public keys on the server so that - // other devices can start conversations with us. But we can only store - // a finite number of private keys in the olm Account object. - // To complicate things further then can be a delay between a device - // claiming a public one time key from the server and it sending us a - // message. We need to keep the corresponding private key locally until - // we receive the message. - // But that message might never arrive leaving us stuck with duff - // private keys clogging up our local storage. - // So we need some kind of engineering compromise to balance all of - // these factors. - tryOrNull("Unable to upload OTK") { - val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit) - Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent") - } - } else { - Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync") - lastOneTimeKeyCheck = 0 + + // We need to keep a pool of one time public keys on the server so that + // other devices can start conversations with us. But we can only store + // a finite number of private keys in the olm Account object. + // To complicate things further then can be a delay between a device + // claiming a public one time key from the server and it sending us a + // message. We need to keep the corresponding private key locally until + // we receive the message. + // But that message might never arrive leaving us stuck with duff + // private keys clogging up our local storage. + // So we need some kind of engineering compromise to balance all of + // these factors. + tryOrNull("Unable to upload OTK") { + val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit) + Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent") } oneTimeKeyCheckInProgress = false + + // Check if we need to forget a fallback key + val latestPublishedTime = getLastFallbackKeyPublishTime() + if (latestPublishedTime != 0L && System.currentTimeMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) { + // This should be called once you are reasonably certain that you will not receive any more messages + // that use the old fallback key + Timber.d("## forgetFallbackKey()") + olmDevice.forgetFallbackKey() + } } private suspend fun fetchOtkCount(): Int? { return tryOrNull("Unable to get OTK count") { - val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null)) + val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null)) result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) } } @@ -121,24 +149,47 @@ internal class OneTimeKeysUploader @Inject constructor( * @return the number of uploaded keys */ private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int { - if (keyLimit <= keyCount) { + if (keyLimit <= keyCount && !olmDevice.hasUnpublishedFallbackKey()) { // If we don't need to generate any more keys then we are done. return 0 } - val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) - olmDevice.generateOneTimeKeys(keysThisLoop) + var keysThisLoop = 0 + if (keyLimit > keyCount) { + // Creating keys can be an expensive operation so we limit the + // number we generate in one go to avoid blocking the application + // for too long. + keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) + olmDevice.generateOneTimeKeys(keysThisLoop) + } + + // We check before sending if there is an unpublished key in order to saveLastFallbackKeyPublishTime if needed + val hadUnpublishedFallbackKey = olmDevice.hasUnpublishedFallbackKey() val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys()) olmDevice.markKeysAsPublished() + if (hadUnpublishedFallbackKey) { + // It had an unpublished fallback key that was published just now + saveLastFallbackKeyPublishTime(System.currentTimeMillis()) + } if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { // Maybe upload other keys - return keysThisLoop + uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) + return keysThisLoop + + uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) + + (if (hadUnpublishedFallbackKey) 1 else 0) } else { Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519") } } + private fun saveLastFallbackKeyPublishTime(timeMillis: Long) { + storage.edit().putLong("last_fb_key_publish", timeMillis).apply() + } + + private fun getLastFallbackKeyPublishTime(): Long { + return storage.getLong("last_fb_key_publish", 0) + } + /** * Upload curve25519 one time keys. */ @@ -159,10 +210,26 @@ internal class OneTimeKeysUploader @Inject constructor( oneTimeJson["signed_curve25519:$key_id"] = k } + val fallbackJson = mutableMapOf<String, Any>() + val fallbackCurve25519Map = olmDevice.getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty() + fallbackCurve25519Map.forEach { (key_id, key) -> + val k = mutableMapOf<String, Any>() + k["key"] = key + k["fallback"] = true + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k) + k["signatures"] = objectSigner.signObject(canonicalJson) + + fallbackJson["signed_curve25519:$key_id"] = k + } + // For now, we set the device id explicitly, as we may not be using the // same one as used in login. - val uploadParams = UploadKeysTask.Params(null, oneTimeJson) - return uploadKeysTask.execute(uploadParams) + val uploadParams = UploadKeysTask.Params( + deviceKeys = null, + oneTimeKeys = oneTimeJson, + fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() } + ) + return uploadKeysTask.executeRetry(uploadParams, 3) } companion object { @@ -173,6 +240,6 @@ internal class OneTimeKeysUploader @Inject constructor( private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5 // frequency with which to check & upload one-time keys - private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60 * 1000).toLong() // one minute + private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60_000).toLong() // one minute } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index b2ba189b..3129ccae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest @@ -37,9 +38,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams import timber.log.Timber import javax.inject.Inject -internal class SendGossipRequestWorker(context: Context, - params: WorkerParameters) : - SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, Params::class.java) { +internal class SendGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index b96943e4..ff206a3c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap @@ -37,9 +38,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams import timber.log.Timber import javax.inject.Inject -internal class SendGossipWorker(context: Context, - params: WorkerParameters) : - SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, Params::class.java) { +internal class SendGossipWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index 3326d370..5cd647ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -25,6 +25,7 @@ import io.realm.kotlin.where import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity @@ -50,9 +51,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams import timber.log.Timber import javax.inject.Inject -internal class UpdateTrustWorker(context: Context, - params: WorkerParameters) : - SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, Params::class.java) { +internal class UpdateTrustWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadBody.kt index 69b39923..363dee9a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadBody.kt @@ -40,5 +40,12 @@ internal data class KeysUploadBody( * May be absent if no new one-time keys are required. */ @Json(name = "one_time_keys") - val oneTimeKeys: JsonDict? = null + val oneTimeKeys: JsonDict? = null, + + /** + * If the user had previously uploaded a fallback key for a given algorithm, it is replaced. + * The server will only keep one fallback key per algorithm for each user. + */ + @Json(name = "org.matrix.msc2732.fallback_keys") + val fallbackKeys: JsonDict? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt index cac4dadd..30de8e87 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt @@ -32,7 +32,8 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon // the device keys to send. val deviceKeys: DeviceKeys?, // the one-time keys to send. - val oneTimeKeys: JsonDict? + val oneTimeKeys: JsonDict?, + val fallbackKeys: JsonDict? ) } @@ -44,7 +45,8 @@ internal class DefaultUploadKeysTask @Inject constructor( override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse { val body = KeysUploadBody( deviceKeys = params.deviceKeys, - oneTimeKeys = params.oneTimeKeys + oneTimeKeys = params.oneTimeKeys, + fallbackKeys = params.fallbackKeys ) Timber.i("## Uploading device keys -> $body") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt index 481ce85f..a763c05e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt @@ -20,6 +20,7 @@ import androidx.work.Data import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.failure.shouldBeRetried +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker @@ -33,9 +34,8 @@ import javax.inject.Inject * Possible previous worker: None * Possible next worker : None */ -internal class SendVerificationMessageWorker(context: Context, - params: WorkerParameters) : - SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, Params::class.java) { +internal class SendVerificationMessageWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt index 81a067f2..d9a4f1bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.TestInterceptor import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.system.SystemModule +import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import org.matrix.olm.OlmManager import java.io.File @@ -86,6 +87,8 @@ internal interface MatrixComponent { fun sessionManager(): SessionManager + fun matrixWorkerFactory(): MatrixWorkerFactory + fun inject(matrix: Matrix) @Component.Factory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt index 074f8dc4..9e50e9ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageNoticeContent -import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType @@ -57,8 +56,7 @@ object MoshiProvider { .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION) .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE) .registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST) - .registerSubtype(MessageOptionsContent::class.java, MessageType.MSGTYPE_OPTIONS) - .registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_RESPONSE) + .registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE) ) .add(SerializeNulls.JSON_ADAPTER_FACTORY) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NoOpTestModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NoOpTestModule.kt index 210eadef..b136041f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NoOpTestModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NoOpTestModule.kt @@ -20,6 +20,8 @@ import dagger.Module import dagger.Provides import org.matrix.android.sdk.internal.session.MockHttpInterceptor import org.matrix.android.sdk.internal.session.TestInterceptor +import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver +import org.matrix.android.sdk.internal.util.DefaultBackgroundDetectionObserver @Module internal object NoOpTestModule { @@ -28,4 +30,11 @@ internal object NoOpTestModule { @JvmStatic @MockHttpInterceptor fun providesTestInterceptor(): TestInterceptor? = null + + @Provides + @JvmStatic + @MatrixScope + fun providesBackgroundDetectionObserver(): BackgroundDetectionObserver { + return DefaultBackgroundDetectionObserver() + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt index bafffdf8..7d004bc5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt @@ -17,24 +17,38 @@ package org.matrix.android.sdk.internal.di import android.content.Context +import androidx.lifecycle.Observer import androidx.work.Constraints import androidx.work.ListenableWorker import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkInfo import androidx.work.WorkManager import androidx.work.WorkRequest +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import java.util.concurrent.TimeUnit import javax.inject.Inject +@SessionScope internal class WorkManagerProvider @Inject constructor( context: Context, - @SessionId private val sessionId: String + @SessionId private val sessionId: String, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val sessionScope: CoroutineScope ) { private val tag = MATRIX_SDK_TAG_PREFIX + sessionId val workManager = WorkManager.getInstance(context) + init { + checkIfWorkerFactoryIsSetup() + } + /** * Create a OneTimeWorkRequestBuilder, with the Matrix SDK tag */ @@ -60,6 +74,27 @@ internal class WorkManagerProvider @Inject constructor( } } + private fun checkIfWorkerFactoryIsSetup() { + sessionScope.launch(coroutineDispatchers.main) { + val checkWorkerRequest = OneTimeWorkRequestBuilder<MatrixWorkerFactory.CheckFactoryWorker>().build() + workManager.enqueue(checkWorkerRequest) + val checkWorkerLiveState = workManager.getWorkInfoByIdLiveData(checkWorkerRequest.id) + val observer = object : Observer<WorkInfo> { + override fun onChanged(workInfo: WorkInfo) { + if (workInfo.state.isFinished) { + checkWorkerLiveState.removeObserver(this) + if (workInfo.state == WorkInfo.State.FAILED) { + throw RuntimeException("MatrixWorkerFactory is not being set on your worker configuration.\n" + + "Makes sure to add it to a DelegatingWorkerFactory if you have your own factory or use it directly.\n" + + "You can grab the instance through the Matrix class.") + } + } + } + } + checkWorkerLiveState.observeForever(observer) + } + } + companion object { private const val MATRIX_SDK_TAG_PREFIX = "MatrixSDK-" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt index 9fc84e6f..a8971387 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt @@ -203,8 +203,11 @@ internal class MxCallImpl( override fun selectAnswer() { Timber.tag(loggerTag.value).v("select answer $callId") - if (isOutgoing) return - state = CallState.Answering + if (!isOutgoing) return + // This is an outgoing call, select the remote client that answered. + if (state != CallState.Dialing && state !is CallState.Connected) { + Timber.tag(loggerTag.value).w("Expected state is CallState.Dialing or CallState.Connected got $state.") + } CallSelectAnswerContent( callId = callId, partyId = ourPartyId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 7f35c910..52dee0ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.util.MimeTypes +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo import org.matrix.android.sdk.internal.database.mapper.ContentMapper @@ -63,8 +64,8 @@ private data class NewAttachmentAttributes( * Possible previous worker: None * Possible next worker : Always [MultipleEventSendingDispatcherWorker] */ -internal class UploadContentWorker(val context: Context, params: WorkerParameters) : - SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, Params::class.java) { +internal class UploadContentWorker(val context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt index 338f43bd..716859f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.group import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -28,8 +29,8 @@ import javax.inject.Inject * Possible previous worker: None * Possible next worker : None */ -internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : - SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, Params::class.java) { +internal class GetGroupDataWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt index 0ac21b55..da15e158 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt @@ -56,6 +56,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor( val allEvents = (newJoinEvents + inviteEvents).filter { event -> when (event.type) { + EventType.POLL_START, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt index 4df42b2c..ce29efaa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt @@ -19,13 +19,14 @@ import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject -internal class AddPusherWorker(context: Context, params: WorkerParameters) : - SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, Params::class.java) { +internal class AddPusherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 5a1eb190..62b6d626 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -17,20 +17,27 @@ package org.matrix.android.sdk.internal.session.room import io.realm.Realm import org.matrix.android.sdk.api.crypto.VerificationState +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.PollSummaryContent +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.VoteInfo +import org.matrix.android.sdk.api.session.room.model.VoteSummary import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.mapper.ContentMapper @@ -50,11 +57,13 @@ import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import timber.log.Timber import javax.inject.Inject internal class EventRelationsAggregationProcessor @Inject constructor( - @UserId private val userId: String + @UserId private val userId: String, + private val stateEventDataSource: StateEventDataSource ) : EventInsertLiveProcessor { private val allowedTypes = listOf( @@ -69,7 +78,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // TODO Add ? // EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, - EventType.ENCRYPTED + EventType.ENCRYPTED, + EventType.POLL_RESPONSE, + EventType.POLL_END ) override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { @@ -107,9 +118,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.v("###REPLACE in room $roomId for event ${event.eventId}") // A replace! handleReplace(realm, event, content, roomId, isLocalEcho) - } else if (content?.relatesTo?.type == RelationType.RESPONSE) { - Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, event, content, roomId, isLocalEcho) } } @@ -139,9 +147,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.v("###REPLACE in room $roomId for event ${event.eventId}") // A replace! handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) { - Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + } else if (event.getClearType() == EventType.POLL_RESPONSE) { + event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let { pollResponseContent -> + Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") + handleResponse(realm, event, pollResponseContent, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + } } } } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) { @@ -158,6 +168,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor( handleVerification(realm, event, roomId, isLocalEcho, it) } } + EventType.POLL_RESPONSE -> { + event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let { + handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId) + } + } + EventType.POLL_END -> { + event.content.toModel<MessageEndPollContent>(catchError = true)?.let { + handleEndPoll(realm, event, it, roomId, isLocalEcho) + } + } } } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) { // Reaction @@ -188,6 +208,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } + EventType.POLL_RESPONSE -> { + event.content.toModel<MessagePollResponseContent>(catchError = true)?.let { + handleResponse(realm, event, it, roomId, isLocalEcho) + } + } + EventType.POLL_END -> { + event.content.toModel<MessageEndPollContent>(catchError = true)?.let { + handleEndPoll(realm, event, it, roomId, isLocalEcho) + } + } else -> Timber.v("UnHandled event ${event.eventId}") } } catch (t: Throwable) { @@ -276,7 +306,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private fun handleResponse(realm: Realm, event: Event, - content: MessageContent, + content: MessagePollResponseContent, roomId: String, isLocalEcho: Boolean, relatedEventId: String? = null) { @@ -321,11 +351,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( return } - val responseContent = event.content.toModel<MessagePollResponseContent>() ?: return Unit.also { - Timber.d("## POLL Receiving malformed response eventId:$eventId content: ${event.content}") - } - - val optionIndex = responseContent.relatesTo?.option ?: return Unit.also { + val option = content.response?.answers?.first() ?: return Unit.also { Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}") } @@ -336,22 +362,36 @@ internal class EventRelationsAggregationProcessor @Inject constructor( val existingVote = votes[existingVoteIndex] if (existingVote.voteTimestamp < eventTimestamp) { // Take the new one - votes[existingVoteIndex] = VoteInfo(senderId, optionIndex, eventTimestamp) + votes[existingVoteIndex] = VoteInfo(senderId, option, eventTimestamp) if (userId == senderId) { - sumModel.myVote = optionIndex + sumModel.myVote = option } - Timber.v("## POLL adding vote $optionIndex for user $senderId in poll :$targetEventId ") + Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ") } else { Timber.v("## POLL Ignoring vote (older than known one) eventId:$eventId ") } } else { - votes.add(VoteInfo(senderId, optionIndex, eventTimestamp)) + votes.add(VoteInfo(senderId, option, eventTimestamp)) if (userId == senderId) { - sumModel.myVote = optionIndex + sumModel.myVote = option } - Timber.v("## POLL adding vote $optionIndex for user $senderId in poll :$targetEventId ") + Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ") } sumModel.votes = votes + + // Precompute the percentage of votes for all options + val totalVotes = votes.size + sumModel.totalVotes = totalVotes + sumModel.votesSummary = votes + .groupBy({ it.option }, { it.userId }) + .mapValues { + VoteSummary( + total = it.value.size, + percentage = if (totalVotes == 0 && it.value.isEmpty()) 0.0 else it.value.size.toDouble() / totalVotes + ) + } + sumModel.winnerVoteCount = sumModel.votesSummary?.maxOf { it.value.total } ?: 0 + if (isLocalEcho) { existingPollSummary.sourceLocalEchoEvents.add(eventId) } else { @@ -361,6 +401,51 @@ internal class EventRelationsAggregationProcessor @Inject constructor( existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent()) } + private fun handleEndPoll(realm: Realm, + event: Event, + content: MessageEndPollContent, + roomId: String, + isLocalEcho: Boolean) { + val pollEventId = content.relatesTo?.eventId ?: return + + var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst() + if (existing == null) { + Timber.v("## POLL creating new relation summary for $pollEventId") + existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId) + } + + // we have it + val existingPollSummary = existing.pollResponseSummary + ?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also { + existing.pollResponseSummary = it + } + + if (existingPollSummary.closedTime != null) { + Timber.v("## Received poll.end event for already ended poll $pollEventId") + return + } + + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + ?.content?.toModel<PowerLevelsContent>() + ?.let { PowerLevelsHelper(it) } + if (!powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) { + Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId") + return + } + + val txId = event.unsignedData?.transactionId + // is it a remote echo? + if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) { + // ok it has already been managed + Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId") + existingPollSummary.sourceLocalEchoEvents.remove(txId) + existingPollSummary.sourceEvents.add(event.eventId) + return + } + + existingPollSummary.closedTime = event.originServerTs + } + private fun handleInitialAggregatedRelations(realm: Realm, event: Event, roomId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 23b77678..5ae4007c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -70,7 +70,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr } else { when (typeToPrune) { EventType.ENCRYPTED, - EventType.MESSAGE -> { + EventType.MESSAGE, + EventType.POLL_START -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") val unsignedData = EventMapper.map(eventToPrune).unsignedData ?: UnsignedData(null, null) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 77aadef6..d3162aef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -103,8 +103,14 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable { - return localEchoEventFactory.createOptionsReplyEvent(roomId, pollEventId, optionIndex, optionValue) + override fun voteToPoll(pollEventId: String, answerId: String): Cancelable { + return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId) + .also { createLocalEcho(it) } + .let { sendEvent(it) } + } + + override fun endPoll(pollEventId: String): Cancelable { + return localEchoEventFactory.createEndPollEvent(roomId, pollEventId) .also { createLocalEcho(it) } .let { sendEvent(it) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index a31d0cde..85b22628 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.message.ImageInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent @@ -46,6 +47,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.PollAnswer import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo import org.matrix.android.sdk.api.session.room.model.message.PollQuestion +import org.matrix.android.sdk.api.session.room.model.message.PollResponse import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo import org.matrix.android.sdk.api.session.room.model.message.VideoInfo import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent @@ -122,19 +124,28 @@ internal class LocalEchoEventFactory @Inject constructor( )) } - fun createOptionsReplyEvent(roomId: String, - pollEventId: String, - optionIndex: Int, - optionLabel: String): Event { - return createMessageEvent(roomId, - MessagePollResponseContent( - body = optionLabel, - relatesTo = RelationDefaultContent( - type = RelationType.RESPONSE, - option = optionIndex, - eventId = pollEventId) + fun createPollReplyEvent(roomId: String, + pollEventId: String, + answerId: String): Event { + val content = MessagePollResponseContent( + body = answerId, + relatesTo = RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = pollEventId), + response = PollResponse( + answers = listOf(answerId) + ) - )) + ) + val localId = LocalEcho.createLocalEchoId() + return Event( + roomId = roomId, + originServerTs = dummyOriginServerTs(), + senderId = userId, + eventId = localId, + type = EventType.POLL_RESPONSE, + content = content.toContent(), + unsignedData = UnsignedData(age = null, transactionId = localId)) } fun createPollEvent(roomId: String, @@ -147,7 +158,7 @@ internal class LocalEchoEventFactory @Inject constructor( ), answers = options.mapIndexed { index, option -> PollAnswer( - id = index.toString(), + id = "$index-$option", answer = option ) } @@ -164,6 +175,25 @@ internal class LocalEchoEventFactory @Inject constructor( unsignedData = UnsignedData(age = null, transactionId = localId)) } + fun createEndPollEvent(roomId: String, + eventId: String): Event { + val content = MessageEndPollContent( + relatesTo = RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = eventId + ) + ) + val localId = LocalEcho.createLocalEchoId() + return Event( + roomId = roomId, + originServerTs = dummyOriginServerTs(), + senderId = userId, + eventId = localId, + type = EventType.POLL_END, + content = content.toContent(), + unsignedData = UnsignedData(age = null, transactionId = localId)) + } + fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent, originalEvent: TimelineEvent, @@ -413,7 +443,7 @@ internal class LocalEchoEventFactory @Inject constructor( when (content?.msgType) { MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_TEXT, - MessageType.MSGTYPE_NOTICE -> { + MessageType.MSGTYPE_NOTICE -> { var formattedText: String? = null if (content is MessageContentWithFormattedBody) { formattedText = content.matrixFormattedBody @@ -424,11 +454,12 @@ internal class LocalEchoEventFactory @Inject constructor( TextContent(content.body, formattedText) } } - MessageType.MSGTYPE_FILE -> return TextContent("sent a file.") - MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.") - MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.") - MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.") - else -> return TextContent(content?.body ?: "") + MessageType.MSGTYPE_FILE -> return TextContent("sent a file.") + MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.") + MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.") + MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.") + MessageType.MSGTYPE_POLL_START -> return TextContent((content as? MessagePollContent)?.pollCreationInfo?.question?.question ?: "") + else -> return TextContent(content?.body ?: "") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt index 16a9eba3..f44c255f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt @@ -21,6 +21,7 @@ import androidx.work.OneTimeWorkRequest import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.content.UploadContentWorker @@ -38,8 +39,8 @@ import javax.inject.Inject * Possible previous worker: Always [UploadContentWorker] * Possible next worker : None, but it will post new work to send events, encrypted or not */ -internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters) : - SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, Params::class.java) { +internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt index b4436bfc..c03d1fa8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt @@ -19,6 +19,7 @@ import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent @@ -32,8 +33,8 @@ import javax.inject.Inject * Possible previous worker: None * Possible next worker : None */ -internal class RedactEventWorker(context: Context, params: WorkerParameters) : - SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, Params::class.java) { +internal class RedactEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt index 8b7fe4b9..7f24688e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt @@ -23,6 +23,7 @@ import io.realm.RealmConfiguration import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.SessionComponent @@ -38,9 +39,8 @@ import javax.inject.Inject * Possible previous worker: [EncryptEventWorker] or first worker * Possible next worker : None */ -internal class SendEventWorker(context: Context, - params: WorkerParameters) : - SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, Params::class.java) { +internal class SendEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTokenStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTokenStore.kt index 35e561a1..869a4d42 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTokenStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTokenStore.kt @@ -25,9 +25,12 @@ import javax.inject.Inject internal class SyncTokenStore @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { fun getLastToken(): String? { - return Realm.getInstance(monarchy.realmConfiguration).use { + val token = Realm.getInstance(monarchy.realmConfiguration).use { + // Makes sure realm is up-to-date as it's used for querying internally on non looper thread. + it.refresh() it.where(SyncEntity::class.java).findFirst()?.nextBatch } + return token } fun saveToken(realm: Realm, token: String?) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt index 41bb1a44..763cd557 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt @@ -21,8 +21,8 @@ import androidx.work.ExistingWorkPolicy import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.failure.isTokenError +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.WorkManagerProvider -import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.sync.SyncPresence import org.matrix.android.sdk.internal.session.sync.SyncTask @@ -41,9 +41,8 @@ private const val DEFAULT_DELAY_TIMEOUT = 30_000L * Possible previous worker: None * Possible next worker : None */ -internal class SyncWorker(context: Context, - workerParameters: WorkerParameters -) : SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, Params::class.java) { +internal class SyncWorker(context: Context, workerParameters: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( @@ -56,7 +55,6 @@ internal class SyncWorker(context: Context, @Inject lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor - @Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker @Inject lateinit var workManagerProvider: WorkManagerProvider override fun injectWith(injector: SessionComponent) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index d40fd8d0..c52c6a40 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt @@ -18,10 +18,13 @@ package org.matrix.android.sdk.internal.session.terms import dagger.Lazy import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.terms.GetTermsResponse import org.matrix.android.sdk.api.session.terms.TermsService +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.network.RetrofitFactory @@ -55,6 +58,27 @@ internal class DefaultTermsService @Inject constructor( return GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData()) } + /** + * We use a trick here to get the homeserver T&C, we use the register API + */ + override suspend fun getHomeserverTerms(baseUrl: String): TermsResponse { + return try { + executeRequest(null) { + termsAPI.register(baseUrl + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") + } + // Return empty result if it succeed, but it should never happen + TermsResponse() + } catch (throwable: Throwable) { + @Suppress("UNCHECKED_CAST") + TermsResponse( + policies = (throwable.toRegistrationFlowResponse() + ?.params + ?.get(LoginFlowTypes.TERMS) as? JsonDict) + ?.get("policies") as? JsonDict + ) + } + } + override suspend fun agreeToTerms(serviceType: TermsService.ServiceType, baseUrl: String, agreedUrls: List<String>, @@ -91,7 +115,7 @@ internal class DefaultTermsService @Inject constructor( private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String { val servicePath = when (serviceType) { TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH - TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2 + TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2 } return "${baseUrl.ensureTrailingSlash()}$servicePath" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt index 91d27030..fb6aff5a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.session.terms +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.api.util.emptyJsonDict import org.matrix.android.sdk.internal.network.HttpHeaders import retrofit2.http.Body import retrofit2.http.GET @@ -37,4 +39,12 @@ internal interface TermsAPI { suspend fun agreeToTerms(@Url url: String, @Body params: AcceptTermsBody, @Header(HttpHeaders.Authorization) token: String) + + /** + * API to retrieve the terms for a homeserver. The API /terms does not exist yet, so retrieve the terms from the login flow. + * We do not care about the result (Credentials) + */ + @POST + suspend fun register(@Url url: String, + @Body body: JsonDict = emptyJsonDict) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt index 3e977b31..9c8b36a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt @@ -18,26 +18,32 @@ package org.matrix.android.sdk.internal.util import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import org.matrix.android.sdk.internal.di.MatrixScope import timber.log.Timber -import javax.inject.Inject -/** - * To be attached to ProcessLifecycleOwner lifecycle - */ -@MatrixScope -internal class BackgroundDetectionObserver @Inject constructor() : DefaultLifecycleObserver { +interface BackgroundDetectionObserver : DefaultLifecycleObserver { + val isInBackground: Boolean + + fun register(listener: Listener) + fun unregister(listener: Listener) + + interface Listener { + fun onMoveToForeground() + fun onMoveToBackground() + } +} + +internal class DefaultBackgroundDetectionObserver : BackgroundDetectionObserver { - var isInBackground: Boolean = true + override var isInBackground: Boolean = true private set - private val listeners = LinkedHashSet<Listener>() + private val listeners = LinkedHashSet<BackgroundDetectionObserver.Listener>() - fun register(listener: Listener) { + override fun register(listener: BackgroundDetectionObserver.Listener) { listeners.add(listener) } - fun unregister(listener: Listener) { + override fun unregister(listener: BackgroundDetectionObserver.Listener) { listeners.remove(listener) } @@ -52,9 +58,4 @@ internal class BackgroundDetectionObserver @Inject constructor() : DefaultLifecy isInBackground = true listeners.forEach { it.onMoveToBackground() } } - - interface Listener { - fun onMoveToForeground() - fun onMoveToBackground() - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt index b58cab99..0b451e9c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt @@ -17,16 +17,33 @@ package org.matrix.android.sdk.internal.worker import android.content.Context +import androidx.work.CoroutineWorker import androidx.work.ListenableWorker import androidx.work.WorkerFactory import androidx.work.WorkerParameters +import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker +import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker +import org.matrix.android.sdk.internal.crypto.SendGossipWorker +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker +import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker +import org.matrix.android.sdk.internal.di.MatrixScope +import org.matrix.android.sdk.internal.session.content.UploadContentWorker +import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker +import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker +import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker +import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker +import org.matrix.android.sdk.internal.session.room.send.SendEventWorker +import org.matrix.android.sdk.internal.session.sync.job.SyncWorker import timber.log.Timber import javax.inject.Inject -import javax.inject.Provider -class MatrixWorkerFactory @Inject constructor( - private val workerFactories: Map<Class<out ListenableWorker>, @JvmSuppressWildcards Provider<DelegateWorkerFactory>> -) : WorkerFactory() { +/** + * This factory is responsible of creating Workers by giving the session manager. + * This is not the cleanest way but getting SessionComponent is dependant of args type. + */ +@MatrixScope +internal class MatrixWorkerFactory @Inject constructor(private val sessionManager: SessionManager) : WorkerFactory() { override fun createWorker( appContext: Context, @@ -34,11 +51,61 @@ class MatrixWorkerFactory @Inject constructor( workerParameters: WorkerParameters ): ListenableWorker? { Timber.d("MatrixWorkerFactory.createWorker for $workerClassName") + return when (workerClassName) { + CheckFactoryWorker::class.java.name -> + CheckFactoryWorker(appContext, workerParameters, true) + AddPusherWorker::class.java.name -> + AddPusherWorker(appContext, workerParameters, sessionManager) + CancelGossipRequestWorker::class.java.name -> + CancelGossipRequestWorker(appContext, workerParameters, sessionManager) + GetGroupDataWorker::class.java.name -> + GetGroupDataWorker(appContext, workerParameters, sessionManager) + MultipleEventSendingDispatcherWorker::class.java.name -> + MultipleEventSendingDispatcherWorker(appContext, workerParameters, sessionManager) + RedactEventWorker::class.java.name -> + RedactEventWorker(appContext, workerParameters, sessionManager) + SendEventWorker::class.java.name -> + SendEventWorker(appContext, workerParameters, sessionManager) + SendGossipRequestWorker::class.java.name -> + SendGossipRequestWorker(appContext, workerParameters, sessionManager) + SendGossipWorker::class.java.name -> + SendGossipWorker(appContext, workerParameters, sessionManager) + SendVerificationMessageWorker::class.java.name -> + SendVerificationMessageWorker(appContext, workerParameters, sessionManager) + SyncWorker::class.java.name -> + SyncWorker(appContext, workerParameters, sessionManager) + UpdateTrustWorker::class.java.name -> + UpdateTrustWorker(appContext, workerParameters, sessionManager) + UploadContentWorker::class.java.name -> + UploadContentWorker(appContext, workerParameters, sessionManager) + else -> { + Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.") + // Return null to delegate to the default WorkerFactory. + null + } + } + } + + /** + * This worker is launched by the factory with the isCreatedByMatrixWorkerFactory flag to true. + * If the MatrixWorkerFactory is not set up, it will default to the other constructor and it will throw + */ + class CheckFactoryWorker(context: Context, + workerParameters: WorkerParameters, + private val isCreatedByMatrixWorkerFactory: Boolean) : + CoroutineWorker(context, workerParameters) { + + // Called by WorkManager if there is no MatrixWorkerFactory + constructor(context: Context, workerParameters: WorkerParameters) : this(context, + workerParameters, + isCreatedByMatrixWorkerFactory = false) - val foundEntry = - workerFactories.entries.find { Class.forName(workerClassName).isAssignableFrom(it.key) } - val factoryProvider = foundEntry?.value - ?: throw IllegalArgumentException("unknown worker class name: $workerClassName") - return factoryProvider.get().create(appContext, workerParameters) + override suspend fun doWork(): Result { + return if (!isCreatedByMatrixWorkerFactory) { + Result.failure() + } else { + Result.success() + } + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt index d4179e22..334c4580 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt @@ -22,6 +22,7 @@ import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.session.SessionComponent import timber.log.Timber @@ -33,6 +34,7 @@ import timber.log.Timber internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>( context: Context, workerParameters: WorkerParameters, + private val sessionManager: SessionManager, private val paramClass: Class<PARAM> ) : CoroutineWorker(context, workerParameters) { @@ -48,7 +50,7 @@ internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>( .also { Timber.e("Unable to parse work parameters") } return try { - val sessionComponent = getSessionComponent(params.sessionId) + val sessionComponent = sessionManager.getSessionComponent(params.sessionId) ?: return buildErrorResult(params, "No session") // Make sure to inject before handling error as you may need some dependencies to process them. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Worker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Worker.kt deleted file mode 100644 index 24035355..00000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Worker.kt +++ /dev/null @@ -1,25 +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.worker - -import androidx.work.ListenableWorker -import org.matrix.android.sdk.api.Matrix -import org.matrix.android.sdk.internal.session.SessionComponent - -internal fun ListenableWorker.getSessionComponent(sessionId: String): SessionComponent? { - return Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(sessionId) -} -- GitLab