diff --git a/CHANGES.md b/CHANGES.md index 30fb194c5e1a802761f919a62aed257f445108e3..22ed75abf2e9a698b654cee9f27b3d1a1bfaa9bd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,18 @@ Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/main/CHANGES.md +Changes in Element v1.4.32 (2022-08-10) +======================================= + +Imported from Element 1.4.32. (https://github.com/vector-im/element-android/releases/tag/v1.4.32) + +Changes in Element v1.4.30 (2022-07-29) +======================================= + +SDK API changes âš ï¸ +------------------ +- Communities/Groups are removed completely ([#5733](https://github.com/vector-im/element-android/issues/5733)) +- SDK - The SpaceFilter is query parameter is no longer nullable, use SpaceFilter.NoFilter instead ([#6666](https://github.com/vector-im/element-android/issues/6666)) + Changes in Matrix-SDK 1.4.27 (2022-07-20) =================================================== diff --git a/dependencies.gradle b/dependencies.gradle index db9278b9757695e11478cb61a1bbae54046c6e33..93a62a548ed30ba09328c73d4e43369e7997db11 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -13,15 +13,16 @@ ext.versions = [ def gradle = "7.1.3" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.6.21" -def kotlinCoroutines = "1.6.3" +def kotlinCoroutines = "1.6.4" def dagger = "2.42" +def appDistribution = "16.0.0-beta03" def retrofit = "2.9.0" def arrow = "0.8.2" def markwon = "4.6.2" def moshi = "1.13.0" -def lifecycle = "2.4.1" +def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.151.1" +def flipper = "0.156.0" def epoxy = "4.6.2" def mavericks = "2.7.0" def glide = "4.13.2" @@ -29,7 +30,7 @@ def bigImageViewer = "1.8.1" def jjwt = "0.11.5" def vanniktechEmoji = "0.15.0" -def fragment = "1.4.1" +def fragment = "1.5.1" // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 @@ -49,9 +50,7 @@ ext.libs = [ 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ - 'annotation' : "androidx.annotation:annotation:1.4.0", - 'activity' : "androidx.activity:activity:1.4.0", - 'annotations' : "androidx.annotation:annotation:1.3.0", + 'activity' : "androidx.activity:activity:1.5.1", 'appCompat' : "androidx.appcompat:appcompat:1.4.2", 'biometric' : "androidx.biometric:biometric:1.1.0", 'core' : "androidx.core:core-ktx:1.8.0", @@ -83,7 +82,9 @@ ext.libs = [ 'transition' : "androidx.transition:transition:1.2.0", ], google : [ - 'material' : "com.google.android.material:material:1.6.1" + 'material' : "com.google.android.material:material:1.6.1", + 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", + 'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", ], dagger : [ 'dagger' : "com.google.dagger:dagger:$dagger", @@ -96,6 +97,9 @@ ext.libs = [ 'flipper' : "com.facebook.flipper:flipper:$flipper", 'flipperNetworkPlugin' : "com.facebook.flipper:flipper-network-plugin:$flipper", ], + element : [ + 'opusencoder' : "io.element.android:opusencoder:1.0.4", + ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", 'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi", diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index b785c7f50b71d941d22ebd9d8beabd29e73c1a07..d5972ed8461eff2b78f6deb8fbd20440b306bb6f 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -74,6 +74,7 @@ ext.groups = [ 'com.github.javaparser', 'com.github.piasy', 'com.github.shyiko.klob', + 'com.github.rubensousa', 'com.google', 'com.google.android', 'com.google.api.grpc', @@ -124,6 +125,8 @@ ext.groups = [ 'commons-logging', 'info.picocli', 'io.arrow-kt', + 'io.element.android', + 'io.github.davidburstrom.contester', 'io.github.detekt.sarif4k', 'io.github.microutils', 'io.github.reactivecircus.flowbinding', diff --git a/gradle.properties b/gradle.properties index a5d0668154e016749568e6804dd059f1e31ba4bf..2dd5d3213fe9fda8191d3ffcc7885f6aee37e35a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ vector.httpLogLevel=NONE # Ref: https://github.com/vanniktech/gradle-maven-publish-plugin GROUP=org.matrix.android POM_ARTIFACT_ID=matrix-android-sdk2 -VERSION_NAME=1.4.27 +VERSION_NAME=1.4.32 POM_PACKAGING=aar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1e0c8dc42bfac53afdfb5d12df6da8b4f96f93c..924f117d9a3ca0a3b524ec5a466d9744356ef40d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index a7cb1cad2fc016d84f240d069491f57bf544ef66..e57154f6a8ad006db3e7934f236cba66e850b73f 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -19,7 +19,7 @@ buildscript { } } dependencies { - classpath "io.realm:realm-gradle-plugin:10.9.0" + classpath "io.realm:realm-gradle-plugin:10.11.0" } } @@ -202,7 +202,7 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.51' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53' testImplementation libs.tests.junit // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 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 7dafe33935f0c84baec58743f05208b1d4d9266e..a78953caacc87a7cfa27534f64b9d104876ff597 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 @@ -18,12 +18,15 @@ package org.matrix.android.sdk.common import android.content.Context import android.net.Uri +import android.util.Log import androidx.lifecycle.Observer import androidx.test.internal.runner.junit4.statement.UiThreadStatement import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -38,7 +41,10 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -47,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.sync.SyncState import timber.log.Timber import java.util.UUID +import java.util.concurrent.CancellationException import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -54,7 +61,7 @@ import java.util.concurrent.TimeUnit * This class exposes methods to be used in common cases * Registration, login, Sync, Sending messages... */ -class CommonTestHelper private constructor(context: Context) { +class CommonTestHelper internal constructor(context: Context) { companion object { internal fun runSessionTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CommonTestHelper) -> Unit) { @@ -241,6 +248,37 @@ class CommonTestHelper private constructor(context: Context) { return sentEvents } + fun waitForAndAcceptInviteInRoom(otherSession: Session, roomID: String) { + waitWithLatch { latch -> + retryPeriodicallyWithLatch(latch) { + val roomSummary = otherSession.getRoomSummary(roomID) + (roomSummary != null && roomSummary.membership == Membership.INVITE).also { + if (it) { + Log.v("# TEST", "${otherSession.myUserId} can see the invite") + } + } + } + } + + // not sure why it's taking so long :/ + runBlockingTest(90_000) { + Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $roomID") + try { + otherSession.roomService().joinRoom(roomID) + } catch (ex: JoinRoomFailure.JoinedWithTimeout) { + // it's ok we will wait after + } + } + + Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") + waitWithLatch { + retryPeriodicallyWithLatch(it) { + val roomSummary = otherSession.getRoomSummary(roomID) + roomSummary != null && roomSummary.membership == Membership.JOIN + } + } + } + /** * Reply in a thread * @param room the room where to send the messages @@ -285,6 +323,8 @@ class CommonTestHelper private constructor(context: Context) { ) assertNotNull(session) return session.also { + // most of the test was created pre-MSC3061 so ensure compatibility + it.cryptoService().enableShareKeyOnInvite(false) trackedSessions.add(session) } } @@ -428,16 +468,26 @@ class CommonTestHelper private constructor(context: Context) { * @param latch * @throws InterruptedException */ - fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) { + fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis, job: Job? = null) { assertTrue( "Timed out after " + timeout + "ms waiting for something to happen. See stacktrace for cause.", - latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) + latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS).also { + if (!it) { + // cancel job on timeout + job?.cancel("Await timeout") + } + } ) } suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { while (true) { - delay(1000) + try { + delay(1000) + } catch (ex: CancellationException) { + // the job was canceled, just stop + return + } if (condition()) { latch.countDown() return @@ -447,10 +497,10 @@ class CommonTestHelper private constructor(context: Context) { fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, dispatcher: CoroutineDispatcher = Dispatchers.Main, block: suspend (CountDownLatch) -> Unit) { val latch = CountDownLatch(1) - coroutineScope.launch(dispatcher) { + val job = coroutineScope.launch(dispatcher) { block(latch) } - await(latch, timeout) + await(latch, timeout, job) } fun <T> runBlockingTest(timeout: Long = TestConstants.timeOutMillis, block: suspend () -> T): T { 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 5fd86d4fdb87ec58d9d6481ab97138d8cdf79dde..f36bfb6210e164e8e4c82316bbd4b70601ff3776 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 @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility 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.model.message.MessageContent @@ -76,11 +77,14 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { /** * @return alice session */ - fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData { + fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val roomId = testHelper.runBlockingTest { - aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" }) + aliceSession.roomService().createRoom(CreateRoomParams().apply { + historyVisibility = roomHistoryVisibility + name = "MyRoom" + }) } if (encryptedRoom) { testHelper.waitWithLatch { latch -> @@ -104,8 +108,8 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { /** * @return alice and bob sessions */ - fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData { - val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom) + fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData { + val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom, roomHistoryVisibility) val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..32d63a1934241c23e99f0a6bdb7a54436923914b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt @@ -0,0 +1,298 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import android.util.Log +import androidx.test.filters.LargeTest +import org.amshove.kluent.internal.assertEquals +import org.junit.Assert +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest +import org.matrix.android.sdk.common.CryptoTestData +import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.common.TestConstants +import org.matrix.android.sdk.common.TestMatrixCallback + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class E2EShareKeysConfigTest : InstrumentedTest { + + @Test + fun msc3061ShouldBeDisabledByDefault() = runCryptoTest(context()) { _, commonTestHelper -> + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) + Assert.assertFalse("MSC3061 is lab and should be disabled by default", aliceSession.cryptoService().isShareKeysOnInviteEnabled()) + } + + @Test + fun ensureKeysAreNotSharedIfOptionDisabled() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + aliceSession.cryptoService().enableShareKeyOnInvite(false) + val roomId = commonTestHelper.runBlockingTest { + aliceSession.roomService().createRoom(CreateRoomParams().apply { + historyVisibility = RoomHistoryVisibility.SHARED + name = "MyRoom" + enableEncryption() + }) + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true + } + } + val roomAlice = aliceSession.roomService().getRoom(roomId)!! + + // send some messages + val withSession1 = commonTestHelper.sendTextMessage(roomAlice, "Hello", 1) + aliceSession.cryptoService().discardOutboundSession(roomId) + val withSession2 = commonTestHelper.sendTextMessage(roomAlice, "World", 1) + + // Create bob account + val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(withInitialSync = true)) + + // Let alice invite bob + commonTestHelper.runBlockingTest { + roomAlice.membershipService().invite(bobSession.myUserId) + } + + commonTestHelper.waitForAndAcceptInviteInRoom(bobSession, roomId) + + // Bob has join but should not be able to decrypt history + cryptoTestHelper.ensureCannotDecrypt( + withSession1.map { it.eventId } + withSession2.map { it.eventId }, + bobSession, + roomId + ) + + // We don't need bob anymore + commonTestHelper.signOutAndClose(bobSession) + + // Now let's enable history key sharing on alice side + aliceSession.cryptoService().enableShareKeyOnInvite(true) + + // let's add a new message first + val afterFlagOn = commonTestHelper.sendTextMessage(roomAlice, "After", 1) + + // Worth nothing to check that the session was rotated + Assert.assertNotEquals( + "Session should have been rotated", + withSession2.first().root.content?.get("session_id")!!, + afterFlagOn.first().root.content?.get("session_id")!! + ) + + // Invite a new user + val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) + + // Let alice invite sam + commonTestHelper.runBlockingTest { + roomAlice.membershipService().invite(samSession.myUserId) + } + + commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId) + + // Sam shouldn't be able to decrypt messages with the first session, but should decrypt the one with 3rd session + cryptoTestHelper.ensureCannotDecrypt( + withSession1.map { it.eventId } + withSession2.map { it.eventId }, + samSession, + roomId + ) + + cryptoTestHelper.ensureCanDecrypt( + afterFlagOn.map { it.eventId }, + samSession, + roomId, + afterFlagOn.map { it.root.getClearContent()?.get("body") as String }) + } + + @Test + fun ifSharingDisabledOnAliceSideBobShouldNotShareAliceHistoty() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED) + val aliceSession = testData.firstSession.also { + it.cryptoService().enableShareKeyOnInvite(false) + } + val bobSession = testData.secondSession!!.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + + val (fromAliceNotSharable, fromBobSharable, samSession) = commonAliceAndBobSendMessages(commonTestHelper, aliceSession, testData, bobSession) + + // Bob should have shared history keys to sam. + // But has alice hasn't enabled sharing, bob shouldn't send her sessions + cryptoTestHelper.ensureCannotDecrypt( + fromAliceNotSharable.map { it.eventId }, + samSession, + testData.roomId + ) + + cryptoTestHelper.ensureCanDecrypt( + fromBobSharable.map { it.eventId }, + samSession, + testData.roomId, + fromBobSharable.map { it.root.getClearContent()?.get("body") as String }) + } + + @Test + fun ifSharingEnabledOnAliceSideBobShouldShareAliceHistoty() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED) + val aliceSession = testData.firstSession.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + val bobSession = testData.secondSession!!.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + + val (fromAliceNotSharable, fromBobSharable, samSession) = commonAliceAndBobSendMessages(commonTestHelper, aliceSession, testData, bobSession) + + cryptoTestHelper.ensureCanDecrypt( + fromAliceNotSharable.map { it.eventId }, + samSession, + testData.roomId, + fromAliceNotSharable.map { it.root.getClearContent()?.get("body") as String }) + + cryptoTestHelper.ensureCanDecrypt( + fromBobSharable.map { it.eventId }, + samSession, + testData.roomId, + fromBobSharable.map { it.root.getClearContent()?.get("body") as String }) + } + + private fun commonAliceAndBobSendMessages(commonTestHelper: CommonTestHelper, aliceSession: Session, testData: CryptoTestData, bobSession: Session): Triple<List<TimelineEvent>, List<TimelineEvent>, Session> { + val fromAliceNotSharable = commonTestHelper.sendTextMessage(aliceSession.getRoom(testData.roomId)!!, "Hello from alice", 1) + val fromBobSharable = commonTestHelper.sendTextMessage(bobSession.getRoom(testData.roomId)!!, "Hello from bob", 1) + + // Now let bob invite Sam + // Invite a new user + val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) + + // Let bob invite sam + commonTestHelper.runBlockingTest { + bobSession.getRoom(testData.roomId)!!.membershipService().invite(samSession.myUserId) + } + + commonTestHelper.waitForAndAcceptInviteInRoom(samSession, testData.roomId) + return Triple(fromAliceNotSharable, fromBobSharable, samSession) + } + + // test flag on backup is correct + + @Test + fun testBackupFlagIsCorrect() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + aliceSession.cryptoService().enableShareKeyOnInvite(false) + val roomId = commonTestHelper.runBlockingTest { + aliceSession.roomService().createRoom(CreateRoomParams().apply { + historyVisibility = RoomHistoryVisibility.SHARED + name = "MyRoom" + enableEncryption() + }) + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true + } + } + val roomAlice = aliceSession.roomService().getRoom(roomId)!! + + // send some messages + val notSharableMessage = commonTestHelper.sendTextMessage(roomAlice, "Hello", 1) + aliceSession.cryptoService().enableShareKeyOnInvite(true) + val sharableMessage = commonTestHelper.sendTextMessage(roomAlice, "World", 1) + + Log.v("#E2E TEST", "Create and start key backup for bob ...") + val keysBackupService = aliceSession.cryptoService().keysBackupService() + val keyBackupPassword = "FooBarBaz" + val megolmBackupCreationInfo = commonTestHelper.doSync<MegolmBackupCreationInfo> { + keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it) + } + val version = commonTestHelper.doSync<KeysVersion> { + keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it) + } + + commonTestHelper.waitWithLatch { latch -> + keysBackupService.backupAllGroupSessions( + null, + TestMatrixCallback(latch, true) + ) + } + + // signout + commonTestHelper.signOutAndClose(aliceSession) + + val newAliceSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + newAliceSession.cryptoService().enableShareKeyOnInvite(true) + + newAliceSession.cryptoService().keysBackupService().let { kbs -> + val keyVersionResult = commonTestHelper.doSync<KeysVersionResult?> { + kbs.getVersion(version.version, it) + } + + val importedResult = commonTestHelper.doSync<ImportRoomKeysResult> { + kbs.restoreKeyBackupWithPassword( + keyVersionResult!!, + keyBackupPassword, + null, + null, + null, + it + ) + } + + assertEquals(2, importedResult.totalNumberOfKeys) + } + + // Now let's invite sam + // Invite a new user + val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) + + // Let alice invite sam + commonTestHelper.runBlockingTest { + newAliceSession.getRoom(roomId)!!.membershipService().invite(samSession.myUserId) + } + + commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId) + + // Sam shouldn't be able to decrypt messages with the first session, but should decrypt the one with 3rd session + cryptoTestHelper.ensureCannotDecrypt( + notSharableMessage.map { it.eventId }, + samSession, + roomId + ) + + cryptoTestHelper.ensureCanDecrypt( + sharableMessage.map { it.eventId }, + samSession, + roomId, + sharableMessage.map { it.root.getClearContent()?.get("body") as String }) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index 5a61eee7fe30c238ddfd410e318c9a901999a980..251c13ccbf4a2dd4142cb4599518c31940bed670 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -23,7 +23,6 @@ import org.amshove.kluent.fail import org.amshove.kluent.internal.assertEquals import org.junit.Assert import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -49,9 +48,7 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.Room -import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -67,10 +64,10 @@ import org.matrix.android.sdk.common.TestMatrixCallback import org.matrix.android.sdk.mustFail import java.util.concurrent.CountDownLatch +// @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.") @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest -@Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.") class E2eeSanityTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) @@ -115,7 +112,7 @@ class E2eeSanityTests : InstrumentedTest { // All user should accept invite otherAccounts.forEach { otherSession -> - waitForAndAcceptInviteInRoom(testHelper, otherSession, e2eRoomID) + testHelper.waitForAndAcceptInviteInRoom(otherSession, e2eRoomID) Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID") } @@ -156,7 +153,7 @@ class E2eeSanityTests : InstrumentedTest { } newAccount.forEach { - waitForAndAcceptInviteInRoom(testHelper, it, e2eRoomID) + testHelper.waitForAndAcceptInviteInRoom(it, e2eRoomID) } ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID) @@ -740,37 +737,6 @@ class E2eeSanityTests : InstrumentedTest { } } - private fun waitForAndAcceptInviteInRoom(testHelper: CommonTestHelper, otherSession: Session, e2eRoomID: String) { - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val roomSummary = otherSession.getRoomSummary(e2eRoomID) - (roomSummary != null && roomSummary.membership == Membership.INVITE).also { - if (it) { - Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice") - } - } - } - } - - // not sure why it's taking so long :/ - testHelper.runBlockingTest(90_000) { - Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") - try { - otherSession.roomService().joinRoom(e2eRoomID) - } catch (ex: JoinRoomFailure.JoinedWithTimeout) { - // it's ok we will wait after - } - } - - Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") - testHelper.waitWithLatch { - testHelper.retryPeriodicallyWithLatch(it) { - val roomSummary = otherSession.getRoomSummary(e2eRoomID) - roomSummary != null && roomSummary.membership == Membership.JOIN - } - } - } - private fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List<String>, session: Session, e2eRoomID: String) { testHelper.waitWithLatch { latch -> sentEventIds.forEach { sentEventId -> diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..32a95008b1ac5017ff1790dc6442f09041795e66 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -0,0 +1,424 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import android.util.Log +import androidx.test.filters.LargeTest +import org.amshove.kluent.internal.assertEquals +import org.amshove.kluent.internal.assertNotEquals +import org.junit.Assert +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +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.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.shouldShareHistory +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest +import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.SessionTestParams + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class E2eeShareKeysHistoryTest : InstrumentedTest { + + @Test + fun testShareMessagesHistoryWithRoomWorldReadable() { + testShareHistoryWithRoomVisibility(RoomHistoryVisibility.WORLD_READABLE) + } + + @Test + fun testShareMessagesHistoryWithRoomShared() { + testShareHistoryWithRoomVisibility(RoomHistoryVisibility.SHARED) + } + + @Test + fun testShareMessagesHistoryWithRoomJoined() { + testShareHistoryWithRoomVisibility(RoomHistoryVisibility.JOINED) + } + + @Test + fun testShareMessagesHistoryWithRoomInvited() { + testShareHistoryWithRoomVisibility(RoomHistoryVisibility.INVITED) + } + + /** + * In this test we create a room and test that new members + * can decrypt history when the room visibility is + * RoomHistoryVisibility.SHARED or RoomHistoryVisibility.WORLD_READABLE. + * We should not be able to view messages/decrypt otherwise + */ + private fun testShareHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) = + runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility) + + val e2eRoomID = cryptoTestData.roomId + + // Alice + val aliceSession = cryptoTestData.firstSession.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! + + // Bob + val bobSession = cryptoTestData.secondSession!!.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + val bobRoomPOV = bobSession.roomService().getRoom(e2eRoomID)!! + + assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) + Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID") + + val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!", testHelper) + Assert.assertTrue("Message should be sent", aliceMessageId != null) + Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID") + + // Bob should be able to decrypt the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } + } + } + + // Create a new user + val arisSession = testHelper.createAccount("aris", SessionTestParams(true)).also { + it.cryptoService().enableShareKeyOnInvite(true) + } + Log.v("#E2E TEST", "Aris user created") + + // Alice invites new user to the room + testHelper.runBlockingTest { + Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}") + aliceRoomPOV.membershipService().invite(arisSession.myUserId) + } + + waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper) + + ensureMembersHaveJoined(aliceSession, arrayListOf(arisSession), e2eRoomID, testHelper) + Log.v("#E2E TEST", "Aris has joined roomId: $e2eRoomID") + + when (roomHistoryVisibility) { + RoomHistoryVisibility.WORLD_READABLE, + RoomHistoryVisibility.SHARED, + null + -> { + // Aris should be able to decrypt the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE + ).also { + if (it) { + Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } + } + } + } + RoomHistoryVisibility.INVITED, + RoomHistoryVisibility.JOINED -> { + // Aris should not even be able to get the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = arisSession.roomService().getRoom(e2eRoomID) + ?.timelineService() + ?.getTimelineEvent(aliceMessageId!!) + timelineEvent == null + } + } + } + } + + testHelper.signOutAndClose(arisSession) + cryptoTestData.cleanUp(testHelper) + } + + @Test + fun testNeedsRotationFromWorldReadableToShared() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared")) + } + + @Test + fun testNeedsRotationFromWorldReadableToInvited() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("invited")) + } + + @Test + fun testNeedsRotationFromWorldReadableToJoined() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("joined")) + } + + @Test + fun testNeedsRotationFromSharedToWorldReadable() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("world_readable")) + } + + @Test + fun testNeedsRotationFromSharedToInvited() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("invited")) + } + + @Test + fun testNeedsRotationFromSharedToJoined() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("joined")) + } + + @Test + fun testNeedsRotationFromInvitedToShared() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared")) + } + + @Test + fun testNeedsRotationFromInvitedToWorldReadable() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("world_readable")) + } + + @Test + fun testNeedsRotationFromInvitedToJoined() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("joined")) + } + + @Test + fun testNeedsRotationFromJoinedToShared() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared")) + } + + @Test + fun testNeedsRotationFromJoinedToInvited() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("invited")) + } + + @Test + fun testNeedsRotationFromJoinedToWorldReadable() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("world_readable")) + } + + /** + * In this test we will test that a rotation is needed when + * When the room's history visibility setting changes to world_readable or shared + * from invited or joined, or changes to invited or joined from world_readable or shared, + * senders that support this flag must rotate their megolm sessions. + */ + private fun testRotationDueToVisibilityChange( + initRoomHistoryVisibility: RoomHistoryVisibility, + nextRoomHistoryVisibility: RoomHistoryVisibilityContent + ) { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, initRoomHistoryVisibility) + val e2eRoomID = cryptoTestData.roomId + + // Alice + val aliceSession = cryptoTestData.firstSession.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! +// val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting + + // Bob + val bobSession = cryptoTestData.secondSession!! + + val bobRoomPOV = bobSession.roomService().getRoom(e2eRoomID)!! + + assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) + Log.v("#E2E TEST ROTATION", "Alice and Bob are in roomId: $e2eRoomID") + + val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!", testHelper) + Assert.assertTrue("Message should be sent", aliceMessageId != null) + Log.v("#E2E TEST ROTATION", "Alice sent message to roomId: $e2eRoomID") + + // Bob should be able to decrypt the message + var firstAliceMessageMegolmSessionId: String? = null + val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID) + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobRoomPov + ?.timelineService() + ?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + firstAliceMessageMegolmSessionId = timelineEvent?.root?.content?.get("session_id") as? String + Log.v( + "#E2E TEST", + "Bob can decrypt the message (sid:$firstAliceMessageMegolmSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}" + ) + } + } + } + } + + Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId) + + var secondAliceMessageSessionId: String? = null + sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage -> + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobRoomPov + ?.timelineService() + ?.getTimelineEvent(secondMessage) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + secondAliceMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String + Log.v( + "#E2E TEST", + "Bob can decrypt the message (sid:$secondAliceMessageSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}" + ) + } + } + } + } + } + assertEquals("No rotation needed session should be the same", firstAliceMessageMegolmSessionId, secondAliceMessageSessionId) + Log.v("#E2E TEST ROTATION", "No rotation needed yet") + + // Let's change the room history visibility + testHelper.runBlockingTest { + aliceRoomPOV.stateService() + .sendStateEvent( + eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, + stateKey = "", + body = RoomHistoryVisibilityContent( + historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr + ).toContent() + ) + } + + // ensure that the state did synced down + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)?.content + ?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility + } + } + + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val roomVisibility = aliceSession.getRoom(e2eRoomID)!! + .stateService() + .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty) + ?.content + ?.toModel<RoomHistoryVisibilityContent>() + Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}") + roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility + } + } + + var aliceThirdMessageSessionId: String? = null + sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage -> + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobRoomPov + ?.timelineService() + ?.getTimelineEvent(thirdMessage) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + aliceThirdMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String + } + } + } + } + } + + when { + initRoomHistoryVisibility.shouldShareHistory() == nextRoomHistoryVisibility.historyVisibility?.shouldShareHistory() -> { + assertEquals("Session shouldn't have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId) + Log.v("#E2E TEST ROTATION", "Rotation is not needed") + } + initRoomHistoryVisibility.shouldShareHistory() != nextRoomHistoryVisibility.historyVisibility!!.shouldShareHistory() -> { + assertNotEquals("Session should have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId) + Log.v("#E2E TEST ROTATION", "Rotation is needed!") + } + } + + cryptoTestData.cleanUp(testHelper) + } + + private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? { + return testHelper.sendTextMessage(aliceRoomPOV, text, 1).firstOrNull()?.eventId + } + + private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + otherAccounts.map { + aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership + }.all { + it == Membership.JOIN + } + } + } + } + + private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID) + (roomSummary != null && roomSummary.membership == Membership.INVITE).also { + if (it) { + Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice") + } + } + } + } + + testHelper.runBlockingTest(60_000) { + Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") + try { + otherSession.roomService().joinRoom(e2eRoomID) + } catch (ex: JoinRoomFailure.JoinedWithTimeout) { + // it's ok we will wait after + } + } + + Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { + val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID) + roomSummary != null && roomSummary.membership == Membership.JOIN + } + } + } +} 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 e37ae5be86ffb8a2354a17aeb78244093c8f1d29..e8e7b1d708444b9589884837a559a8e0ca442e9f 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 @@ -72,7 +72,7 @@ class PreShareKeysTest : InstrumentedTest { assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice) assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId) - val megolmSessionId = bobInboundForAlice.olmInboundGroupSession!!.sessionIdentifier() + val megolmSessionId = bobInboundForAlice.session.sessionIdentifier() assertEquals("Wrong session", aliceOutboundSessionInRoom, megolmSessionId) 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 e8a474a54a5d9cd60a9eef8e2be89194571768d1..5fe737618411831e27d93b4baa6c465b4b11a8d6 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 @@ -21,7 +21,6 @@ import org.amshove.kluent.shouldBe import org.junit.Assert import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -60,7 +59,6 @@ import kotlin.coroutines.resume */ @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) -@Ignore class UnwedgingTest : InstrumentedTest { private lateinit var messagesReceivedByBob: List<TimelineEvent> 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 ae420a09b38e59414d3cc90dd223a62369cafc22..0aac4297e4265114ac8b405218e7fcf424b444f1 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 @@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import org.junit.Assert import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -47,7 +46,6 @@ import org.matrix.android.sdk.mustFail @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest -@Ignore class WithHeldTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt index 45fdb9e1e30f07b4e5b512243b850ede447d284a..cf201611a0c0aa02d591a99d72fab3c13d273a92 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt @@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestData -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper /** * Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword] */ internal data class KeysBackupScenarioData( val cryptoTestData: CryptoTestData, - val aliceKeys: List<OlmInboundGroupSessionWrapper2>, + val aliceKeys: List<MXInboundMegolmSessionWrapper>, val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, val aliceSession2: Session ) { 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 fb498e0de5dfde0e47a872738d7ea5fe32b7b111..2439119f01c8eb1ddcd0abd8cc91d103cf2903fe 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 @@ -24,7 +24,6 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -56,7 +55,6 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest -@Ignore class KeysBackupTest : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) @@ -301,7 +299,7 @@ class KeysBackupTest : InstrumentedTest { val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo // - Check encryptGroupSession() returns stg - val keyBackupData = keysBackup.encryptGroupSession(session) + val keyBackupData = testHelper.runBlockingTest { keysBackup.encryptGroupSession(session) } assertNotNull(keyBackupData) assertNotNull(keyBackupData!!.sessionData) @@ -312,7 +310,7 @@ class KeysBackupTest : InstrumentedTest { val sessionData = keysBackup .decryptKeyBackupData( keyBackupData, - session.olmInboundGroupSession!!.sessionIdentifier(), + session.safeSessionId!!, cryptoTestData.roomId, decryption!! ) 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 38f94c51039cb9a57c5af236c6f56233259b187a..2cc2b506b925322552f247fbb6f002c0e889fbbd 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 @@ -187,7 +187,7 @@ internal class KeysBackupTestHelper( // - Alice must have the same keys on both devices for (aliceKey1 in testData.aliceKeys) { val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store - .getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!) + .getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!) Assert.assertNotNull(aliceKey2) assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys()) } 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 3dbf206e089c12815ecf89dd0ce9c8ebab86e306..2e5c69b04874855432b3fb6f79f8c45cc12c5088 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 @@ -22,6 +22,7 @@ import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -52,6 +53,7 @@ class TimelineForwardPaginationTest : InstrumentedTest { * This test ensure that if we click to permalink, we will be able to go back to the live */ @Test + @Ignore("Ignoring this test until it's fixed since it blocks the CI.") fun forwardPaginationTest() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val numberOfMessagesToSend = 90 val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) 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 38136ff5cee79fd5576e8063884218fca948c4ce..2cd579df24dfec8056199cf1c48cc539656b6d3d 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 @@ -56,19 +56,17 @@ class SpaceCreationTest : InstrumentedTest { val roomName = "My Space" val topic = "A public space for test" var spaceId: String = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { spaceId = session.spaceService().createSpace(roomName, topic, null, true) - // wait a bit to let the summary update it self :/ - it.countDown() } - Thread.sleep(4_000) - val syncedSpace = session.spaceService().getSpace(spaceId) commonTestHelper.waitWithLatch { commonTestHelper.retryPeriodicallyWithLatch(it) { - syncedSpace?.asRoom()?.roomSummary()?.name != null + session.spaceService().getSpace(spaceId)?.asRoom()?.roomSummary()?.name != null } } + + val syncedSpace = session.spaceService().getSpace(spaceId) assertEquals("Room name should be set", roomName, syncedSpace?.asRoom()?.roomSummary()?.name) assertEquals("Room topic should be set", topic, syncedSpace?.asRoom()?.roomSummary()?.topic) // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set") 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 63ca963479f2c0edb135aac507a861d700d90bb4..18645fd6d9ce82cf5323a03a56db9d17140a56f2 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 @@ -20,7 +20,6 @@ import android.util.Log import androidx.lifecycle.Observer import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.FixMethodOrder import org.junit.Ignore @@ -62,47 +61,40 @@ class SpaceHierarchyTest : InstrumentedTest { val spaceName = "My Space" val topic = "A public space for test" var spaceId = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { spaceId = session.spaceService().createSpace(spaceName, topic, null, true) - it.countDown() } val syncedSpace = session.spaceService().getSpace(spaceId) var roomId = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" }) - it.countDown() } val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { syncedSpace!!.addChildren(roomId, viaServers, null, true) - it.countDown() } - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers) - it.countDown() } - Thread.sleep(9000) - - val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents - val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true } - - parents?.forEach { - Log.d("## TEST", "parent : $it") + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents + val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true } + parents?.forEach { + Log.d("## TEST", "parent : $it") + } + parents?.size == 1 && + parents.first().roomSummary?.name == spaceName && + canonicalParents?.size == 1 && + canonicalParents.first().roomSummary?.name == spaceName + } } - - assertNotNull(parents) - assertEquals(1, parents!!.size) - assertEquals(spaceName, parents.first().roomSummary?.name) - - assertNotNull(canonicalParents) - assertEquals(1, canonicalParents!!.size) - assertEquals(spaceName, canonicalParents.first().roomSummary?.name) } // @Test @@ -173,52 +165,55 @@ class SpaceHierarchyTest : InstrumentedTest { // } @Test - fun testFilteringBySpace() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> + fun testFilteringBySpace() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) + commonTestHelper, + session, "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) ) /* val spaceBInfo = */ createPublicSpace( - session, "SpaceB", listOf( - Triple("B1", true /*auto-join*/, true/*canonical*/), - Triple("B2", true, true), - Triple("B3", true, true) - ) + commonTestHelper, + session, "SpaceB", + listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) ) val spaceCInfo = createPublicSpace( - session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - ) + commonTestHelper, + session, "SpaceC", + listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + ) ) // add C as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { 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 { + commonTestHelper.runBlockingTest { orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" }) - it.countDown() } var orphan2 = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" }) - it.countDown() } val allRooms = session.roomService().getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) @@ -240,10 +235,9 @@ class SpaceHierarchyTest : InstrumentedTest { assertTrue("A1 should be a grand child of A", aChildren.any { it.name == "C2" }) // Add a non canonical child and check that it does not appear as orphan - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" }) spaceA!!.addChildren(a3, viaServers, null, false) - it.countDown() } Thread.sleep(6_000) @@ -255,37 +249,39 @@ class SpaceHierarchyTest : InstrumentedTest { @Test @Ignore("This test will be ignored until it is fixed") - fun testBreakCycle() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> + fun testBreakCycle() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) + commonTestHelper, + session, "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) ) val spaceCInfo = createPublicSpace( - session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - ) + commonTestHelper, + session, "SpaceC", + listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + ) ) // add C as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { 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 { + commonTestHelper.runBlockingTest { val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId) spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true) - it.countDown() } // A -> C -> A @@ -300,37 +296,46 @@ class SpaceHierarchyTest : InstrumentedTest { } @Test - fun testLiveFlatChildren() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> + fun testLiveFlatChildren() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) + commonTestHelper, + session, + "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) ) val spaceBInfo = createPublicSpace( - session, "SpaceB", listOf( - Triple("B1", true /*auto-join*/, true/*canonical*/), - Triple("B2", true, true), - Triple("B3", true, true) - ) + commonTestHelper, + session, + "SpaceB", + listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) ) // add B as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - runBlocking { + commonTestHelper.runBlockingTest { spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true) session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers) } val spaceCInfo = createPublicSpace( - session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - ) + commonTestHelper, + session, + "SpaceC", + listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + ) ) commonTestHelper.waitWithLatch { latch -> @@ -348,13 +353,13 @@ class SpaceHierarchyTest : InstrumentedTest { } } + flatAChildren.observeForever(childObserver) + // add C as subspace of B 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 - - flatAChildren.observeForever(childObserver) } // Test part one of the rooms @@ -374,10 +379,10 @@ class SpaceHierarchyTest : InstrumentedTest { } } - // part from b room - session.roomService().leaveRoom(bRoomId) // The room should have disapear from flat children flatAChildren.observeForever(childObserver) + // part from b room + session.roomService().leaveRoom(bRoomId) } commonTestHelper.signOutAndClose(session) } @@ -388,6 +393,7 @@ class SpaceHierarchyTest : InstrumentedTest { ) private fun createPublicSpace( + commonTestHelper: CommonTestHelper, session: Session, spaceName: String, childInfo: List<Triple<String, Boolean, Boolean?>> @@ -395,29 +401,27 @@ class SpaceHierarchyTest : InstrumentedTest { ): TestSpaceCreationResult { var spaceId = "" var roomIds: List<String> = emptyList() - runSessionTest(context()) { commonTestHelper -> - 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 = childInfo.map { entry -> - session.roomService().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) - } + commonTestHelper.runBlockingTest { + spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true) + val syncedSpace = session.spaceService().getSpace(spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + + roomIds = childInfo.map { entry -> + session.roomService().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) } private fun createPrivateSpace( + commonTestHelper: CommonTestHelper, session: Session, spaceName: String, childInfo: List<Triple<String, Boolean, Boolean?>> @@ -425,34 +429,31 @@ class SpaceHierarchyTest : InstrumentedTest { ): TestSpaceCreationResult { var spaceId = "" var roomIds: List<String> = emptyList() - runSessionTest(context()) { commonTestHelper -> - 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 - .homeServerCapabilitiesService() - .getHomeServerCapabilities() - session.roomService().createRoom(CreateRoomParams().apply { - name = entry.first - this.featurePreset = RestrictedRoomPreset( - homeServerCapabilities, - listOf( - RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) - ) - ) - }) - } - 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) + commonTestHelper.runBlockingTest { + 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 + .homeServerCapabilitiesService() + .getHomeServerCapabilities() + session.roomService().createRoom(CreateRoomParams().apply { + name = entry.first + this.featurePreset = RestrictedRoomPreset( + homeServerCapabilities, + listOf( + RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) + ) + ) + }) } + 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) @@ -463,25 +464,31 @@ class SpaceHierarchyTest : InstrumentedTest { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) /* val spaceAInfo = */ createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) + commonTestHelper, + session, "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) ) val spaceBInfo = createPublicSpace( - session, "SpaceB", listOf( - Triple("B1", true /*auto-join*/, true/*canonical*/), - Triple("B2", true, true), - Triple("B3", true, true) - ) + commonTestHelper, + session, "SpaceB", + listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) ) val spaceCInfo = createPublicSpace( - session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - ) + commonTestHelper, + session, "SpaceC", + listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + ) ) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") @@ -490,7 +497,6 @@ class SpaceHierarchyTest : InstrumentedTest { runBlocking { val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId) spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) - Thread.sleep(6_000) } // Thread.sleep(4_000) @@ -501,11 +507,12 @@ class SpaceHierarchyTest : InstrumentedTest { // + C // + c1, c2 - val rootSpaces = commonTestHelper.runBlockingTest { - session.spaceService().getRootSpaceSummaries() + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val rootSpaces = commonTestHelper.runBlockingTest { session.spaceService().getRootSpaceSummaries() } + rootSpaces.size == 2 + } } - - assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size) } @Test @@ -514,10 +521,12 @@ class SpaceHierarchyTest : InstrumentedTest { val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true)) val spaceAInfo = createPrivateSpace( - aliceSession, "Private Space A", listOf( - Triple("General", true /*suggested*/, true/*canonical*/), - Triple("Random", true, true) - ) + commonTestHelper, + aliceSession, "Private Space A", + listOf( + Triple("General", true /*suggested*/, true/*canonical*/), + Triple("Random", true, true) + ) ) commonTestHelper.runBlockingTest { @@ -529,10 +538,9 @@ class SpaceHierarchyTest : InstrumentedTest { } var bobRoomId = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" }) bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId) - it.countDown() } commonTestHelper.runBlockingTest { @@ -545,9 +553,8 @@ class SpaceHierarchyTest : InstrumentedTest { } } - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: "")) - it.countDown() } commonTestHelper.waitWithLatch { latch -> @@ -603,4 +610,82 @@ class SpaceHierarchyTest : InstrumentedTest { } } } + + @Test + fun testDirectParentNames() = runSessionTest(context()) { commonTestHelper -> + val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true)) + + val spaceAInfo = createPublicSpace( + commonTestHelper, + aliceSession, "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) + ) + + val spaceBInfo = createPublicSpace( + commonTestHelper, + aliceSession, "SpaceB", + listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) + ) + + // also add B1 in space A + + val B1roomId = spaceBInfo.roomIds.first() + val viaServers = listOf(aliceSession.sessionParams.homeServerHost ?: "") + + val spaceA = aliceSession.spaceService().getSpace(spaceAInfo.spaceId) + val spaceB = aliceSession.spaceService().getSpace(spaceBInfo.spaceId) + commonTestHelper.runBlockingTest { + spaceA!!.addChildren(B1roomId, viaServers, null, true) + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = aliceSession.getRoomSummary(B1roomId) + roomSummary != null && + roomSummary.directParentNames.size == 2 && + roomSummary.directParentNames.contains(spaceA!!.spaceSummary()!!.name) && + roomSummary.directParentNames.contains(spaceB!!.spaceSummary()!!.name) + } + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = aliceSession.getRoomSummary(spaceAInfo.roomIds.first()) + roomSummary != null && + roomSummary.directParentNames.size == 1 && + roomSummary.directParentNames.contains(spaceA!!.spaceSummary()!!.name) + } + } + + val newAName = "FooBar" + commonTestHelper.runBlockingTest { + spaceA!!.asRoom().stateService().updateName(newAName) + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = aliceSession.getRoomSummary(B1roomId) + roomSummary != null && + roomSummary.directParentNames.size == 2 && + roomSummary.directParentNames.contains(newAName) && + roomSummary.directParentNames.contains(spaceB!!.spaceSummary()!!.name) + } + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = aliceSession.getRoomSummary(spaceAInfo.roomIds.first()) + roomSummary != null && + roomSummary.directParentNames.size == 1 && + roomSummary.directParentNames.contains(newAName) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt index 82f39806c0392e6359496804106fede495ba17da..bae4b06a058a5667cf0032ddf79e9311fe65b870 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt @@ -156,6 +156,20 @@ object MatrixPatterns { return matrixId?.substringAfter(":", missingDelimiterValue = "")?.takeIf { it.isNotEmpty() } } + /** + * Extract user name from a matrix id. + * + * @param matrixId + * @return null if the input is not a valid matrixId + */ + fun extractUserNameFromId(matrixId: String): String? { + return if (isUserId(matrixId)) { + matrixId.removePrefix("@").substringBefore(":", missingDelimiterValue = "") + } else { + null + } + } + /** * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~), * or consist of more than 50 characters, are forbidden and the field should be ignored if received. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt new file mode 100644 index 0000000000000000000000000000000000000000..627a8256799d455b80a8afa75b9bc3485b334289 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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.auth + +enum class LoginType { + PASSWORD, + SSO, + UNSUPPORTED, + CUSTOM, + DIRECT, + UNKNOWN; + + companion object { + + fun fromName(name: String) = when (name) { + PASSWORD.name -> PASSWORD + SSO.name -> SSO + UNSUPPORTED.name -> UNSUPPORTED + CUSTOM.name -> CUSTOM + DIRECT.name -> DIRECT + else -> UNKNOWN + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt index e3815231d93dd04d2ac4f8422d90976fc07d54c7..de227631ed9d58f8e3e270f2e248ab0820438336 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.auth.data +import org.matrix.android.sdk.api.auth.LoginType + /** * This data class holds necessary data to open a session. * You don't have to manually instantiate it. @@ -34,7 +36,12 @@ data class SessionParams( /** * Set to false if the current token is not valid anymore. Application should not have to use this info. */ - val isTokenValid: Boolean + val isTokenValid: Boolean, + + /** + * The authentication method that was used to create the session. + */ + val loginType: LoginType, ) { /* * Shortcuts. Usually the application should only need to use these shortcuts diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt index 9507ddda65726c55a513db0bbd707b224389e242..015cb6a1a28bfc409aa7ee625aefd71447d70de0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt @@ -38,4 +38,5 @@ data class MXCryptoConfig constructor( * You can limit request only to your sessions by turning this setting to `true` */ val limitRoomKeyRequestsToMyDevices: Boolean = false, -) + + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index d3cc8fc8e48a94a394bebac28f15598a830edd5e..68b931b33c21f6a6652b72628eea1e36044aedb8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -62,7 +62,10 @@ fun Throwable.isUsernameInUse() = this is Failure.ServerError && error.code == MatrixError.M_USER_IN_USE fun Throwable.isInvalidUsername() = this is Failure.ServerError && - error.code == MatrixError.M_INVALID_USERNAME + (error.code == MatrixError.M_INVALID_USERNAME || usernameContainsNonAsciiCharacters()) + +private fun Failure.ServerError.usernameContainsNonAsciiCharacters() = error.code == MatrixError.M_UNKNOWN && + error.message == "Query parameter \'username\' must be ascii" fun Throwable.isInvalidPassword() = this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && @@ -86,6 +89,10 @@ fun Throwable.isInvalidUIAAuth() = this is Failure.ServerError && fun Throwable.isHomeserverUnavailable() = this is Failure.NetworkConnection && this.ioException is UnknownHostException +fun Throwable.isMissingEmailVerification() = this is Failure.ServerError && + error.code == MatrixError.M_UNAUTHORIZED && + error.message == "Unable to get validated threepid" + /** * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt index baf33a59c57775190fa0d480a8b4a5c17aa154a8..f5e5628566a4486d721e94750de51fd9bb164f20 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt @@ -162,7 +162,7 @@ enum class ApiPath(val path: String, val method: String) { KICK_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick", "POST"), REDACT_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}", "PUT"), REPORT_CONTENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}", "POST"), - GET_ALIASES(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases", "GET"), + GET_ALIASES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/aliases", "GET"), SEND_TYPING_STATE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}", "PUT"), PUT_TAG(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}", "PUT"), DELETE_TAG(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}", "DELETE"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/SpaceFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/SpaceFilter.kt index 6383412ffbdf714fa4b2ad8d8391508fc28b223f..ccefd5855fcb22e7eb27fd3f90133a317649aba7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/SpaceFilter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/SpaceFilter.kt @@ -35,9 +35,19 @@ sealed interface SpaceFilter { * Used to get all the rooms that do not have the provided space in their parent hierarchy. */ data class ExcludeSpace(val spaceId: String) : SpaceFilter + + /** + * Used to apply no filtering to the space. + */ + object NoFilter : SpaceFilter } /** * Return a [SpaceFilter.ActiveSpace] if the String is not null, or [SpaceFilter.OrphanRooms]. */ fun String?.toActiveSpaceOrOrphanRooms(): SpaceFilter = this?.let { SpaceFilter.ActiveSpace(it) } ?: SpaceFilter.OrphanRooms + +/** + * Return a [SpaceFilter.ActiveSpace] if the String is not null, or [SpaceFilter.NoFilter]. + */ +fun String?.toActiveSpaceOrNoFilter(): SpaceFilter = this?.let { SpaceFilter.ActiveSpace(it) } ?: SpaceFilter.NoFilter diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt index bd2a1078b244702923b86f1e54715005cba9cead..e701e0f3ba0c4505c73a9633a6bbf78515b84e7e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt @@ -180,11 +180,11 @@ class SecretStoringUtils @Inject constructor( is KeyStore.PrivateKeyEntry -> keyEntry.certificate.publicKey else -> throw IllegalStateException("Unknown KeyEntry type.") } - val cipherMode = when { + val cipherAlgorithm = when { buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> AES_MODE else -> RSA_MODE } - val cipher = Cipher.getInstance(cipherMode) + val cipher = Cipher.getInstance(cipherAlgorithm) cipher.init(Cipher.ENCRYPT_MODE, key) return cipher } @@ -204,13 +204,17 @@ class SecretStoringUtils @Inject constructor( .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setKeySize(128) + .setUserAuthenticationRequired(keyNeedsUserAuthentication) .apply { - setUserAuthenticationRequired(keyNeedsUserAuthentication) - if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.N) { - setInvalidatedByBiometricEnrollment(true) + if (keyNeedsUserAuthentication) { + buildVersionSdkIntProvider.whenAtLeast(Build.VERSION_CODES.N) { + setInvalidatedByBiometricEnrollment(true) + } + buildVersionSdkIntProvider.whenAtLeast(Build.VERSION_CODES.P) { + setUnlockedDeviceRequired(true) + } } } - .setUserAuthenticationRequired(keyNeedsUserAuthentication) .build() generator.init(keyGenSpec) return generator.generateKey() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 1b01239de5222f1b0deca3a3660c862ec987cf81..63c1c25130de509574070607bada8df944f3f667 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -33,7 +33,6 @@ import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.FileService -import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService @@ -154,11 +153,6 @@ interface Session { */ fun roomDirectoryService(): RoomDirectoryService - /** - * Returns the GroupService associated with the session. - */ - fun groupService(): GroupService - /** * Returns the UserService associated with the session. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 638da1180498472bb9e936248dcf045611c0a9b9..a5e05f69e07ec2c270151917b6ac05dd7a0ca0ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent +import org.matrix.android.sdk.internal.crypto.model.SessionInfo interface CryptoService { @@ -84,6 +85,20 @@ interface CryptoService { fun isKeyGossipingEnabled(): Boolean + /** + * As per MSC3061. + * If true will make it possible to share part of e2ee room history + * on invite depending on the room visibility setting. + */ + fun enableShareKeyOnInvite(enable: Boolean) + + /** + * As per MSC3061. + * If true will make it possible to share part of e2ee room history + * on invite depending on the room visibility setting. + */ + fun isShareKeysOnInviteEnabled(): Boolean + fun setRoomUnBlacklistUnverifiedDevices(roomId: String) fun getDeviceTrackingStatus(userId: String): Int @@ -176,4 +191,9 @@ interface CryptoService { * send, in order to speed up sending of the message. */ fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) + + /** + * Share all inbound sessions of the last chunk messages to the provided userId devices. + */ + suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt index 3df4ef7c9a068296184d464105896e1441cdfdd0..664cd00e94347efd7e875001ad14b904197f0159 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt @@ -69,5 +69,11 @@ data class ForwardedRoomKeyContent( * private part of this key unless they have done device verification. */ @Json(name = "sender_claimed_ed25519_key") - val senderClaimedEd25519Key: String? = null + val senderClaimedEd25519Key: String? = null, + + /** + * MSC3061 Identifies keys that were sent when the room's visibility setting was set to world_readable or shared. + */ + @Json(name = "org.matrix.msc3061.shared_history") + val sharedHistory: Boolean? = false, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 7f9ab4c6dd61faa8434ee462819570167dde7e09..59dc6c434d00bfd083f2ac905da2051fd4ebf359 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -202,7 +202,7 @@ data class Event( * It will return a decrypted text message or an empty string otherwise. */ fun getDecryptedTextSummary(): String? { - if (isRedacted()) return "Message Deleted" + if (isRedacted()) return "Message removed" val text = getDecryptedValue() ?: run { if (isPoll()) { return getPollQuestion() ?: "created a poll." @@ -371,6 +371,8 @@ fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClear fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER +fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO + fun Event.getRelationContent(): RelationDefaultContent? { return if (isEncrypted()) { content.toModel<EncryptedEventContent>()?.relatesTo 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 fa3a9f6acd6cd19cd1296f8056ff04bfbc97c125..8fdbba21c5fed6dd7fc7b0813a73372b2bebf4bf 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 @@ -87,7 +87,10 @@ object EventType { // Key share events const val ROOM_KEY_REQUEST = "m.room_key_request" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" - const val ROOM_KEY_WITHHELD = "org.matrix.room_key.withheld" + val ROOM_KEY_WITHHELD = StableUnstableId( + stable = "m.room_key.withheld", + unstable = "org.matrix.room_key.withheld" + ) const val REQUEST_SECRET = "m.secret.request" const val SEND_SECRET = "m.secret.send" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/StableUnstableId.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/StableUnstableId.kt new file mode 100644 index 0000000000000000000000000000000000000000..c68a9e47f970318725048536b2c934d63baa3807 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/StableUnstableId.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 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.events.model + +data class StableUnstableId( + val stable: String, + val unstable: String, +) { + val values = listOf(stable, unstable) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt index 0830a566ab86d6bc301a5f94004f93b3bf369819..5b18d29ea0a73cc0f164660bc74700fcedac055f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt @@ -38,5 +38,12 @@ data class RoomKeyContent( // should be a Long but it is sometimes a double @Json(name = "chain_index") - val chainIndex: Any? = null + val chainIndex: Any? = null, + + /** + * MSC3061 Identifies keys that were sent when the room's visibility setting was set to world_readable or shared. + */ + @Json(name = "org.matrix.msc3061.shared_history") + val sharedHistory: Boolean? = false + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/Group.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/Group.kt deleted file mode 100644 index 25c69e5025cf306c988d7fbcf24830f61c479388..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/Group.kt +++ /dev/null @@ -1,31 +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.group - -/** - * This interface defines methods to interact within a group. - */ -interface Group { - val groupId: String - - /** - * This methods allows you to refresh data about this group. It will be reflected on the GroupSummary. - * The SDK also takes care of refreshing group data every hour. - * @return a Cancelable to be able to cancel requests. - */ - suspend fun fetchGroupData() -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupService.kt deleted file mode 100644 index 1968af222aa1c6c44912302ad0561f8f3433b94a..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupService.kt +++ /dev/null @@ -1,51 +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.group - -import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.group.model.GroupSummary - -/** - * This interface defines methods to get groups. It's implemented at the session level. - */ -interface GroupService { - /** - * Get a group from a groupId. - * @param groupId the groupId to look for. - * @return the group with groupId or null - */ - fun getGroup(groupId: String): Group? - - /** - * Get a groupSummary from a groupId. - * @param groupId the groupId to look for. - * @return the groupSummary with groupId or null - */ - fun getGroupSummary(groupId: String): GroupSummary? - - /** - * Get a list of group summaries. This list is a snapshot of the data. - * @return the list of [GroupSummary] - */ - fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List<GroupSummary> - - /** - * Get a live list of group summaries. This list is refreshed as soon as the data changes. - * @return the [LiveData] of [GroupSummary] - */ - fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>> -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupSummaryQueryParams.kt deleted file mode 100644 index 5104b3ee5371a5b0e53c2a892f82e408e27dcaed..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupSummaryQueryParams.kt +++ /dev/null @@ -1,44 +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.group - -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.room.model.Membership - -fun groupSummaryQueryParams(init: (GroupSummaryQueryParams.Builder.() -> Unit) = {}): GroupSummaryQueryParams { - return GroupSummaryQueryParams.Builder().apply(init).build() -} - -/** - * This class can be used to filter group summaries. - */ -data class GroupSummaryQueryParams( - val displayName: QueryStringValue, - val memberships: List<Membership> -) { - - class Builder { - - var displayName: QueryStringValue = QueryStringValue.IsNotEmpty - var memberships: List<Membership> = Membership.all() - - fun build() = GroupSummaryQueryParams( - displayName = displayName, - memberships = memberships - ) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/model/GroupSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/model/GroupSummary.kt deleted file mode 100644 index ef50fce82ff3fd087a3cd37ad1eee6406fb82d3e..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/model/GroupSummary.kt +++ /dev/null @@ -1,33 +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.group.model - -import org.matrix.android.sdk.api.session.room.model.Membership - -/** - * This class holds some data of a group. - * It can be retrieved through [org.matrix.android.sdk.api.session.group.GroupService] - */ -data class GroupSummary( - val groupId: String, - val membership: Membership, - val displayName: String = "", - val shortDescription: String = "", - val avatarUrl: String = "", - val roomIds: List<String> = emptyList(), - val userIds: List<String> = emptyList() -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt index e8d9c89b547539423f7c8ea9bf48d7726d7b24ea..fc46c9211762fc9c964d8412d06ff75c0396f954 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt @@ -54,7 +54,5 @@ sealed class PermalinkData { data class UserLink(val userId: String) : PermalinkData() - data class GroupLink(val groupId: String) : PermalinkData() - - data class FallbackLink(val uri: Uri) : PermalinkData() + data class FallbackLink(val uri: Uri, val isLegacyGroupLink: Boolean = false) : PermalinkData() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt index 0168b7ac3ab6e9679916da9ecf416a0fdf5205f4..3dccc3fbf2228e02081c485846a486d9a268d203 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt @@ -61,27 +61,29 @@ object PermalinkParser { val params = safeFragment .split(MatrixPatterns.SEP_REGEX) .filter { it.isNotEmpty() } - .map { URLDecoder.decode(it, "UTF-8") } .take(2) + val decodedParams = params + .map { URLDecoder.decode(it, "UTF-8") } + val identifier = params.getOrNull(0) - val extraParameter = params.getOrNull(1) + val decodedIdentifier = decodedParams.getOrNull(0) + val extraParameter = decodedParams.getOrNull(1) return when { - identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) - MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier) - MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier) - MatrixPatterns.isRoomId(identifier) -> { - handleRoomIdCase(fragment, identifier, matrixToUri, extraParameter, viaQueryParameters) + identifier.isNullOrEmpty() || decodedIdentifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) + MatrixPatterns.isUserId(decodedIdentifier) -> PermalinkData.UserLink(userId = decodedIdentifier) + MatrixPatterns.isRoomId(decodedIdentifier) -> { + handleRoomIdCase(fragment, decodedIdentifier, matrixToUri, extraParameter, viaQueryParameters) } - MatrixPatterns.isRoomAlias(identifier) -> { + MatrixPatterns.isRoomAlias(decodedIdentifier) -> { PermalinkData.RoomLink( - roomIdOrAlias = identifier, + roomIdOrAlias = decodedIdentifier, isRoomAlias = true, eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, viaParameters = viaQueryParameters ) } - else -> PermalinkData.FallbackLink(uri) + else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 5dfb8961e3ce0fb5a6630c025cde96ca49a64500..ad8106c9c10dd8a179d1e9d2aa86f02def0417a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -40,6 +40,18 @@ interface RoomService { */ suspend fun createRoom(createRoomParams: CreateRoomParams): String + /** + * Create a room locally. + * This room will not be synchronized with the server and will not come back from the sync, so all the events related to this room will be generated + * locally. + */ + suspend fun createLocalRoom(createRoomParams: CreateRoomParams): String + + /** + * Delete a local room with all its related events. + */ + suspend fun deleteLocalRoom(roomId: String) + /** * Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters. */ @@ -231,14 +243,11 @@ interface RoomService { * @param queryParams The filter to use * @param pagedListConfig The paged list configuration (page size, initial load, prefetch distance...) * @param sortOrder defines how to sort the results - * @param getFlattenParents When true, the list of known parents and grand parents summaries will be resolved. - * This can have significant impact on performance, better be used only on manageable list (filtered by displayName, ..). */ fun getFilteredPagedRoomSummariesLive( queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config = defaultPagedListConfig, sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY, - getFlattenParents: Boolean = false, ): UpdatableLivePageResult /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index 3d943473e4ebb34aa161bf5847be9cda0088da1c..60963ef25a4da2e1a530cbb16bd24c34699c3456 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -86,11 +86,7 @@ data class RoomSummaryQueryParams( /** * Used to filter room using the current space. */ - val spaceFilter: SpaceFilter?, - /** - * Used to filter room using the current group. - */ - val activeGroupId: String? = null + val spaceFilter: SpaceFilter, ) { /** @@ -105,8 +101,7 @@ data class RoomSummaryQueryParams( var roomTagQueryFilter: RoomTagQueryFilter? = null var excludeType: List<String?>? = listOf(RoomType.SPACE) var includeType: List<String?>? = null - var spaceFilter: SpaceFilter? = null - var activeGroupId: String? = null + var spaceFilter: SpaceFilter = SpaceFilter.NoFilter fun build() = RoomSummaryQueryParams( displayName = displayName, @@ -117,7 +112,6 @@ data class RoomSummaryQueryParams( excludeType = excludeType, includeType = includeType, spaceFilter = spaceFilter, - activeGroupId = activeGroupId ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt index ada3dc85d78a66291a9cd7e89af6fb66335ad30c..cd8acbccccb3eeecc82f5d9fddd62c4ef783c68b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.room.location -import androidx.annotation.MainThread import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.util.Cancelable @@ -48,9 +47,10 @@ interface LocationSharingService { /** * Starts sharing live location in the room. * @param timeoutMillis timeout of the live in milliseconds + * @param description description of the live for text fallback * @return the result of the update of the live */ - suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult + suspend fun startLiveLocationShare(timeoutMillis: Long, description: String): UpdateLiveLocationShareResult /** * Stops sharing live location in the room. @@ -58,16 +58,21 @@ interface LocationSharingService { */ suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult + /** + * Redact (delete) the live associated to the given beacon info event id. + * @param beaconInfoEventId event id of the initial beacon info state event + * @param reason Optional reason string + */ + suspend fun redactLiveLocationShare(beaconInfoEventId: String, reason: String?) + /** * Returns a LiveData on the list of current running live location shares. */ - @MainThread fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> /** * Returns a LiveData on the live location share summary with the given eventId. * @param beaconInfoEventId event id of the initial beacon info state event */ - @MainThread fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt index e7ac69be74a5f284721b230ae5580d3234465c9c..144cfeb3b81b6dd57bc50313f9c55efb45ee8cd2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt @@ -30,6 +30,20 @@ interface MembershipService { */ suspend fun loadRoomMembersIfNeeded() + /** + * All the room members can be not loaded, for instance after an initial sync. + * All the members will be loaded when calling [loadRoomMembersIfNeeded], or when sending an encrypted + * event to the room. + * The fun let the app know if all the members have been loaded for this room. + * @return true if all the members are loaded, or false elsewhere. + */ + suspend fun areAllMembersLoaded(): Boolean + + /** + * Live version for [areAllMembersLoaded]. + */ + fun areAllMembersLoadedLive(): LiveData<Boolean> + /** * Return the roomMember with userId or null. * @param userId the userId param to look for diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt index 06069f264620619b0149a92da44b7adb1f7aef22..2b0ea1d8fbcae85b219881de691a14186da248be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt @@ -48,3 +48,9 @@ enum class RoomHistoryVisibility { */ @Json(name = "joined") JOINED } + +/** + * Room history should be shared only if room visibility is world_readable or shared. + */ +internal fun RoomHistoryVisibility.shouldShareHistory() = + this == RoomHistoryVisibility.WORLD_READABLE || this == RoomHistoryVisibility.SHARED diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index 1ab23b7a11953b64a104a283c505aa6a1695ed48..ff4977491fc8524a35fdfc0d1b6dece901fdb1de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -164,9 +164,9 @@ data class RoomSummary( */ val spaceChildren: List<SpaceChildInfo>? = null, /** - * List of all the space parents. Will be empty by default, you have to explicitly request it. + * The names of the room's direct space parents if any. */ - val flattenParents: List<RoomSummary> = emptyList(), + val directParentNames: List<String> = emptyList(), /** * List of all the space parent Ids. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/VersioningState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/VersioningState.kt index 2e1668ebbba7f8a7dad45d9c0073ec8d6e0a6823..8cfe3da03187a29220a3cf2696193997d2540865 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/VersioningState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/VersioningState.kt @@ -33,5 +33,7 @@ enum class VersioningState { /** * The room has been upgraded, and the new room has been joined. */ - UPGRADED_ROOM_JOINED, + UPGRADED_ROOM_JOINED; + + fun isUpgraded() = this != NONE } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupRooms.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt similarity index 58% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupRooms.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt index 9e5d18225b3f06659748755d6272c1b56357bfdd..7ef0d63924ddba419eeb183a28871108c5f2fc28 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupRooms.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2022 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,15 +14,18 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.group.model +package org.matrix.android.sdk.api.session.room.model.localecho -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass +import java.util.UUID -@JsonClass(generateAdapter = true) -internal data class GroupRooms( +object RoomLocalEcho { - @Json(name = "total_room_count_estimate") val totalRoomCountEstimate: Int? = null, - @Json(name = "chunk") val rooms: List<GroupRoom> = emptyList() + private const val PREFIX = "!local." -) + /** + * Tell whether the provider room id is a local id. + */ + fun isLocalEchoId(roomId: String) = roomId.startsWith(PREFIX) + + internal fun createLocalEchoId() = "${PREFIX}${UUID.randomUUID()}" +} 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 9d8c8a13bd7aafaa44df96aff0c645550ba3fd40..d391abf1e61528cc3768335b0d36aab8a550489b 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 @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType 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.isEdition +import org.matrix.android.sdk.api.session.events.model.isLiveLocation import org.matrix.android.sdk.api.session.events.model.isPoll import org.matrix.android.sdk.api.session.events.model.isReply import org.matrix.android.sdk.api.session.events.model.isSticker @@ -165,6 +166,10 @@ fun TimelineEvent.isSticker(): Boolean { return root.isSticker() } +fun TimelineEvent.isLiveLocation(): Boolean { + return root.isLiveLocation() +} + /** * Returns whether or not the event is a root thread event. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStep.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStep.kt index 407585b003c448b3eb1b246d11df009982b37efd..c4a3638ac495cf775500d6642e443680bd8763ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStep.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStep.kt @@ -22,7 +22,6 @@ enum class InitialSyncStep { ImportingAccount, ImportingAccountCrypto, ImportingAccountRoom, - ImportingAccountGroups, ImportingAccountData, ImportingAccountJoinedRooms, ImportingAccountInvitedRooms, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt index 5b2bf651af891090d6776600d19dc16ddd29282a..71f7ab8494b6be34bd67c0e4629e6667b58a1675 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt @@ -60,9 +60,9 @@ interface SyncService { fun getSyncStateLive(): LiveData<SyncState> /** - * Get the [SyncRequestState] as a LiveData. + * Get the [SyncRequestState] as a SharedFlow. */ - fun getSyncRequestStateLive(): LiveData<SyncRequestState> + fun getSyncRequestStateFlow(): SharedFlow<SyncRequestState> /** * This method returns a flow of SyncResponse. New value will be pushed through the sync thread. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/GroupSyncProfile.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/GroupSyncProfile.kt deleted file mode 100644 index 581e6824ee5a41db133ba961aed381b5e10322a4..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/GroupSyncProfile.kt +++ /dev/null @@ -1,33 +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.sync.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class GroupSyncProfile( - /** - * The name of the group, if any. May be nil. - */ - @Json(name = "name") val name: String? = null, - - /** - * The URL for the group's avatar. May be nil. - */ - @Json(name = "avatar_url") val avatarUrl: String? = null -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/GroupsSyncResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/GroupsSyncResponse.kt deleted file mode 100644 index fd8710bbda4f4524a0d7e8dc27aa22b87c448f8a..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/GroupsSyncResponse.kt +++ /dev/null @@ -1,38 +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.sync.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class GroupsSyncResponse( - /** - * Joined groups: An array of groups ids. - */ - @Json(name = "join") val join: Map<String, Any> = emptyMap(), - - /** - * Invitations. The groups that the user has been invited to: keys are groups ids. - */ - @Json(name = "invite") val invite: Map<String, InvitedGroupSync> = emptyMap(), - - /** - * Left groups. An array of groups ids: the groups that the user has left or been banned from. - */ - @Json(name = "leave") val leave: Map<String, Any> = emptyMap() -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/InvitedGroupSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/InvitedGroupSync.kt deleted file mode 100644 index d41df9f0f62b67030c82ff2519494bc02c95ff89..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/InvitedGroupSync.kt +++ /dev/null @@ -1,33 +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.sync.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class InvitedGroupSync( - /** - * The identifier of the inviter. - */ - @Json(name = "inviter") val inviter: String? = null, - - /** - * The group profile. - */ - @Json(name = "profile") val profile: GroupSyncProfile? = null -) 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 c70964a513e3e5c361e4b2571a0cb37689657ee5..382d8a1740421f0ed9e07c8ec4d13c6ed67f793a 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 @@ -65,10 +65,4 @@ data class SyncResponse( */ @Json(name = "org.matrix.msc2732.device_unused_fallback_key_types") val deviceUnusedFallbackKeyTypes: List<String>? = null, - - /** - * List of groups. - */ - @Json(name = "groups") val groups: GroupsSyncResponse? = null - ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt index ee098f9bf2e064ad9f71d6a9959d7a3bf8c28b8e..f02fe4f9de82a9f0c279cf8e270203fd46b7d22f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/model/WidgetType.kt @@ -28,7 +28,8 @@ private val DEFINED_TYPES by lazy { WidgetType.StickerPicker, WidgetType.Grafana, WidgetType.Custom, - WidgetType.IntegrationManager + WidgetType.IntegrationManager, + WidgetType.ElementCall, ) } @@ -47,6 +48,7 @@ sealed class WidgetType(open val preferred: String, open val legacy: String = pr object Grafana : WidgetType("m.grafana") object Custom : WidgetType("m.custom") object IntegrationManager : WidgetType("m.integration_manager") + object ElementCall : WidgetType("io.element.call") data class Fallback(override val preferred: String) : WidgetType(preferred) fun matches(type: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt index b7ea187ec508b7453f821973187eea8aa0b0e7e5..900a2e237f175da98dfd44f7ccae9745c54e0a10 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt @@ -21,4 +21,14 @@ interface BuildVersionSdkIntProvider { * Return the current version of the Android SDK. */ fun get(): Int + + /** + * Checks the if the current OS version is equal or greater than [version]. + * @return A `non-null` result if true, `null` otherwise. + */ + fun <T> whenAtLeast(version: Int, result: () -> T): T? { + return if (get() >= version) { + result() + } else null + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index 26dd31dc2d4f214a12bbd9ef40121abfff635dee..974f1cfcbefa793fb0c6cfd675b6b0c842d3dde5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.util import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType @@ -113,19 +112,6 @@ sealed class MatrixItem( override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } - data class GroupItem( - override val id: String, - override val displayName: String? = null, - override val avatarUrl: String? = null - ) : - MatrixItem(id, displayName, avatarUrl) { - init { - if (BuildConfig.DEBUG) checkId() - } - - override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) - } - protected fun checkId() { if (!id.startsWith(getIdPrefix())) { error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}") @@ -144,7 +130,6 @@ sealed class MatrixItem( is RoomItem, is EveryoneInRoomItem -> '!' is RoomAliasItem -> '#' - is GroupItem -> '+' } fun firstLetterOfDisplayName(): String { @@ -196,8 +181,6 @@ sealed class MatrixItem( fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) -fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl) - fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) { MatrixItem.SpaceItem(roomId, displayName, avatarUrl) } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt index ef47775f1bec34033b2e42ab9207ed67de510c06..5ec0dedadf51b32352def7c4f146277e5f09bcae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt @@ -33,9 +33,15 @@ object MimeTypes { const val Ogg = "audio/ogg" + const val PlainText = "text/plain" + fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse() fun String?.isMimeTypeVideo() = this?.startsWith("video/").orFalse() fun String?.isMimeTypeAudio() = this?.startsWith("audio/").orFalse() + fun String?.isMimeTypeApplication() = this?.startsWith("application/").orFalse() + fun String?.isMimeTypeFile() = this?.startsWith("file/").orFalse() + fun String?.isMimeTypeText() = this?.startsWith("text/").orFalse() + fun String?.isMimeTypeAny() = this?.startsWith("*/").orFalse() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt index 83c858594167aa658d166b268c1830dc76fc4152..1de0a360342eb536522654d07fa9f52bc23d409a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt @@ -76,7 +76,7 @@ object StringOrderUtils { } fun stringToBase(x: String, alphabet: CharArray): BigInteger { - if (x.isEmpty()) throw IllegalArgumentException() + require(x.isNotEmpty()) val len = alphabet.size.toBigInteger() var result = BigInteger("0") x.reversed().forEachIndexed { index, c -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt index ddb70be906b15b8e84d872eab2ab9edc9fb6ef23..463692e574cf767a9837150a643c9ffa9094e4fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt @@ -83,6 +83,9 @@ internal abstract class AuthModule { @Binds abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator + @Binds + abstract fun bindSessionParamsCreator(creator: DefaultSessionParamsCreator): SessionParamsCreator + @Binds abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 9d6b018a672cc113b3cf5e92820ba9d41b3f92b0..446f9318479fc16d0898f84d23b6c034b68645e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -22,6 +22,7 @@ import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.LoginType import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowResult @@ -361,7 +362,7 @@ internal class DefaultAuthenticationService @Inject constructor( homeServerConnectionConfig: HomeServerConnectionConfig, credentials: Credentials ): Session { - return sessionCreator.createSession(credentials, homeServerConnectionConfig) + return sessionCreator.createSession(credentials, homeServerConnectionConfig, LoginType.SSO) } override suspend fun getWellKnownData( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt index ba01146a4a7e450e18bf6f62f63193f6fe679110..7dbb11c7fd04d8b8aaa51cf6ab2f2d2f785802fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt @@ -16,69 +16,41 @@ package org.matrix.android.sdk.internal.auth -import android.net.Uri +import org.matrix.android.sdk.api.auth.LoginType import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.auth.data.SessionParams -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.SessionManager -import timber.log.Timber import javax.inject.Inject internal interface SessionCreator { - suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session + + suspend fun createSession( + credentials: Credentials, + homeServerConnectionConfig: HomeServerConnectionConfig, + loginType: LoginType, + ): Session } internal class DefaultSessionCreator @Inject constructor( private val sessionParamsStore: SessionParamsStore, private val sessionManager: SessionManager, private val pendingSessionStore: PendingSessionStore, - private val isValidClientServerApiTask: IsValidClientServerApiTask + private val sessionParamsCreator: SessionParamsCreator, ) : SessionCreator { /** * Credentials can affect the homeServerConnectionConfig, override homeserver url and/or * identity server url if provided in the credentials. */ - override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session { + override suspend fun createSession( + credentials: Credentials, + homeServerConnectionConfig: HomeServerConnectionConfig, + loginType: LoginType, + ): Session { // We can cleanup the pending session params pendingSessionStore.delete() - - val overriddenUrl = credentials.discoveryInformation?.homeServer?.baseURL - // remove trailing "/" - ?.trim { it == '/' } - ?.takeIf { it.isNotBlank() } - // It can be the same value, so in this case, do not check again the validity - ?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() } - ?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") } - ?.let { Uri.parse(it) } - ?.takeIf { - // Validate the URL, if the configuration is wrong server side, do not override - tryOrNull { - isValidClientServerApiTask.execute( - IsValidClientServerApiTask.Params( - homeServerConnectionConfig.copy(homeServerUriBase = it) - ) - ) - .also { Timber.d("Overriding homeserver url: $it") } - } ?: true // In case of other error (no network, etc.), consider it is valid... - } - - val sessionParams = SessionParams( - credentials = credentials, - homeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase, - identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL - // remove trailing "/" - ?.trim { it == '/' } - ?.takeIf { it.isNotBlank() } - ?.also { Timber.d("Overriding identity server url to $it") } - ?.let { Uri.parse(it) } - ?: homeServerConnectionConfig.identityServerUri - ), - isTokenValid = true) - + val sessionParams = sessionParamsCreator.create(credentials, homeServerConnectionConfig, loginType) sessionParamsStore.save(sessionParams) return sessionManager.getOrCreateSession(sessionParams) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionParamsCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionParamsCreator.kt new file mode 100644 index 0000000000000000000000000000000000000000..31ed9a1e85fafaa585b3ae355db0d454457f75a5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionParamsCreator.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth + +import android.net.Uri +import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.api.extensions.tryOrNull +import timber.log.Timber +import javax.inject.Inject + +internal interface SessionParamsCreator { + + suspend fun create( + credentials: Credentials, + homeServerConnectionConfig: HomeServerConnectionConfig, + loginType: LoginType, + ): SessionParams +} + +internal class DefaultSessionParamsCreator @Inject constructor( + private val isValidClientServerApiTask: IsValidClientServerApiTask +) : SessionParamsCreator { + + override suspend fun create( + credentials: Credentials, + homeServerConnectionConfig: HomeServerConnectionConfig, + loginType: LoginType, + ) = SessionParams( + credentials = credentials, + homeServerConnectionConfig = homeServerConnectionConfig.overrideWithCredentials(credentials), + isTokenValid = true, + loginType = loginType, + ) + + private suspend fun HomeServerConnectionConfig.overrideWithCredentials(credentials: Credentials) = copy( + homeServerUriBase = credentials.getHomeServerUri(this) ?: homeServerUriBase, + identityServerUri = credentials.getIdentityServerUri() ?: identityServerUri + ) + + private suspend fun Credentials.getHomeServerUri(homeServerConnectionConfig: HomeServerConnectionConfig) = + discoveryInformation?.homeServer?.baseURL + ?.trim { it == '/' } + ?.takeIf { it.isNotBlank() } + // It can be the same value, so in this case, do not check again the validity + ?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() } + ?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") } + ?.let { Uri.parse(it) } + ?.takeIf { validateUri(it, homeServerConnectionConfig) } + + private suspend fun validateUri(uri: Uri, homeServerConnectionConfig: HomeServerConnectionConfig) = + // Validate the URL, if the configuration is wrong server side, do not override + tryOrNull { + performClientServerApiValidation(uri, homeServerConnectionConfig) + } ?: true // In case of other error (no network, etc.), consider it is valid... + + private suspend fun performClientServerApiValidation(uri: Uri, homeServerConnectionConfig: HomeServerConnectionConfig) = + isValidClientServerApiTask.execute( + IsValidClientServerApiTask.Params(homeServerConnectionConfig.copy(homeServerUriBase = uri)) + ).also { Timber.d("Overriding homeserver url: $it") } + + private fun Credentials.getIdentityServerUri() = discoveryInformation?.identityServer?.baseURL + ?.trim { it == '/' } + ?.takeIf { it.isNotBlank() } + ?.also { Timber.d("Overriding identity server url to $it") } + ?.let { Uri.parse(it) } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt index 88c6d04ee67f6f50c222241b6eb209c63be74c3c..c5b8eae3ff07f5c788b21363e71787db7b55c558 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt @@ -17,15 +17,18 @@ package org.matrix.android.sdk.internal.auth.db import io.realm.DynamicRealm -import io.realm.RealmMigration import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo001 import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo002 import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo003 import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo004 -import timber.log.Timber +import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo005 +import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject -internal class AuthRealmMigration @Inject constructor() : RealmMigration { +internal class AuthRealmMigration @Inject constructor() : MatrixRealmMigration( + dbName = "Auth", + schemaVersion = 5L, +) { /** * Forces all AuthRealmMigration instances to be equal. * Avoids Realm throwing when multiple instances of the migration are set. @@ -33,14 +36,11 @@ internal class AuthRealmMigration @Inject constructor() : RealmMigration { override fun equals(other: Any?) = other is AuthRealmMigration override fun hashCode() = 4000 - val schemaVersion = 4L - - override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { - Timber.d("Migrating Auth Realm from $oldVersion to $newVersion") - + override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { if (oldVersion < 1) MigrateAuthTo001(realm).perform() if (oldVersion < 2) MigrateAuthTo002(realm).perform() if (oldVersion < 3) MigrateAuthTo003(realm).perform() if (oldVersion < 4) MigrateAuthTo004(realm).perform() + if (oldVersion < 5) MigrateAuthTo005(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsEntity.kt index ba1ab8147b766a06b64425305703b4a1645b2ff0..f6c883cac0e97287db9d97e0b8edf3133f8e6b21 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsEntity.kt @@ -26,5 +26,6 @@ internal open class SessionParamsEntity( var homeServerConnectionConfigJson: String = "", // Set to false when the token is invalid and the user has been soft logged out // In case of hard logout, this object is deleted from DB - var isTokenValid: Boolean = true + var isTokenValid: Boolean = true, + var loginType: String = "", ) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt index 86929b1afe7f7a9f93ff25b316139eedfd2773e9..23923bf2677001357ab24c47179135736bc8c6ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.auth.db import com.squareup.moshi.Moshi +import org.matrix.android.sdk.api.auth.LoginType import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SessionParams @@ -37,7 +38,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { if (credentials == null || homeServerConnectionConfig == null) { return null } - return SessionParams(credentials, homeServerConnectionConfig, entity.isTokenValid) + return SessionParams(credentials, homeServerConnectionConfig, entity.isTokenValid, LoginType.fromName(entity.loginType)) } fun map(sessionParams: SessionParams?): SessionParamsEntity? { @@ -54,7 +55,8 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { sessionParams.userId, credentialsJson, homeServerConnectionConfigJson, - sessionParams.isTokenValid + sessionParams.isTokenValid, + sessionParams.loginType.name, ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo005.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo005.kt new file mode 100644 index 0000000000000000000000000000000000000000..2cf1b62a4c4a9a2be519df32105f563262b3976b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo005.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.db.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator +import timber.log.Timber + +internal class MigrateAuthTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) { + + override fun doMigrate(realm: DynamicRealm) { + Timber.d("Update SessionParamsEntity to add LoginType") + + realm.schema.get("SessionParamsEntity") + ?.addField(SessionParamsEntityFields.LOGIN_TYPE, String::class.java) + ?.setRequired(SessionParamsEntityFields.LOGIN_TYPE, true) + ?.transform { it.set(SessionParamsEntityFields.LOGIN_TYPE, LoginType.UNKNOWN.name) } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt index 656a4f671bd46488bedd98cebaca32363a8f3fca..468e998407cac4e38d06ebf256221053cf0e80fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.auth.login import android.util.Patterns +import org.matrix.android.sdk.api.auth.LoginType import org.matrix.android.sdk.api.auth.login.LoginProfileInfo import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegisterThreePid @@ -78,7 +79,7 @@ internal class DefaultLoginWizard( authAPI.login(loginParams) } - return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) + return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, LoginType.PASSWORD) } /** @@ -92,7 +93,7 @@ internal class DefaultLoginWizard( authAPI.login(loginParams) } - return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) + return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, LoginType.SSO) } override suspend fun loginCustom(data: JsonDict): Session { @@ -100,7 +101,7 @@ internal class DefaultLoginWizard( authAPI.login(data) } - return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) + return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, LoginType.CUSTOM) } override suspend fun resetPassword(email: String) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt index c9311867c8c6bffee491b9ae835fdc5a4b586cca..af421057561fa0a6a990cfcd57fd33c2335373a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth.login import dagger.Lazy import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.auth.LoginType import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session @@ -77,7 +78,7 @@ internal class DefaultDirectLoginTask @Inject constructor( } } - return sessionCreator.createSession(credentials, params.homeServerConnectionConfig) + return sessionCreator.createSession(credentials, params.homeServerConnectionConfig, LoginType.DIRECT) } private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt index d6ec0297b4a88e900805f0ec15e7c0c8466305a3..46ebbb7b7171f7881b1b87e300b58124e74d8017 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.auth.registration import kotlinx.coroutines.delay +import org.matrix.android.sdk.api.auth.LoginType import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegisterThreePid @@ -64,7 +65,7 @@ internal class DefaultRegistrationWizard( override suspend fun getRegistrationFlow(): RegistrationResult { val params = RegistrationParams() - return performRegistrationRequest(params) + return performRegistrationRequest(params, LoginType.PASSWORD) } override suspend fun createAccount( @@ -77,7 +78,7 @@ internal class DefaultRegistrationWizard( password = password, initialDeviceDisplayName = initialDeviceDisplayName ) - return performRegistrationRequest(params) + return performRegistrationRequest(params, LoginType.PASSWORD) .also { pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true) .also { pendingSessionStore.savePendingSessionData(it) } @@ -89,7 +90,7 @@ internal class DefaultRegistrationWizard( ?: throw IllegalStateException("developer error, call createAccount() method first") val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response)) - return performRegistrationRequest(params) + return performRegistrationRequest(params, LoginType.PASSWORD) } override suspend fun acceptTerms(): RegistrationResult { @@ -97,7 +98,7 @@ internal class DefaultRegistrationWizard( ?: throw IllegalStateException("developer error, call createAccount() method first") val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession)) - return performRegistrationRequest(params) + return performRegistrationRequest(params, LoginType.PASSWORD) } override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult { @@ -151,14 +152,14 @@ internal class DefaultRegistrationWizard( .also { pendingSessionStore.savePendingSessionData(it) } // and send the sid a first time - return performRegistrationRequest(params) + return performRegistrationRequest(params, LoginType.PASSWORD) } override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult { val safeParam = pendingSessionData.currentThreePidData?.registrationParams ?: throw IllegalStateException("developer error, no pending three pid") - return performRegistrationRequest(safeParam, delayMillis) + return performRegistrationRequest(safeParam, LoginType.PASSWORD, delayMillis) } override suspend fun handleValidateThreePid(code: String): RegistrationResult { @@ -179,7 +180,7 @@ internal class DefaultRegistrationWizard( if (validationResponse.isSuccess()) { // The entered code is correct // Same than validate email - return performRegistrationRequest(registrationParams, 3_000) + return performRegistrationRequest(registrationParams, LoginType.PASSWORD, 3_000) } else { // The code is not correct throw Failure.SuccessError @@ -191,7 +192,7 @@ internal class DefaultRegistrationWizard( ?: throw IllegalStateException("developer error, call createAccount() method first") val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession)) - return performRegistrationRequest(params) + return performRegistrationRequest(params, LoginType.PASSWORD) } override suspend fun registrationCustom( @@ -204,25 +205,28 @@ internal class DefaultRegistrationWizard( mutableParams["session"] = safeSession val params = RegistrationCustomParams(auth = mutableParams) - return performRegistrationOtherRequest(params) + return performRegistrationOtherRequest(LoginType.CUSTOM, params) } private suspend fun performRegistrationRequest( registrationParams: RegistrationParams, + loginType: LoginType, delayMillis: Long = 0 ): RegistrationResult { delay(delayMillis) - return register { registerTask.execute(RegisterTask.Params(registrationParams)) } + return register(loginType) { registerTask.execute(RegisterTask.Params(registrationParams)) } } private suspend fun performRegistrationOtherRequest( - registrationCustomParams: RegistrationCustomParams + loginType: LoginType, + registrationCustomParams: RegistrationCustomParams, ): RegistrationResult { - return register { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) } + return register(loginType) { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) } } private suspend fun register( - execute: suspend () -> Credentials + loginType: LoginType, + execute: suspend () -> Credentials, ): RegistrationResult { val credentials = try { execute.invoke() @@ -237,8 +241,7 @@ internal class DefaultRegistrationWizard( } } - val session = - sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) + val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, loginType) return RegistrationResult.Success(session) } 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 e0bcde2296369032d1140b01a51e0cfa831f1a55..35c066dea897ec316294d960b04f4f10b3423fdf 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 @@ -71,6 +71,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.shouldShareHistory import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction @@ -81,6 +82,7 @@ import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFact import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE +import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -818,7 +820,7 @@ internal class DefaultCryptoService @Inject constructor( EventType.SEND_SECRET -> { onSecretSendReceived(event) } - EventType.ROOM_KEY_WITHHELD -> { + in EventType.ROOM_KEY_WITHHELD.values -> { onKeyWithHeldReceived(event) } else -> { @@ -867,7 +869,7 @@ internal class DefaultCryptoService @Inject constructor( senderKey = withHeldContent.senderKey, fromDevice = withHeldContent.fromDevice, event = Event( - type = EventType.ROOM_KEY_WITHHELD, + type = EventType.ROOM_KEY_WITHHELD.stable, senderId = senderId, content = event.getClearContent() ) @@ -963,8 +965,12 @@ internal class DefaultCryptoService @Inject constructor( private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { if (!event.isStateEvent()) return val eventContent = event.content.toModel<RoomHistoryVisibilityContent>() - eventContent?.historyVisibility?.let { - cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED) + val historyVisibility = eventContent?.historyVisibility + if (historyVisibility == null) { + cryptoStore.setShouldShareHistory(roomId, false) + } else { + cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) + cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) } } @@ -1111,6 +1117,10 @@ internal class DefaultCryptoService @Inject constructor( override fun isKeyGossipingEnabled() = cryptoStore.isKeyGossipingEnabled() + override fun isShareKeysOnInviteEnabled() = cryptoStore.isShareKeysOnInviteEnabled() + + override fun enableShareKeyOnInvite(enable: Boolean) = cryptoStore.enableShareKeyOnInvite(enable) + /** * Tells whether the client should ever send encrypted messages to unverified devices. * The default value is false. @@ -1335,6 +1345,30 @@ internal class DefaultCryptoService @Inject constructor( } } + override suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?) { + deviceListManager.downloadKeys(listOf(userId), false) + val userDevices = cryptoStore.getUserDeviceList(userId) + val sessionToShare = sessionInfoSet.orEmpty().mapNotNull { sessionInfo -> + // Get inbound session from sessionId and sessionKey + withContext(coroutineDispatchers.crypto) { + olmDevice.getInboundGroupSession( + sessionId = sessionInfo.sessionId, + senderKey = sessionInfo.senderKey, + roomId = roomId + ).takeIf { it.wrapper.sessionData.sharedHistory } + } + } + + userDevices?.forEach { deviceInfo -> + // Lets share the provided inbound sessions for every user device + sessionToShare.forEach { inboundGroupSession -> + val encryptor = roomEncryptorsStore.get(roomId) + encryptor?.shareHistoryKeysWithDevice(inboundGroupSession, deviceInfo) + Timber.i("## CRYPTO | Sharing inbound session") + } + } + } + /* ========================================================================================== * For test only * ========================================================================================== */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index c1d04eb22ba482fe4c7a8791233ef7730d478ad3..bc3309132a8cac906aa638d73efa8618d30fe9a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCoroutineDispatchers @@ -68,6 +70,7 @@ internal class EventDecryptor @Inject constructor( val senderKey: String? ) + private val wedgedMutex = Mutex() private val wedgedDevices = mutableListOf<WedgedDeviceInfo>() /** @@ -151,11 +154,13 @@ internal class EventDecryptor @Inject constructor( } } - private fun markOlmSessionForUnwedging(senderId: String, senderKey: String) { - val info = WedgedDeviceInfo(senderId, senderKey) - if (!wedgedDevices.contains(info)) { - Timber.tag(loggerTag.value).d("Marking device from $senderId key:$senderKey as wedged") - wedgedDevices.add(info) + private suspend fun markOlmSessionForUnwedging(senderId: String, senderKey: String) { + wedgedMutex.withLock { + val info = WedgedDeviceInfo(senderId, senderKey) + if (!wedgedDevices.contains(info)) { + Timber.tag(loggerTag.value).d("Marking device from $senderId key:$senderKey as wedged") + wedgedDevices.add(info) + } } } @@ -167,15 +172,17 @@ internal class EventDecryptor @Inject constructor( Timber.tag(loggerTag.value).v("Unwedging: ${wedgedDevices.size} are wedged") // get the one that should be retried according to rate limit val now = clock.epochMillis() - val toUnwedge = wedgedDevices.filter { - val lastForcedDate = lastNewSessionForcedDates[it] ?: 0 - if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { - Timber.tag(loggerTag.value).d("Unwedging, New session for $it already forced with device at $lastForcedDate") - return@filter false + val toUnwedge = wedgedMutex.withLock { + wedgedDevices.filter { + val lastForcedDate = lastNewSessionForcedDates[it] ?: 0 + if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { + Timber.tag(loggerTag.value).d("Unwedging, New session for $it already forced with device at $lastForcedDate") + return@filter false + } + // let's already mark that we tried now + lastNewSessionForcedDates[it] = now + true } - // let's already mark that we tried now - lastNewSessionForcedDates[it] = now - true } if (toUnwedge.isEmpty()) { @@ -230,6 +237,15 @@ internal class EventDecryptor @Inject constructor( withContext(coroutineDispatchers.io) { sendToDeviceTask.executeRetry(sendToDeviceParams, remainingRetry = SEND_TO_DEVICE_RETRY_COUNT) } + + deviceList.values.flatten().forEach { deviceInfo -> + wedgedMutex.withLock { + wedgedDevices.removeAll { + it.senderKey == deviceInfo.identityKey() && + it.userId == deviceInfo.userId + } + } + } } catch (failure: Throwable) { deviceList.flatMap { it.value }.joinToString { it.shortDebugString() }.let { Timber.tag(loggerTag.value).e(failure, "## Failed to unwedge devices: $it}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt index e4d322cadd3a148c840f33235b4ae905de57c0ae..39dfb72149047bd09ca80ca7182d3512f6064612 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt @@ -21,17 +21,14 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import timber.log.Timber -import java.util.Timer -import java.util.TimerTask import javax.inject.Inject internal data class InboundGroupSessionHolder( - val wrapper: OlmInboundGroupSessionWrapper2, + val wrapper: MXInboundMegolmSessionWrapper, val mutex: Mutex = Mutex() ) @@ -57,18 +54,13 @@ internal class InboundGroupSessionStore @Inject constructor( if (oldValue != null) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { Timber.tag(loggerTag.value).v("## Inbound: entryRemoved ${oldValue.wrapper.roomId}-${oldValue.wrapper.senderKey}") - store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper }) - oldValue.wrapper.olmInboundGroupSession?.releaseSession() + // store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper }) + oldValue.wrapper.session.releaseSession() } } } } - private val timer = Timer() - private var timerTask: TimerTask? = null - - private val dirtySession = mutableListOf<OlmInboundGroupSessionWrapper2>() - @Synchronized fun clear() { sessionCache.evictAll() @@ -90,12 +82,11 @@ internal class InboundGroupSessionStore @Inject constructor( @Synchronized fun replaceGroupSession(old: InboundGroupSessionHolder, new: InboundGroupSessionHolder, sessionId: String, senderKey: String) { Timber.tag(loggerTag.value).v("## Replacing outdated session ${old.wrapper.roomId}-${old.wrapper.senderKey}") - dirtySession.remove(old.wrapper) store.removeInboundGroupSession(sessionId, senderKey) sessionCache.remove(CacheKey(sessionId, senderKey)) // release removed session - old.wrapper.olmInboundGroupSession?.releaseSession() + old.wrapper.session.releaseSession() internalStoreGroupSession(new, sessionId, senderKey) } @@ -107,33 +98,14 @@ internal class InboundGroupSessionStore @Inject constructor( private fun internalStoreGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) { Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession mark as dirty ${holder.wrapper.roomId}-${holder.wrapper.senderKey}") - // We want to batch this a bit for performances - dirtySession.add(holder.wrapper) if (sessionCache[CacheKey(sessionId, senderKey)] == null) { // first time seen, put it in memory cache while waiting for batch insert // If it's already known, no need to update cache it's already there sessionCache.put(CacheKey(sessionId, senderKey), holder) } - - timerTask?.cancel() - timerTask = object : TimerTask() { - override fun run() { - batchSave() - } - } - timer.schedule(timerTask!!, 300) - } - - @Synchronized - private fun batchSave() { - val toSave = mutableListOf<OlmInboundGroupSessionWrapper2>().apply { addAll(dirtySession) } - dirtySession.clear() cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession batching save of ${toSave.size}") - tryOrNull { - store.storeInboundGroupSessions(toSave) - } + store.storeInboundGroupSessions(listOf(holder.wrapper)) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt index 7f36224daec5f66fef7c29588b130428ac5b1f7d..729b4481e42c55b24fae2968de9c2507756bab60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt @@ -315,7 +315,7 @@ internal class IncomingKeyRequestManager @Inject constructor( ) val params = SendToDeviceTask.Params( - EventType.ROOM_KEY_WITHHELD, + EventType.ROOM_KEY_WITHHELD.stable, MXUsersDevicesMap<Any>().apply { setObject(request.requestingUserId, request.requestingDeviceId, withHeldContent) } 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 24b6fd166f316866affb67f718b8e68bd1bf37c7..96ccba51dc10094967157ff4267884a9d35975dc 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 @@ -27,7 +27,8 @@ import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXOutboundSessionInfo import org.matrix.android.sdk.internal.crypto.algorithms.megolm.SharedWithHelper -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.MoshiProvider @@ -38,6 +39,7 @@ import org.matrix.android.sdk.internal.util.convertToUTF8 import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.olm.OlmAccount import org.matrix.olm.OlmException +import org.matrix.olm.OlmInboundGroupSession import org.matrix.olm.OlmMessage import org.matrix.olm.OlmOutboundGroupSession import org.matrix.olm.OlmSession @@ -514,8 +516,9 @@ internal class MXOlmDevice @Inject constructor( return MXOutboundSessionInfo( sessionId = sessionId, sharedWithHelper = SharedWithHelper(roomId, sessionId, store), - clock, - restoredOutboundGroupSession.creationTime + clock = clock, + creationTime = restoredOutboundGroupSession.creationTime, + sharedHistory = restoredOutboundGroupSession.sharedHistory ) } return null @@ -598,6 +601,7 @@ internal class MXOlmDevice @Inject constructor( * @param forwardingCurve25519KeyChain Devices involved in forwarding this session to us. * @param keysClaimed Other keys the sender claims. * @param exportFormat true if the megolm keys are in export format + * @param sharedHistory MSC3061, this key is sharable on invite * @return true if the operation succeeds. */ fun addInboundGroupSession( @@ -607,31 +611,39 @@ internal class MXOlmDevice @Inject constructor( senderKey: String, forwardingCurve25519KeyChain: List<String>, keysClaimed: Map<String, String>, - exportFormat: Boolean + exportFormat: Boolean, + sharedHistory: Boolean ): AddSessionResult { - val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat) + val candidateSession = tryOrNull("Failed to create inbound session in room $roomId") { + if (exportFormat) { + OlmInboundGroupSession.importSession(sessionKey) + } else { + OlmInboundGroupSession(sessionKey) + } + } + val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) } val existingSession = existingSessionHolder?.wrapper // If we have an existing one we should check if the new one is not better if (existingSession != null) { Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session") try { - val existingFirstKnown = existingSession.firstKnownIndex ?: return AddSessionResult.NotImported.also { + val existingFirstKnown = tryOrNull { existingSession.session.firstKnownIndex } ?: return AddSessionResult.NotImported.also { // This is quite unexpected, could throw if native was released? Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session") - candidateSession.olmInboundGroupSession?.releaseSession() + candidateSession?.releaseSession() // Probably should discard it? } - val newKnownFirstIndex = candidateSession.firstKnownIndex + val newKnownFirstIndex = tryOrNull("Failed to get candidate first known index") { candidateSession?.firstKnownIndex } // If our existing session is better we keep it if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId") - candidateSession.olmInboundGroupSession?.releaseSession() + candidateSession?.releaseSession() return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt()) } } catch (failure: Throwable) { Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}") - candidateSession.olmInboundGroupSession?.releaseSession() + candidateSession?.releaseSession() return AddSessionResult.NotImported } } @@ -639,36 +651,42 @@ internal class MXOlmDevice @Inject constructor( Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Candidate session should be added $senderKey/$sessionId") // sanity check on the new session - val candidateOlmInboundSession = candidateSession.olmInboundGroupSession - if (null == candidateOlmInboundSession) { + if (null == candidateSession) { Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session <null>") return AddSessionResult.NotImported } try { - if (candidateOlmInboundSession.sessionIdentifier() != sessionId) { + if (candidateSession.sessionIdentifier() != sessionId) { Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey") - candidateOlmInboundSession.releaseSession() + candidateSession.releaseSession() return AddSessionResult.NotImported } } catch (e: Throwable) { - candidateOlmInboundSession.releaseSession() + candidateSession.releaseSession() Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed") return AddSessionResult.NotImported } - candidateSession.senderKey = senderKey - candidateSession.roomId = roomId - candidateSession.keysClaimed = keysClaimed - candidateSession.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain + val candidateSessionData = InboundGroupSessionData( + senderKey = senderKey, + roomId = roomId, + keysClaimed = keysClaimed, + forwardingCurve25519KeyChain = forwardingCurve25519KeyChain, + sharedHistory = sharedHistory, + ) + val wrapper = MXInboundMegolmSessionWrapper( + candidateSession, + candidateSessionData + ) if (existingSession != null) { - inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(candidateSession), sessionId, senderKey) + inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(wrapper), sessionId, senderKey) } else { - inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey) + inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(wrapper), sessionId, senderKey) } - return AddSessionResult.Imported(candidateSession.firstKnownIndex?.toInt() ?: 0) + return AddSessionResult.Imported(candidateSession.firstKnownIndex.toInt()) } /** @@ -677,41 +695,22 @@ internal class MXOlmDevice @Inject constructor( * @param megolmSessionsData the megolm sessions data * @return the successfully imported sessions. */ - fun importInboundGroupSessions(megolmSessionsData: List<MegolmSessionData>): List<OlmInboundGroupSessionWrapper2> { - val sessions = ArrayList<OlmInboundGroupSessionWrapper2>(megolmSessionsData.size) + fun importInboundGroupSessions(megolmSessionsData: List<MegolmSessionData>): List<MXInboundMegolmSessionWrapper> { + val sessions = ArrayList<MXInboundMegolmSessionWrapper>(megolmSessionsData.size) for (megolmSessionData in megolmSessionsData) { val sessionId = megolmSessionData.sessionId ?: continue val senderKey = megolmSessionData.senderKey ?: continue val roomId = megolmSessionData.roomId - var candidateSessionToImport: OlmInboundGroupSessionWrapper2? = null - - try { - candidateSessionToImport = OlmInboundGroupSessionWrapper2(megolmSessionData) - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") - } - - // sanity check - if (candidateSessionToImport?.olmInboundGroupSession == null) { - Timber.tag(loggerTag.value).e("## importInboundGroupSession : invalid session") - continue - } - - val candidateOlmInboundGroupSession = candidateSessionToImport.olmInboundGroupSession - try { - if (candidateOlmInboundGroupSession?.sessionIdentifier() != sessionId) { - Timber.tag(loggerTag.value).e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey") - candidateOlmInboundGroupSession?.releaseSession() - continue - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession : sessionIdentifier() failed") - candidateOlmInboundGroupSession?.releaseSession() + val candidateSessionToImport = try { + MXInboundMegolmSessionWrapper.newFromMegolmData(megolmSessionData, true) + } catch (e: Throwable) { + Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Failed to import session $senderKey/$sessionId") continue } + val candidateOlmInboundGroupSession = candidateSessionToImport.session val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) } val existingSession = existingSessionHolder?.wrapper @@ -721,16 +720,16 @@ internal class MXOlmDevice @Inject constructor( sessions.add(candidateSessionToImport) } else { Timber.tag(loggerTag.value).e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") - val existingFirstKnown = tryOrNull { existingSession.firstKnownIndex } - val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.firstKnownIndex } + val existingFirstKnown = tryOrNull { existingSession.session.firstKnownIndex } + val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.session.firstKnownIndex } if (existingFirstKnown == null || candidateFirstKnownIndex == null) { // should not happen? - candidateSessionToImport.olmInboundGroupSession?.releaseSession() + candidateSessionToImport.session.releaseSession() Timber.tag(loggerTag.value) .w("## importInboundGroupSession() : Can't check session null index $existingFirstKnown/$candidateFirstKnownIndex") } else { - if (existingFirstKnown <= candidateSessionToImport.firstKnownIndex!!) { + if (existingFirstKnown <= candidateFirstKnownIndex) { // Ignore this, keep existing candidateOlmInboundGroupSession.releaseSession() } else { @@ -774,8 +773,7 @@ internal class MXOlmDevice @Inject constructor( ): OlmDecryptionResult { val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId) val wrapper = sessionHolder.wrapper - val inboundGroupSession = wrapper.olmInboundGroupSession - ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null") + val inboundGroupSession = wrapper.session if (roomId != wrapper.roomId) { // Check that the room id matches the original one for the session. This stops // the HS pretending a message was targeting a different room. @@ -810,7 +808,6 @@ internal class MXOlmDevice @Inject constructor( } replayAttackMap[messageIndexKey] = eventId } - inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey) val payload = try { val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) @@ -822,9 +819,9 @@ internal class MXOlmDevice @Inject constructor( return OlmDecryptionResult( payload, - wrapper.keysClaimed, + wrapper.sessionData.keysClaimed, senderKey, - wrapper.forwardingCurve25519KeyChain + wrapper.sessionData.forwardingCurve25519KeyChain ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt index f6bc9a9148af14285a23d32ed0d32645eddf92f2..ca0bdc8a0ebafd989793ed12c35de9b3cae80d1f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt @@ -69,5 +69,13 @@ internal data class MegolmSessionData( * Devices which forwarded this session to us (normally empty). */ @Json(name = "forwarding_curve25519_key_chain") - val forwardingCurve25519KeyChain: List<String>? = null + val forwardingCurve25519KeyChain: List<String>? = null, + + /** + * Flag that indicates whether or not the current inboundSession will be shared to + * invited users to decrypt past messages. + */ + // When this feature lands in spec name = shared_history should be used + @Json(name = "org.matrix.msc3061.shared_history") + val sharedHistory: Boolean = false, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt index 6b22cc09d6e8ff37f3ec348b81168a032faa9546..810699d933953860fe3e53e9b9f90516b7e22c1a 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt @@ -437,7 +437,10 @@ internal class OutgoingKeyRequestManager @Inject constructor( if (perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)) { // let's see what's the index val knownIndex = tryOrNull { - inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")?.wrapper?.firstKnownIndex + inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "") + ?.wrapper + ?.session + ?.firstKnownIndex } if (knownIndex != null && knownIndex <= request.fromIndex) { // we found the key in backup with good enough index, so we can just mark as cancelled, no need to send request diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt index f6ab96aee68033c95460b670ecc194b403003e89..a624b92a198c89b40ae2e9271609bcb9293e14d7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt @@ -84,8 +84,9 @@ internal class MegolmSessionDataImporter @Inject constructor( megolmSessionData.senderKey ?: "", tryOrNull { olmInboundGroupSessionWrappers - .firstOrNull { it.olmInboundGroupSession?.sessionIdentifier() == megolmSessionData.sessionId } - ?.firstKnownIndex?.toInt() + .firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId } + ?.session?.firstKnownIndex + ?.toInt() } ?: 0 ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt index 73ce5a5004b10ca227b8d50362fe6881546dbb69..1454f5b4868d2f38f9c8a866d0401851abcd49e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt @@ -16,7 +16,9 @@ package org.matrix.android.sdk.internal.crypto.algorithms +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder /** * An interface for encrypting data. @@ -32,4 +34,6 @@ internal interface IMXEncrypting { * @return the encrypted content */ suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content + + suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {} } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 141d6f74cd0b23525356af59790b459dbeceba3e..410b74e19f1d6b155875241bff96d50e79e584c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import dagger.Lazy +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.NewSessionListener @@ -41,6 +42,7 @@ internal class MXMegolmDecryption( private val olmDevice: MXOlmDevice, private val outgoingKeyRequestManager: OutgoingKeyRequestManager, private val cryptoStore: IMXCryptoStore, + private val matrixConfiguration: MatrixConfiguration, private val liveEventManager: Lazy<StreamEventsManager> ) : IMXDecrypting { @@ -240,13 +242,14 @@ internal class MXMegolmDecryption( Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}") val addSessionResult = olmDevice.addInboundGroupSession( - roomKeyContent.sessionId, - roomKeyContent.sessionKey, - roomKeyContent.roomId, - senderKey, - forwardingCurve25519KeyChain, - keysClaimed, - exportFormat + sessionId = roomKeyContent.sessionId, + sessionKey = roomKeyContent.sessionKey, + roomId = roomKeyContent.roomId, + senderKey = senderKey, + forwardingCurve25519KeyChain = forwardingCurve25519KeyChain, + keysClaimed = keysClaimed, + exportFormat = exportFormat, + sharedHistory = roomKeyContent.getSharedKey() ) when (addSessionResult) { @@ -296,6 +299,14 @@ internal class MXMegolmDecryption( } } + /** + * Returns boolean shared key flag, if enabled with respect to matrix configuration. + */ + private fun RoomKeyContent.getSharedKey(): Boolean { + if (!cryptoStore.isShareKeysOnInviteEnabled()) return false + return sharedHistory ?: false + } + /** * Check if the some messages can be decrypted with a new session. * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt index 81a6fb28c06e4fc30bdc24c7b5f62fbc5995c1c0..38edbb7430a2bd932dd86c855a237cc4482ad6d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import dagger.Lazy +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -27,6 +28,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor( private val olmDevice: MXOlmDevice, private val outgoingKeyRequestManager: OutgoingKeyRequestManager, private val cryptoStore: IMXCryptoStore, + private val matrixConfiguration: MatrixConfiguration, private val eventsManager: Lazy<StreamEventsManager> ) { @@ -35,6 +37,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor( olmDevice, outgoingKeyRequestManager, cryptoStore, + matrixConfiguration, eventsManager ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 7bfbae6edf5ab194de33dec1054e3fef5b3c2d2b..771b5f9a626e6eaf17322a2cb87638f9ac34f850 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.internal.crypto.DeviceListManager +import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter @@ -151,14 +152,27 @@ internal class MXMegolmEncryption( "ed25519" to olmDevice.deviceEd25519Key!! ) + val sharedHistory = cryptoStore.shouldShareHistory(roomId) + Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() as sharedHistory $sharedHistory") olmDevice.addInboundGroupSession( - sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!, - emptyList(), keysClaimedMap, false + sessionId = sessionId!!, + sessionKey = olmDevice.getSessionKey(sessionId)!!, + roomId = roomId, + senderKey = olmDevice.deviceCurve25519Key!!, + forwardingCurve25519KeyChain = emptyList(), + keysClaimed = keysClaimedMap, + exportFormat = false, + sharedHistory = sharedHistory ) defaultKeysBackupService.maybeBackupKeys() - return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore), clock) + return MXOutboundSessionInfo( + sessionId = sessionId, + sharedWithHelper = SharedWithHelper(roomId, sessionId, cryptoStore), + clock = clock, + sharedHistory = sharedHistory + ) } /** @@ -172,6 +186,8 @@ internal class MXMegolmEncryption( if (session == null || // Need to make a brand new session? session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) || + // Is there a room history visibility change since the last outboundSession + cryptoStore.shouldShareHistory(roomId) != session.sharedHistory || // Determine if we have shared with anyone we shouldn't have session.sharedWithTooManyDevices(devicesInRoom)) { Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.") @@ -231,26 +247,29 @@ internal class MXMegolmEncryption( /** * Share the device keys of a an user. * - * @param session the session info + * @param sessionInfo the session info * @param devicesByUser the devices map */ private suspend fun shareUserDevicesKey( - session: MXOutboundSessionInfo, + sessionInfo: MXOutboundSessionInfo, devicesByUser: Map<String, List<CryptoDeviceInfo>> ) { - val sessionKey = olmDevice.getSessionKey(session.sessionId) - val chainIndex = olmDevice.getMessageIndex(session.sessionId) - - val submap = HashMap<String, Any>() - submap["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM - submap["room_id"] = roomId - submap["session_id"] = session.sessionId - submap["session_key"] = sessionKey!! - submap["chain_index"] = chainIndex - - val payload = HashMap<String, Any>() - payload["type"] = EventType.ROOM_KEY - payload["content"] = submap + val sessionKey = olmDevice.getSessionKey(sessionInfo.sessionId) ?: return Unit.also { + Timber.tag(loggerTag.value).v("shareUserDevicesKey() Failed to share session, failed to export") + } + val chainIndex = olmDevice.getMessageIndex(sessionInfo.sessionId) + + val payload = mapOf( + "type" to EventType.ROOM_KEY, + "content" to mapOf( + "algorithm" to MXCRYPTO_ALGORITHM_MEGOLM, + "room_id" to roomId, + "session_id" to sessionInfo.sessionId, + "session_key" to sessionKey, + "chain_index" to chainIndex, + "org.matrix.msc3061.shared_history" to sessionInfo.sharedHistory + ) + ) var t0 = clock.epochMillis() Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts") @@ -292,7 +311,7 @@ internal class MXMegolmEncryption( // for dead devices on every message. for ((_, devicesToShareWith) in devicesByUser) { for (deviceInfo in devicesToShareWith) { - session.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex) + sessionInfo.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex) // XXX is it needed to add it to the audit trail? // For now decided that no, we are more interested by forward trail } @@ -300,8 +319,8 @@ internal class MXMegolmEncryption( if (haveTargets) { t0 = clock.epochMillis() - Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target") - Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}") + Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${sessionInfo.sessionId} : has target") + Timber.tag(loggerTag.value).d("sending to device room key for ${sessionInfo.sessionId} to ${contentMap.toDebugString()}") val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap) try { withContext(coroutineDispatchers.io) { @@ -310,7 +329,7 @@ internal class MXMegolmEncryption( Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${clock.epochMillis() - t0} ms") } catch (failure: Throwable) { // What to do here... - Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${session.sessionId}>") + Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${sessionInfo.sessionId}>") } } else { Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key") @@ -320,7 +339,7 @@ internal class MXMegolmEncryption( // XXX offload?, as they won't read the message anyhow? notifyKeyWithHeld( noOlmToNotify, - session.sessionId, + sessionInfo.sessionId, olmDevice.deviceCurve25519Key, WithHeldCode.NO_OLM ) @@ -346,7 +365,7 @@ internal class MXMegolmEncryption( fromDevice = myDeviceId ) val params = SendToDeviceTask.Params( - EventType.ROOM_KEY_WITHHELD, + EventType.ROOM_KEY_WITHHELD.stable, MXUsersDevicesMap<Any>().apply { targets.forEach { setObject(it.userId, it.deviceId, withHeldContent) @@ -514,6 +533,51 @@ internal class MXMegolmEncryption( } } + @Throws + override suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) { + require(inboundSessionWrapper.wrapper.sessionData.sharedHistory) { "This key can't be shared" } + Timber.tag(loggerTag.value).i("process shareHistoryKeys for ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}") + val userId = deviceInfo.userId + val deviceId = deviceInfo.deviceId + val devicesByUser = mapOf(userId to listOf(deviceInfo)) + val usersDeviceMap = try { + ensureOlmSessionsForDevicesAction.handle(devicesByUser) + } catch (failure: Throwable) { + Timber.tag(loggerTag.value).i(failure, "process shareHistoryKeys failed to ensure olm") + // process anyway? + null + } + val olmSessionResult = usersDeviceMap?.getObject(userId, deviceId) + if (olmSessionResult?.sessionId == null) { + Timber.tag(loggerTag.value).w("shareHistoryKeys: no session with this device, probably because there were no one-time keys") + return + } + + val export = inboundSessionWrapper.mutex.withLock { + inboundSessionWrapper.wrapper.exportKeys() + } ?: return Unit.also { + Timber.tag(loggerTag.value).e("shareHistoryKeys: failed to export group session ${inboundSessionWrapper.wrapper.safeSessionId}") + } + + val payloadJson = mapOf( + "type" to EventType.FORWARDED_ROOM_KEY, + "content" to export + ) + + val encodedPayload = + withContext(coroutineDispatchers.computation) { + messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + } + val sendToDeviceMap = MXUsersDevicesMap<Any>() + sendToDeviceMap.setObject(userId, deviceId, encodedPayload) + Timber.tag(loggerTag.value) + .d("shareHistoryKeys() : sending session ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}") + val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) + withContext(coroutineDispatchers.io) { + sendToDeviceTask.execute(sendToDeviceParams) + } + } + data class DeviceInRoomInfo( val allowedDevices: MXUsersDevicesMap<CryptoDeviceInfo> = MXUsersDevicesMap(), val withHeldDevices: MXUsersDevicesMap<WithHeldCode> = MXUsersDevicesMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt index 28d925d8fd5cf3690af003798442a82b00da9483..e0caa0d9a5900c494d40b06d7db1835f132a5c07 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt @@ -28,6 +28,7 @@ internal class MXOutboundSessionInfo( private val clock: Clock, // When the session was created private val creationTime: Long = clock.epochMillis(), + val sharedHistory: Boolean = false ) { // Number of times this session has been used diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 5eaa106af3cc732c38953816dd1c94f505aa97ca..8691c087798bcb415a4162cb3745604f90c4200b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -24,8 +24,10 @@ import androidx.annotation.WorkerThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP @@ -50,6 +52,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.api.util.fromBase64 +import org.matrix.android.sdk.internal.crypto.InboundGroupSessionStore import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.ObjectSigner @@ -71,7 +74,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDa import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.android.sdk.internal.di.MoshiProvider @@ -118,6 +121,8 @@ internal class DefaultKeysBackupService @Inject constructor( private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, // Task executor private val taskExecutor: TaskExecutor, + private val matrixConfiguration: MatrixConfiguration, + private val inboundGroupSessionStore: InboundGroupSessionStore, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope ) : KeysBackupService { @@ -1316,7 +1321,7 @@ internal class DefaultKeysBackupService @Inject constructor( olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach - val olmInboundGroupSession = olmInboundGroupSessionWrapper.olmInboundGroupSession ?: return@forEach + val olmInboundGroupSession = olmInboundGroupSessionWrapper.session try { encryptGroupSession(olmInboundGroupSessionWrapper) @@ -1344,6 +1349,8 @@ internal class DefaultKeysBackupService @Inject constructor( // Mark keys as backed up cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) + // we can release the sessions now + olmInboundGroupSessionWrappers.onEach { it.session.releaseSession() } if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { Timber.v("backupKeys: All keys have been backed up") @@ -1405,19 +1412,29 @@ internal class DefaultKeysBackupService @Inject constructor( @VisibleForTesting @WorkerThread - fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData? { + suspend fun encryptGroupSession(olmInboundGroupSessionWrapper: MXInboundMegolmSessionWrapper): KeyBackupData? { + olmInboundGroupSessionWrapper.safeSessionId ?: return null + olmInboundGroupSessionWrapper.senderKey ?: return null // Gather information for each key - val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) } + val device = cryptoStore.deviceWithIdentityKey(olmInboundGroupSessionWrapper.senderKey) // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format - val sessionData = olmInboundGroupSessionWrapper.exportKeys() ?: return null + val sessionData = inboundGroupSessionStore + .getInboundGroupSession(olmInboundGroupSessionWrapper.safeSessionId, olmInboundGroupSessionWrapper.senderKey) + ?.let { + withContext(coroutineDispatchers.computation) { + it.mutex.withLock { it.wrapper.exportKeys() } + } + } + ?: return null val sessionBackupData = mapOf( "algorithm" to sessionData.algorithm, "sender_key" to sessionData.senderKey, "sender_claimed_keys" to sessionData.senderClaimedKeys, "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()), - "session_key" to sessionData.sessionKey + "session_key" to sessionData.sessionKey, + "org.matrix.msc3061.shared_history" to sessionData.sharedHistory ) val json = MoshiProvider.providesMoshi() @@ -1425,7 +1442,9 @@ internal class DefaultKeysBackupService @Inject constructor( .toJson(sessionBackupData) val encryptedSessionBackupData = try { - backupOlmPkEncryption?.encrypt(json) + withContext(coroutineDispatchers.computation) { + backupOlmPkEncryption?.encrypt(json) + } } catch (e: OlmException) { Timber.e(e, "OlmException") null @@ -1435,14 +1454,14 @@ internal class DefaultKeysBackupService @Inject constructor( // Build backup data for that key return KeyBackupData( firstMessageIndex = try { - olmInboundGroupSessionWrapper.olmInboundGroupSession?.firstKnownIndex ?: 0 + olmInboundGroupSessionWrapper.session.firstKnownIndex } catch (e: OlmException) { Timber.e(e, "OlmException") 0L }, - forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain.orEmpty().size, + forwardedCount = olmInboundGroupSessionWrapper.sessionData.forwardingCurve25519KeyChain.orEmpty().size, isVerified = device?.isVerified == true, - + sharedHistory = olmInboundGroupSessionWrapper.getSharedKey(), sessionData = mapOf( "ciphertext" to encryptedSessionBackupData.mCipherText, "mac" to encryptedSessionBackupData.mMac, @@ -1451,6 +1470,14 @@ internal class DefaultKeysBackupService @Inject constructor( ) } + /** + * Returns boolean shared key flag, if enabled with respect to matrix configuration. + */ + private fun MXInboundMegolmSessionWrapper.getSharedKey(): Boolean { + if (!cryptoStore.isShareKeysOnInviteEnabled()) return false + return sessionData.sharedHistory + } + @VisibleForTesting @WorkerThread fun decryptKeyBackupData(keyBackupData: KeyBackupData, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt index 5c3d0c12b0ec16ac06ee87fb658f566e68c52524..1817b18e2ac6d82452509bf36977a6a1734b2cb2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt @@ -50,5 +50,12 @@ internal data class KeyBackupData( * Algorithm-dependent data. */ @Json(name = "session_data") - val sessionData: JsonDict + val sessionData: JsonDict, + + /** + * Flag that indicates whether or not the current inboundSession will be shared to + * invited users to decrypt past messages. + */ + @Json(name = "org.matrix.msc3061.shared_history") + val sharedHistory: Boolean = false ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt new file mode 100644 index 0000000000000000000000000000000000000000..2ce36aa209ba7f90d922652b431f7890187f429d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class InboundGroupSessionData( + + /** The room in which this session is used. */ + @Json(name = "room_id") + var roomId: String? = null, + + /** The base64-encoded curve25519 key of the sender. */ + @Json(name = "sender_key") + var senderKey: String? = null, + + /** Other keys the sender claims. */ + @Json(name = "keys_claimed") + var keysClaimed: Map<String, String>? = null, + + /** Devices which forwarded this session to us (normally emty). */ + @Json(name = "forwarding_curve25519_key_chain") + var forwardingCurve25519KeyChain: List<String>? = emptyList(), + + /** Not yet used, will be in backup v2 + val untrusted?: Boolean = false */ + + /** + * Flag that indicates whether or not the current inboundSession will be shared to + * invited users to decrypt past messages. + */ + @Json(name = "shared_history") + val sharedHistory: Boolean = false, + + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt new file mode 100644 index 0000000000000000000000000000000000000000..2772b3483594e1e58205386a6e6526ac27d34bbd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.model + +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.internal.crypto.MegolmSessionData +import org.matrix.olm.OlmInboundGroupSession +import timber.log.Timber + +data class MXInboundMegolmSessionWrapper( + // olm object + val session: OlmInboundGroupSession, + // data about the session + val sessionData: InboundGroupSessionData +) { + // shortcut + val roomId = sessionData.roomId + val senderKey = sessionData.senderKey + val safeSessionId = tryOrNull("Fail to get megolm session Id") { session.sessionIdentifier() } + + /** + * Export the inbound group session keys. + * @param index the index to export. If null, the first known index will be used + * @return the inbound group session as MegolmSessionData if the operation succeeds + */ + internal fun exportKeys(index: Long? = null): MegolmSessionData? { + return try { + val keysClaimed = sessionData.keysClaimed ?: return null + val wantedIndex = index ?: session.firstKnownIndex + + MegolmSessionData( + senderClaimedEd25519Key = sessionData.keysClaimed?.get("ed25519"), + forwardingCurve25519KeyChain = sessionData.forwardingCurve25519KeyChain?.toList().orEmpty(), + sessionKey = session.export(wantedIndex), + senderClaimedKeys = keysClaimed, + roomId = sessionData.roomId, + sessionId = session.sessionIdentifier(), + senderKey = senderKey, + algorithm = MXCRYPTO_ALGORITHM_MEGOLM, + sharedHistory = sessionData.sharedHistory + ) + } catch (e: Exception) { + Timber.e(e, "## Failed to export megolm : sessionID ${tryOrNull { session.sessionIdentifier() }} failed") + null + } + } + + companion object { + + /** + * @exportFormat true if the megolm keys are in export format + * (ie, they lack an ed25519 signature) + */ + @Throws + internal fun newFromMegolmData(megolmSessionData: MegolmSessionData, exportFormat: Boolean): MXInboundMegolmSessionWrapper { + val exportedKey = megolmSessionData.sessionKey ?: throw IllegalArgumentException("key data not found") + val inboundSession = if (exportFormat) { + OlmInboundGroupSession.importSession(exportedKey) + } else { + OlmInboundGroupSession(exportedKey) + } + .also { + if (it.sessionIdentifier() != megolmSessionData.sessionId) { + it.releaseSession() + throw IllegalStateException("Mismatched group session Id") + } + } + val data = InboundGroupSessionData( + roomId = megolmSessionData.roomId, + senderKey = megolmSessionData.senderKey, + keysClaimed = megolmSessionData.senderClaimedKeys, + forwardingCurve25519KeyChain = megolmSessionData.forwardingCurve25519KeyChain, + sharedHistory = megolmSessionData.sharedHistory, + ) + + return MXInboundMegolmSessionWrapper( + inboundSession, + data + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index 289c169d6d142ddfaee52badaed4f97f551c0ec4..600fcb10033ba5e26a51e8fb93135e5fef909c5c 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -26,6 +26,8 @@ import java.io.Serializable * This class adds more context to a OlmInboundGroupSession object. * This allows additional checks. The class implements Serializable so that the context can be stored. */ +// Note used anymore, just for database migration +// Deprecated("Use MXInboundMegolmSessionWrapper") internal class OlmInboundGroupSessionWrapper2 : Serializable { // The associated olm inbound group session. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt index 4ac87f44cef04359dfb5e1e64b4ead7947ae4d73..5a6d1f4bc12fe9934e57dc153ce9adfda912a509 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt @@ -20,5 +20,9 @@ import org.matrix.olm.OlmOutboundGroupSession internal data class OutboundGroupSessionWrapper( val outboundGroupSession: OlmOutboundGroupSession, - val creationTime: Long + val creationTime: Long, + /** + * As per MSC 3061, declares if this key could be shared when inviting a new user to the room. + */ + val sharedHistory: Boolean = false ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupUsers.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt similarity index 53% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupUsers.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt index 9dd1339157ae8967fd0e1137726cb14b756e2fad..b3a2ba4dfe7273128089083d6bf147e4a4caab06 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupUsers.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt @@ -1,11 +1,11 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2022 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 + * 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, @@ -14,13 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.group.model +package org.matrix.android.sdk.internal.crypto.model -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -internal data class GroupUsers( - @Json(name = "total_user_count_estimate") val totalUserCountEstimate: Int, - @Json(name = "chunk") val users: List<GroupUser> = emptyList() +data class SessionInfo( + val sessionId: String, + val senderKey: String ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index b5b8d8e97446d08d5cf5494738824f3066f0dd49..0413fc730c71305974b78130106d2bb057f8e870 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -35,7 +35,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity @@ -64,7 +64,15 @@ internal interface IMXCryptoStore { * * @return the list of all known group sessions, to export them. */ - fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2> + fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper> + + /** + * Retrieve the known inbound group sessions for the specified room. + * + * @param roomId The roomId that the sessions will be returned + * @return the list of all known group sessions, for the provided roomId + */ + fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper> /** * @return true to unilaterally blacklist all unverified devices. @@ -90,6 +98,20 @@ internal interface IMXCryptoStore { fun isKeyGossipingEnabled(): Boolean + /** + * As per MSC3061. + * If true will make it possible to share part of e2ee room history + * on invite depending on the room visibility setting. + */ + fun enableShareKeyOnInvite(enable: Boolean) + + /** + * As per MSC3061. + * If true will make it possible to share part of e2ee room history + * on invite depending on the room visibility setting. + */ + fun isShareKeysOnInviteEnabled(): Boolean + /** * Provides the rooms ids list in which the messages are not encrypted for the unverified devices. * @@ -250,6 +272,17 @@ internal interface IMXCryptoStore { fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) + fun shouldShareHistory(roomId: String): Boolean + + /** + * Sets a boolean flag that will determine whether or not room history (existing inbound sessions) + * will be shared to new user invites. + * + * @param roomId the room id + * @param shouldShareHistory The boolean flag + */ + fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) + /** * Store a session between the logged-in user and another device. * @@ -290,7 +323,7 @@ internal interface IMXCryptoStore { * * @param sessions the inbound group sessions to store. */ - fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) + fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>) /** * Retrieve an inbound group session. @@ -299,7 +332,17 @@ internal interface IMXCryptoStore { * @param senderKey the base64-encoded curve25519 key of the sender. * @return an inbound group session. */ - fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? + fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? + + /** + * Retrieve an inbound group session, filtering shared history. + * + * @param sessionId the session identifier. + * @param senderKey the base64-encoded curve25519 key of the sender. + * @param sharedHistory filter inbound session with respect to shared history field + * @return an inbound group session. + */ + fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper? /** * Get the current outbound group session for this encrypted room. @@ -333,7 +376,7 @@ internal interface IMXCryptoStore { * * @param olmInboundGroupSessionWrappers the sessions */ - fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>) + fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<MXInboundMegolmSessionWrapper>) /** * Retrieve inbound group sessions that are not yet backed up. @@ -341,7 +384,7 @@ internal interface IMXCryptoStore { * @param limit the maximum number of sessions to return. * @return an array of non backed up inbound group sessions. */ - fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper2> + fun inboundGroupSessionsToBackup(limit: Int): List<MXInboundMegolmSessionWrapper> /** * Number of stored inbound group sessions. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 028d8f73f9556bcdb8002704e8d107b3e36d8e84..f5468634cbf8854beb0551635b71193ee0868c8f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -50,7 +50,7 @@ import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldCo import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -657,12 +657,28 @@ internal class RealmCryptoStore @Inject constructor( ?: false } + override fun shouldShareHistory(roomId: String): Boolean { + if (!isShareKeysOnInviteEnabled()) return false + return doWithRealm(realmConfiguration) { + CryptoRoomEntity.getById(it, roomId)?.shouldShareHistory + } + ?: false + } + override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { doRealmTransaction(realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers } } + override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { + Timber.tag(loggerTag.value) + .v("setShouldShareHistory for room $roomId is $shouldShareHistory") + doRealmTransaction(realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory + } + } + override fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) { var sessionIdentifier: String? = null @@ -727,54 +743,61 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) { + override fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>) { if (sessions.isEmpty()) { return } doRealmTransaction(realmConfiguration) { realm -> - sessions.forEach { session -> - var sessionIdentifier: String? = null + sessions.forEach { wrapper -> - try { - sessionIdentifier = session.olmInboundGroupSession?.sessionIdentifier() + val sessionIdentifier = try { + wrapper.session.sessionIdentifier() } catch (e: OlmException) { Timber.e(e, "## storeInboundGroupSession() : sessionIdentifier failed") + return@forEach } - if (sessionIdentifier != null) { - val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey) +// val shouldShareHistory = session.roomId?.let { roomId -> +// CryptoRoomEntity.getById(realm, roomId)?.shouldShareHistory +// } ?: false + val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, wrapper.sessionData.senderKey) - val existing = realm.where<OlmInboundGroupSessionEntity>() - .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) - .findFirst() + val existing = realm.where<OlmInboundGroupSessionEntity>() + .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) + .findFirst() - if (existing != null) { - // we want to keep the existing backup status - existing.putInboundGroupSession(session) - } else { - val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { - primaryKey = key - sessionId = sessionIdentifier - senderKey = session.senderKey - putInboundGroupSession(session) - } - - realm.insertOrUpdate(realmOlmInboundGroupSession) - } + val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { + primaryKey = key + store(wrapper) + backedUp = existing?.backedUp ?: false } + + Timber.v("## CRYPTO | shouldShareHistory: ${wrapper.sessionData.sharedHistory} for $key") + realm.insertOrUpdate(realmOlmInboundGroupSession) } } } - override fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? { + override fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) + return doWithRealm(realmConfiguration) { realm -> + realm.where<OlmInboundGroupSessionEntity>() + .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) + .findFirst() + ?.toModel() + } + } + + override fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper? { + val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) return doWithRealm(realmConfiguration) { it.where<OlmInboundGroupSessionEntity>() + .equalTo(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, sharedHistory) .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findFirst() - ?.getInboundGroupSession() + ?.toModel() } } @@ -786,7 +809,8 @@ internal class RealmCryptoStore @Inject constructor( entity.getOutboundGroupSession()?.let { OutboundGroupSessionWrapper( it, - entity.creationTime ?: 0 + entity.creationTime ?: 0, + entity.shouldShareHistory ) } } @@ -806,6 +830,8 @@ internal class RealmCryptoStore @Inject constructor( if (outboundGroupSession != null) { val info = realm.createObject(OutboundGroupSessionInfoEntity::class.java).apply { creationTime = clock.epochMillis() + // Store the room history visibility on the outbound session creation + shouldShareHistory = entity.shouldShareHistory putOutboundGroupSession(outboundGroupSession) } entity.outboundSessionInfo = info @@ -814,17 +840,32 @@ internal class RealmCryptoStore @Inject constructor( } } +// override fun needsRotationDueToVisibilityChange(roomId: String): Boolean { +// return doWithRealm(realmConfiguration) { realm -> +// CryptoRoomEntity.getById(realm, roomId)?.let { entity -> +// entity.shouldShareHistory != entity.outboundSessionInfo?.shouldShareHistory +// } +// } ?: false +// } + /** * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2, * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management. */ - override fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2> { - return doWithRealm(realmConfiguration) { - it.where<OlmInboundGroupSessionEntity>() + override fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper> { + return doWithRealm(realmConfiguration) { realm -> + realm.where<OlmInboundGroupSessionEntity>() .findAll() - .mapNotNull { inboundGroupSessionEntity -> - inboundGroupSessionEntity.getInboundGroupSession() - } + .mapNotNull { it.toModel() } + } + } + + override fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper> { + return doWithRealm(realmConfiguration) { realm -> + realm.where<OlmInboundGroupSessionEntity>() + .equalTo(OlmInboundGroupSessionEntityFields.ROOM_ID, roomId) + .findAll() + .mapNotNull { it.toModel() } } } @@ -885,7 +926,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>) { + override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<MXInboundMegolmSessionWrapper>) { if (olmInboundGroupSessionWrappers.isEmpty()) { return } @@ -893,10 +934,13 @@ internal class RealmCryptoStore @Inject constructor( doRealmTransaction(realmConfiguration) { realm -> olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> try { - val sessionIdentifier = olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier() + val sessionIdentifier = + tryOrNull("Failed to get session identifier") { + olmInboundGroupSessionWrapper.session.sessionIdentifier() + } ?: return@forEach val key = OlmInboundGroupSessionEntity.createPrimaryKey( sessionIdentifier, - olmInboundGroupSessionWrapper.senderKey + olmInboundGroupSessionWrapper.sessionData.senderKey ) val existing = realm.where<OlmInboundGroupSessionEntity>() @@ -909,9 +953,7 @@ internal class RealmCryptoStore @Inject constructor( // ... might be in cache but not yet persisted, create a record to persist backedup state val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { primaryKey = key - sessionId = sessionIdentifier - senderKey = olmInboundGroupSessionWrapper.senderKey - putInboundGroupSession(olmInboundGroupSessionWrapper) + store(olmInboundGroupSessionWrapper) backedUp = true } @@ -924,15 +966,13 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper2> { + override fun inboundGroupSessionsToBackup(limit: Int): List<MXInboundMegolmSessionWrapper> { return doWithRealm(realmConfiguration) { it.where<OlmInboundGroupSessionEntity>() .equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, false) .limit(limit.toLong()) .findAll() - .mapNotNull { inboundGroupSession -> - inboundGroupSession.getInboundGroupSession() - } + .mapNotNull { it.toModel() } } } @@ -973,6 +1013,18 @@ internal class RealmCryptoStore @Inject constructor( } ?: false } + override fun isShareKeysOnInviteEnabled(): Boolean { + return doWithRealm(realmConfiguration) { + it.where<CryptoMetadataEntity>().findFirst()?.enableKeyForwardingOnInvite + } ?: false + } + + override fun enableShareKeyOnInvite(enable: Boolean) { + doRealmTransaction(realmConfiguration) { + it.where<CryptoMetadataEntity>().findFirst()?.enableKeyForwardingOnInvite = enable + } + } + override fun setDeviceKeysUploaded(uploaded: Boolean) { doRealmTransaction(realmConfiguration) { it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer = uploaded diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 02c2a27decd9ae64f27246599e66d233ecf0724b..c36d572da63024fe55622e1a283ca9c029d76b0b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.crypto.store.db import io.realm.DynamicRealm -import io.realm.RealmMigration import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo001Legacy import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo002Legacy import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo003RiotX @@ -34,13 +33,23 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo016 +import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017 +import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber import javax.inject.Inject +/** + * Schema version history: + * 0, 1, 2: legacy Riot-Android; + * 3: migrate to RiotX schema; + * 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6). + */ internal class RealmCryptoStoreMigration @Inject constructor( private val clock: Clock, -) : RealmMigration { +) : MatrixRealmMigration( + dbName = "Crypto", + schemaVersion = 17L, +) { /** * Forces all RealmCryptoStoreMigration instances to be equal. * Avoids Realm throwing when multiple instances of the migration are set. @@ -48,14 +57,7 @@ internal class RealmCryptoStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmCryptoStoreMigration override fun hashCode() = 5000 - // 0, 1, 2: legacy Riot-Android - // 3: migrate to RiotX schema - // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) - val schemaVersion = 16L - - override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { - Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion") - + override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { if (oldVersion < 1) MigrateCryptoTo001Legacy(realm).perform() if (oldVersion < 2) MigrateCryptoTo002Legacy(realm).perform() if (oldVersion < 3) MigrateCryptoTo003RiotX(realm).perform() @@ -72,5 +74,6 @@ internal class RealmCryptoStoreMigration @Inject constructor( if (oldVersion < 14) MigrateCryptoTo014(realm).perform() if (oldVersion < 15) MigrateCryptoTo015(realm).perform() if (oldVersion < 16) MigrateCryptoTo016(realm).perform() + if (oldVersion < 17) MigrateCryptoTo017(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt new file mode 100644 index 0000000000000000000000000000000000000000..8904c412cd8324b9efee3251596b2f8fe40991d5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.store.db.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData +import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm +import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields +import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields +import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields +import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields +import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm +import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.util.database.RealmMigrator +import timber.log.Timber + +/** + * Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061. + * Also migrates how megolm session are stored to avoid additional serialized frozen class. + */ +internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("CryptoRoomEntity") + ?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform { + // We don't have access to the session database to check for the state here and set the good value. + // But for now as it's behind a lab flag, will set to false and force initial sync when enabled + it.setBoolean(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, false) + } + + realm.schema.get("OutboundGroupSessionInfoEntity") + ?.addField(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform { + // We don't have access to the session database to check for the state here and set the good value. + // But for now as it's behind a lab flag, will set to false and force initial sync when enabled + it.setBoolean(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, false) + } + + realm.schema.get("CryptoMetadataEntity") + ?.addField(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, Boolean::class.java) + ?.transform { obj -> + // default to false + obj.setBoolean(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, false) + } + + val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java) + + realm.schema.get("OlmInboundGroupSessionEntity") + ?.addField(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java) + ?.addField(OlmInboundGroupSessionEntityFields.ROOM_ID, String::class.java) + ?.addField(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, String::class.java) + ?.addField(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, String::class.java) + ?.transform { dynamicObject -> + try { + // we want to convert the old wrapper frozen class into a + // map of sessionData & the pickled session herself + dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { oldData -> + val oldWrapper = tryOrNull("Failed to convert megolm inbound group data") { + @Suppress("DEPRECATION") + deserializeFromRealm<org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2?>(oldData) + } + val groupSession = oldWrapper?.olmInboundGroupSession + ?: return@transform Unit.also { + Timber.w("Failed to migrate megolm session, no olmInboundGroupSession") + } + // now convert to new data + val data = InboundGroupSessionData( + senderKey = oldWrapper.senderKey, + roomId = oldWrapper.roomId, + keysClaimed = oldWrapper.keysClaimed, + forwardingCurve25519KeyChain = oldWrapper.forwardingCurve25519KeyChain, + sharedHistory = false, + ) + + dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(data)) + dynamicObject.setString(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, serializeForRealm(groupSession)) + + // denormalized fields + dynamicObject.setString(OlmInboundGroupSessionEntityFields.ROOM_ID, oldWrapper.roomId) + dynamicObject.setBoolean(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, false) + } + } catch (failure: Throwable) { + Timber.e(failure, "Failed to migrate megolm session") + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt index 63ed0e537e45eaddd479b3223e43c19178a2b3f3..88708f824eaa41e3cc0f3a2530510beecc0ca2c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt @@ -35,6 +35,11 @@ internal open class CryptoMetadataEntity( var globalBlacklistUnverifiedDevices: Boolean = false, // setting to enable or disable key gossiping var globalEnableKeyGossiping: Boolean = true, + + // MSC3061: Sharing room keys for past messages + // If set to true key history will be shared to invited users with respect to room setting + var enableKeyForwardingOnInvite: Boolean = false, + // The keys backup version currently used. Null means no backup. var backupVersion: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt index 114a596964fa7228ed15a5db8490f164b321a906..be575861632fc669e97a60d37e17a4e19ea07e73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt @@ -24,6 +24,8 @@ internal open class CryptoRoomEntity( var algorithm: String? = null, var shouldEncryptForInvitedMembers: Boolean? = null, var blacklistUnverifiedDevices: Boolean = false, + // Determines whether or not room history should be shared on new member invites + var shouldShareHistory: Boolean = false, // Store the current outbound session for this room, // to avoid re-create and re-share at each startup (if rotation not needed..) // This is specific to megolm but not sure how to model it better diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt index a4f6c279acb964fd1697847a0edc7c9f3b545831..62ab73e379e9158238f9bc734ca878ae6de11321 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt @@ -18,9 +18,12 @@ package org.matrix.android.sdk.internal.crypto.store.db.model import io.realm.RealmObject import io.realm.annotations.PrimaryKey -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm +import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.olm.OlmInboundGroupSession import timber.log.Timber internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey" @@ -28,27 +31,83 @@ internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: internal open class OlmInboundGroupSessionEntity( // Combined value to build a primary key @PrimaryKey var primaryKey: String? = null, + + // denormalization for faster querying (these fields are in the inboundGroupSessionDataJson) var sessionId: String? = null, var senderKey: String? = null, - // olmInboundGroupSessionData contains Json + var roomId: String? = null, + + // Deprecated, used for migration / olmInboundGroupSessionData contains Json + // keep it in case of problem to have a chance to recover var olmInboundGroupSessionData: String? = null, + + // Stores the session data in an extensible format + // to allow to store data not yet supported for later use + var inboundGroupSessionDataJson: String? = null, + + // The pickled session + var serializedOlmInboundGroupSession: String? = null, + + // Flag that indicates whether or not the current inboundSession will be shared to + // invited users to decrypt past messages + var sharedHistory: Boolean = false, // Indicate if the key has been backed up to the homeserver var backedUp: Boolean = false ) : RealmObject() { - fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? { + fun store(wrapper: MXInboundMegolmSessionWrapper) { + this.serializedOlmInboundGroupSession = serializeForRealm(wrapper.session) + this.inboundGroupSessionDataJson = adapter.toJson(wrapper.sessionData) + this.roomId = wrapper.sessionData.roomId + this.senderKey = wrapper.sessionData.senderKey + this.sessionId = wrapper.session.sessionIdentifier() + this.sharedHistory = wrapper.sessionData.sharedHistory + } +// fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? { +// return try { +// deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData) +// } catch (failure: Throwable) { +// Timber.e(failure, "## Deserialization failure") +// return null +// } +// } +// +// fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) { +// olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper) +// } + + fun getOlmGroupSession(): OlmInboundGroupSession? { return try { - deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData) + deserializeFromRealm(serializedOlmInboundGroupSession) } catch (failure: Throwable) { Timber.e(failure, "## Deserialization failure") return null } } - fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) { - olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper) + fun getData(): InboundGroupSessionData? { + return try { + inboundGroupSessionDataJson?.let { + adapter.fromJson(it) + } + } catch (failure: Throwable) { + Timber.e(failure, "## Deserialization failure") + return null + } } - companion object + fun toModel(): MXInboundMegolmSessionWrapper? { + val data = getData() ?: return null + val session = getOlmGroupSession() ?: return null + return MXInboundMegolmSessionWrapper( + session = session, + sessionData = data + ) + } + + companion object { + private val adapter = MoshiProvider.providesMoshi() + .adapter(InboundGroupSessionData::class.java) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt index d50db78415dad5128b9d85b1f0d4cd92915e91d0..2ebd550201fb1a5bcacbd851f236835d6399a06e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt @@ -24,7 +24,8 @@ import timber.log.Timber internal open class OutboundGroupSessionInfoEntity( var serializedOutboundSessionData: String? = null, - var creationTime: Long? = null + var creationTime: Long? = null, + var shouldShareHistory: Boolean = false ) : RealmObject() { fun getOutboundGroupSession(): OlmOutboundGroupSession? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt index 854d148b76c66be956c91c95de583bcacd83f6c9..b10e7501d6a7066e5f5e641129e583a501f39819 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt @@ -117,7 +117,7 @@ internal open class OutgoingKeyRequestEntity( private fun eventToResult(event: Event): RequestResult? { return when (event.getClearType()) { - EventType.ROOM_KEY_WITHHELD -> { + in EventType.ROOM_KEY_WITHHELD.values -> { event.content.toModel<RoomKeyWithHeldContent>()?.code?.let { RequestResult.Failure(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index fbd9d245d9c1493b29b5f76d357f0e5e27b69ac3..bb14b417dd3020c89aa00037cd144239fb1d2e15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -15,7 +15,6 @@ */ package org.matrix.android.sdk.internal.crypto.tasks -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.network.GlobalErrorReceiver @@ -48,8 +47,12 @@ internal class DefaultSendEventTask @Inject constructor( params.event.roomId ?.takeIf { params.encrypt } ?.let { roomId -> - tryOrNull { + try { loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId)) + } catch (failure: Throwable) { + // send any way? + // the result is that some users won't probably be able to decrypt :/ + Timber.w(failure, "SendEvent: failed to load members in room ${params.event.roomId}") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt index 821663bcff1f8255576454bb39e4a4ccc33dc9d3..8a805a55888b86e54356868a7176705445acb462 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt @@ -63,7 +63,7 @@ internal class VerificationMessageProcessor @Inject constructor( // the message should be ignored by the receiver. if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { - Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms") + Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:${event.ageLocalTs} ms") } Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index 690ac122687c5835e38b7c673cfbd8a2a1ca3f16..5b1a4752f1464e8a3b6cf2bb03f3f7b337e987c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -84,7 +84,7 @@ internal class DefaultQrCodeVerificationTransaction( // Perform some checks if (otherQrCodeData.transactionId != transactionId) { Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.transactionId} expected:$transactionId") - cancel(CancelCode.QrCodeInvalid) + cancel(CancelCode.UnknownTransaction) return } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 665567bf2a4d3ebdf10ee4d3f8bf3e6b3157e0ce..b733aa6fc052ffa1767d58b69f8ebaaf7ba60f88 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.database import io.realm.DynamicRealm -import io.realm.RealmMigration import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo001 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo002 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo003 @@ -49,13 +48,20 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035 import org.matrix.android.sdk.internal.util.Normalizer -import timber.log.Timber +import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer -) : RealmMigration { +) : MatrixRealmMigration( + dbName = "Session", + schemaVersion = 35L, +) { /** * Forces all RealmSessionStoreMigration instances to be equal. * Avoids Realm throwing when multiple instances of the migration are set. @@ -63,11 +69,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 31L - - override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { - Timber.d("Migrating Realm Session from $oldVersion to $newVersion") - + override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { if (oldVersion < 1) MigrateSessionTo001(realm).perform() if (oldVersion < 2) MigrateSessionTo002(realm).perform() if (oldVersion < 3) MigrateSessionTo003(realm).perform() @@ -99,5 +101,9 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 29) MigrateSessionTo029(realm).perform() if (oldVersion < 30) MigrateSessionTo030(realm).perform() if (oldVersion < 31) MigrateSessionTo031(realm).perform() + if (oldVersion < 32) MigrateSessionTo032(realm).perform() + if (oldVersion < 33) MigrateSessionTo033(realm).perform() + if (oldVersion < 34) MigrateSessionTo034(realm).perform() + if (oldVersion < 35) MigrateSessionTo035(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index 234caec970df92d5d201e0fdec4146615642d151..221abe0df50d6048bc58fe014f46d26a51185a49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -18,7 +18,11 @@ package org.matrix.android.sdk.internal.database.helper import io.realm.Realm import io.realm.kotlin.createObject +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.crypto.model.SessionInfo +import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity @@ -31,6 +35,7 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.find +import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection @@ -180,3 +185,12 @@ internal fun ChunkEntity.isMoreRecentThan(chunkToCheck: ChunkEntity): Boolean { // We don't know, so we assume it's false return false } + +internal fun ChunkEntity.Companion.findLatestSessionInfo(realm: Realm, roomId: String): Set<SessionInfo>? = + ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.mapNotNull { timelineEvent -> + timelineEvent?.root?.asDomain()?.content?.toModel<EncryptedEventContent>()?.let { content -> + content.sessionId ?: return@mapNotNull null + content.senderKey ?: return@mapNotNull null + SessionInfo(content.sessionId, content.senderKey) + } + }?.toSet() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/GroupSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/GroupSummaryMapper.kt deleted file mode 100644 index 13c3a796c4d1d797097b17f0c991e05b72895f18..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/GroupSummaryMapper.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.internal.database.mapper - -import org.matrix.android.sdk.api.session.group.model.GroupSummary -import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity - -internal object GroupSummaryMapper { - - fun map(groupSummaryEntity: GroupSummaryEntity): GroupSummary { - return GroupSummary( - groupSummaryEntity.groupId, - groupSummaryEntity.membership, - groupSummaryEntity.displayName, - groupSummaryEntity.shortDescription, - groupSummaryEntity.avatarUrl, - groupSummaryEntity.roomIds.toList(), - groupSummaryEntity.userIds.toList() - ) - } -} - -internal fun GroupSummaryEntity.asDomain(): GroupSummary { - return GroupSummaryMapper.map(this) -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 735cfe411ce67b2b695652b3fd35e5dc78b93cf5..72b0f7a0432022af7cf36dfd4bec4a6309fc1937 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -106,6 +106,7 @@ internal class RoomSummaryMapper @Inject constructor( worldReadable = it.childSummaryEntity?.joinRules == RoomJoinRules.PUBLIC ) }, + directParentNames = roomSummaryEntity.directParentNames.toList(), flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList(), roomEncryptionAlgorithm = when (val alg = roomSummaryEntity.e2eAlgorithm) { // I should probably use #hasEncryptorClassForAlgorithm but it says it supports diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt index aae80423ac5118ab2dde48a47fbfa8b10a0dadf5..27423a9dca9033bee7e1987fb9a53271e39c01f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt @@ -49,7 +49,7 @@ internal class MigrateSessionTo010(realm: DynamicRealm) : RealmMigrator(realm, 1 realm.schema.get("RoomSummaryEntity") ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java) ?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java) - ?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java) + ?.addField("groupIds", String::class.java) ?.transform { obj -> val creationEvent = realm.where("CurrentStateEventEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt similarity index 51% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt index 9c37d4db6c9ff2fafbc13159f7eef14d944dd083..1506b8c8b38498c1ea062474448f59437aff844c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo032.kt @@ -1,11 +1,11 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 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 + * 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, @@ -14,17 +14,15 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.group +package org.matrix.android.sdk.internal.database.migration -import org.matrix.android.sdk.api.session.group.Group +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.util.database.RealmMigrator -internal class DefaultGroup( - override val groupId: String, - private val getGroupDataTask: GetGroupDataTask -) : Group { +internal class MigrateSessionTo032(realm: DynamicRealm) : RealmMigrator(realm, 32) { - override suspend fun fetchGroupData() { - val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId)) - getGroupDataTask.execute(params) + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.removeField("groupIds") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt new file mode 100644 index 0000000000000000000000000000000000000000..0e3a8599c594383faf8997a3e4e5ad7d04e2c4df --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 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.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * Migrating to: + * Live location sharing aggregated summary: adding new field relatedEventIds. + */ +internal class MigrateSessionTo033(realm: DynamicRealm) : RealmMigrator(realm, 33) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("LiveLocationShareAggregatedSummaryEntity") + ?.addRealmListField(LiveLocationShareAggregatedSummaryEntityFields.RELATED_EVENT_IDS.`$`, String::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo034.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo034.kt new file mode 100644 index 0000000000000000000000000000000000000000..b23e84706f40e63298aa730b1439b036a1130521 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo034.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 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.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * Migrating to: + * Live location sharing aggregated summary: adding new field startOfLiveTimestampMillis. + */ +internal class MigrateSessionTo034(realm: DynamicRealm) : RealmMigrator(realm, 34) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("LiveLocationShareAggregatedSummaryEntity") + ?.addField(LiveLocationShareAggregatedSummaryEntityFields.START_OF_LIVE_TIMESTAMP_MILLIS, Long::class.java) + ?.setNullable(LiveLocationShareAggregatedSummaryEntityFields.START_OF_LIVE_TIMESTAMP_MILLIS, true) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt new file mode 100644 index 0000000000000000000000000000000000000000..5b3c95b4a2c36d8e29062ebb0af6cdb0189d678f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo035.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 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.database.migration + +import io.realm.DynamicRealm +import io.realm.RealmList +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo035(realm: DynamicRealm) : RealmMigrator(realm, 35) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addRealmListField(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, String::class.java) + ?.transform { it.setList(RoomSummaryEntityFields.DIRECT_PARENT_NAMES.`$`, RealmList("")) } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt deleted file mode 100644 index 0120bb91d3ef2edcd5a8ee49b093aed01fb94234..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.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.internal.database.model - -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey -import org.matrix.android.sdk.api.session.room.model.Membership - -/** - * This class is used to store group info (groupId and membership) from the sync response. - * Then GetGroupDataTask is called regularly to fetch group information from the homeserver. - */ -internal open class GroupEntity(@PrimaryKey var groupId: String = "") : - RealmObject() { - - private var membershipStr: String = Membership.NONE.name - var membership: Membership - get() { - return Membership.valueOf(membershipStr) - } - set(value) { - membershipStr = value.name - } - - companion object -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupSummaryEntity.kt deleted file mode 100644 index d96514855976cf05b19a1b87534fdc742f4bd83f..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupSummaryEntity.kt +++ /dev/null @@ -1,43 +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.database.model - -import io.realm.RealmList -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey -import org.matrix.android.sdk.api.session.room.model.Membership - -internal open class GroupSummaryEntity( - @PrimaryKey var groupId: String = "", - var displayName: String = "", - var shortDescription: String = "", - var avatarUrl: String = "", - var roomIds: RealmList<String> = RealmList(), - var userIds: RealmList<String> = RealmList() -) : RealmObject() { - - private var membershipStr: String = Membership.NONE.name - var membership: Membership - get() { - return Membership.valueOf(membershipStr) - } - set(value) { - membershipStr = value.name - } - - companion object -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index cd755590be0f253a296559d7f04f596b10694812..471bec59afe7d9bb9737e97b3b014dd6e4a36aca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -34,7 +34,8 @@ internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "", var roomType: String? = null, var parents: RealmList<SpaceParentSummaryEntity> = RealmList(), - var children: RealmList<SpaceChildSummaryEntity> = RealmList() + var children: RealmList<SpaceChildSummaryEntity> = RealmList(), + var directParentNames: RealmList<String> = RealmList(), ) : RealmObject() { private var displayName: String? = "" @@ -240,11 +241,6 @@ internal open class RoomSummaryEntity( if (value != field) field = value } - var groupIds: String? = null - set(value) { - if (value != field) field = value - } - @Index private var membershipStr: String = Membership.NONE.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 890c2300f8bd52852e64b439a424caa5353399d9..d131589dd142afe06a8dbaf10daff74df9cbb66f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -32,8 +32,6 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit EventInsertEntity::class, TimelineEventEntity::class, FilterEntity::class, - GroupEntity::class, - GroupSummaryEntity::class, ReadReceiptEntity::class, RoomEntity::class, RoomSummaryEntity::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt index c5df8e933838d2eaf43b068aa9ccdac1405c662a..ca793ffd8e2ffc962a384715120d1c2fb0fecb56 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.database.model.livelocation +import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey @@ -29,6 +30,11 @@ internal open class LiveLocationShareAggregatedSummaryEntity( @PrimaryKey var eventId: String = "", + /** + * List of event ids used to compute the aggregated summary data. + */ + var relatedEventIds: RealmList<String> = RealmList(), + var roomId: String = "", var userId: String = "", @@ -38,6 +44,8 @@ internal open class LiveLocationShareAggregatedSummaryEntity( */ var isActive: Boolean? = null, + var startOfLiveTimestampMillis: Long? = null, + var endOfLiveTimestampMillis: Long? = null, /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt index e0dbf2eee82cb7baf4e3c2909093ed015534ee9e..e17d07c584454190724366c86f8ffb656eeb5e14 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt @@ -23,13 +23,20 @@ import io.realm.kotlin.createObject import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields +internal fun CurrentStateEventEntity.Companion.whereRoomId( + realm: Realm, + roomId: String +): RealmQuery<CurrentStateEventEntity> { + return realm.where(CurrentStateEventEntity::class.java) + .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) +} + internal fun CurrentStateEventEntity.Companion.whereType( realm: Realm, roomId: String, type: String ): RealmQuery<CurrentStateEventEntity> { - return realm.where(CurrentStateEventEntity::class.java) - .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) + return whereRoomId(realm = realm, roomId = roomId) .equalTo(CurrentStateEventEntityFields.TYPE, type) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt index 6caa832110ccee65587a7420a8b5f97f796b0d7c..1c19c21de29601e43fe06981c946798eae28dbbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt @@ -23,6 +23,11 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> { + return realm.where<EventAnnotationsSummaryEntity>() + .equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId) +} + internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> { return realm.where<EventAnnotationsSummaryEntity>() .equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId) @@ -44,3 +49,7 @@ internal fun EventAnnotationsSummaryEntity.Companion.getOrCreate(realm: Realm, r return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId) } + +internal fun EventAnnotationsSummaryEntity.Companion.get(realm: Realm, eventId: String): EventAnnotationsSummaryEntity? { + return EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/GroupEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/GroupEntityQueries.kt deleted file mode 100644 index 020592d1dd3708b267d8c44b6309c314bc305e7b..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/GroupEntityQueries.kt +++ /dev/null @@ -1,34 +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.database.query - -import io.realm.Realm -import io.realm.RealmQuery -import io.realm.kotlin.where -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.internal.database.model.GroupEntity -import org.matrix.android.sdk.internal.database.model.GroupEntityFields -import org.matrix.android.sdk.internal.query.process - -internal fun GroupEntity.Companion.where(realm: Realm, groupId: String): RealmQuery<GroupEntity> { - return realm.where<GroupEntity>() - .equalTo(GroupEntityFields.GROUP_ID, groupId) -} - -internal fun GroupEntity.Companion.where(realm: Realm, memberships: List<Membership>): RealmQuery<GroupEntity> { - return realm.where<GroupEntity>().process(GroupEntityFields.MEMBERSHIP_STR, memberships) -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/GroupSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/GroupSummaryEntityQueries.kt deleted file mode 100644 index 8131598d95b2de54c0f8a771304c9fce8c5b447a..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/GroupSummaryEntityQueries.kt +++ /dev/null @@ -1,41 +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.database.query - -import io.realm.Realm -import io.realm.RealmQuery -import io.realm.kotlin.createObject -import io.realm.kotlin.where -import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity -import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields - -internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupId: String? = null): RealmQuery<GroupSummaryEntity> { - val query = realm.where<GroupSummaryEntity>() - if (groupId != null) { - query.equalTo(GroupSummaryEntityFields.GROUP_ID, groupId) - } - return query -} - -internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupIds: List<String>): RealmQuery<GroupSummaryEntity> { - return realm.where<GroupSummaryEntity>() - .`in`(GroupSummaryEntityFields.GROUP_ID, groupIds.toTypedArray()) -} - -internal fun GroupSummaryEntity.Companion.getOrCreate(realm: Realm, groupId: String): GroupSummaryEntity { - return where(realm, groupId).findFirst() ?: realm.createObject(groupId) -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt index d69f251f6ffbf769227e29b722d0ce93a52fd72c..a1179ccdce2f6d6a8f4bd94b21abc2c98912e365 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt @@ -23,6 +23,14 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where( + realm: Realm, + eventId: String, +): RealmQuery<LiveLocationShareAggregatedSummaryEntity> { + return realm.where<LiveLocationShareAggregatedSummaryEntity>() + .equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId) +} + internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where( realm: Realm, roomId: String, @@ -72,17 +80,26 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get( return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst() } +internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get( + realm: Realm, + eventId: String, +): LiveLocationShareAggregatedSummaryEntity? { + return LiveLocationShareAggregatedSummaryEntity.where(realm, eventId).findFirst() +} + internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveInRoomForUser( realm: Realm, roomId: String, userId: String, ignoredEventId: String, + startOfLiveTimestampThreshold: Long, ): List<LiveLocationShareAggregatedSummaryEntity> { return LiveLocationShareAggregatedSummaryEntity .whereRoomId(realm, roomId = roomId) .equalTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, userId) .equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) .notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId) + .lessThan(LiveLocationShareAggregatedSummaryEntityFields.START_OF_LIVE_TIMESTAMP_MILLIS, startOfLiveTimestampThreshold) .findAll() .toList() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index b5b46a3f5ae5b89c82364f2765ce9ddd84fed033..113e780e5ce56a1c7be53c02b5e91a3d9b4023de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -21,6 +21,7 @@ import com.squareup.moshi.Moshi import dagger.Module import dagger.Provides import okhttp3.ConnectionSpec +import okhttp3.Dispatcher import okhttp3.OkHttpClient import okhttp3.Protocol import okhttp3.logging.HttpLoggingInterceptor @@ -73,7 +74,9 @@ internal object NetworkModule { apiInterceptor: ApiInterceptor ): OkHttpClient { val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build() - + val dispatcher = Dispatcher().apply { + maxRequestsPerHost = 20 + } return OkHttpClient.Builder() // workaround for #4669 .protocols(listOf(Protocol.HTTP_1_1)) @@ -94,6 +97,7 @@ internal object NetworkModule { addInterceptor(curlLoggingInterceptor) } } + .dispatcher(dispatcher) .connectionSpecs(Collections.singletonList(spec)) .applyMatrixConfiguration(matrixConfiguration) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt index 00cbe0aa85ebb394c02e205be79ad84583557721..c6ea2bc7bd512a47fc635592164457b35caa2f68 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt @@ -20,6 +20,7 @@ import io.realm.RealmList import io.realm.RealmObject import io.realm.RealmObjectSchema import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.util.fatalError internal fun RealmObject.assertIsManaged() { check(isManaged) { "${javaClass.simpleName} entity should be managed to use this function" } @@ -27,10 +28,19 @@ internal fun RealmObject.assertIsManaged() { /** * Clear a RealmList by deleting all its items calling the provided lambda. + * The lambda is supposed to delete the item, which means that after this operation, the list will be empty. */ internal fun <T> RealmList<T>.clearWith(delete: (T) -> Unit) { - while (!isEmpty()) { - first()?.let { delete.invoke(it) } + map { item -> + // Create a lambda for all items of the list + { delete(item) } + }.forEach { lambda -> + // Then invoke all the lambda + lambda.invoke() + } + + if (isNotEmpty()) { + fatalError("`clearWith` MUST delete all elements of the RealmList") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt index 56d9cc21439ca6ab0081b53d02a1f2048f410f76..7d52d9b2bfdb353b76bc64f58ada383446edf84e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt @@ -20,6 +20,7 @@ import android.content.Context import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.runBlocking +import org.matrix.android.sdk.api.auth.LoginType import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.DiscoveryInformation import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -145,7 +146,8 @@ internal class DefaultLegacySessionImporter @Inject constructor( forceUsageTlsVersions = legacyConfig.forceUsageOfTlsVersions() ), // If token is not valid, this boolean will be updated later - isTokenValid = true + isTokenValid = true, + loginType = LoginType.UNKNOWN, ) Timber.d("Migration: save session") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt index f75fb01746609097d9c4bb9a6136ae6a95a76282..90d2719e250277a021135dcfb6339fcca957acf7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt @@ -70,7 +70,15 @@ internal class PreferredNetworkCallbackStrategy @Inject constructor(context: Con override fun register(hasChanged: () -> Unit) { hasChangedCallback = hasChanged - conn.registerDefaultNetworkCallback(networkCallback) + // Add a try catch for safety + // XXX: It happens when running all tests in CI, at some points we reach a limit here causing TooManyRequestsException + // and crashing the sync thread. We might have problem here, would need some investigation + // for now adding a catch to allow CI to continue running + try { + conn.registerDefaultNetworkCallback(networkCallback) + } catch (t: Throwable) { + Timber.e(t, "Unable to register default network callback") + } } override fun unregister() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt index a9dfd47b5a46a42ff94443d3d6aa00b51904956b..575afa84944b18ff780d089376fe2cae5d8b92a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt @@ -17,12 +17,14 @@ package org.matrix.android.sdk.internal.raw import io.realm.DynamicRealm -import io.realm.RealmMigration import org.matrix.android.sdk.internal.raw.migration.MigrateGlobalTo001 -import timber.log.Timber +import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject -internal class GlobalRealmMigration @Inject constructor() : RealmMigration { +internal class GlobalRealmMigration @Inject constructor() : MatrixRealmMigration( + dbName = "Global", + schemaVersion = 1L, +) { /** * Forces all GlobalRealmMigration instances to be equal. * Avoids Realm throwing when multiple instances of the migration are set. @@ -30,11 +32,7 @@ internal class GlobalRealmMigration @Inject constructor() : RealmMigration { override fun equals(other: Any?) = other is GlobalRealmMigration override fun hashCode() = 2000 - val schemaVersion = 1L - - override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { - Timber.d("Migrating Global Realm from $oldVersion to $newVersion") - + override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { if (oldVersion < 1) MigrateGlobalTo001(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 7c50a0ff84ede28af924cbcf0c1856206b8e46fa..57db187bdc8047a9dc7eddf36dedcaf4f6cd2cf1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.FileService -import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService @@ -97,7 +96,6 @@ internal class DefaultSession @Inject constructor( private val sessionListeners: SessionListeners, private val roomService: Lazy<RoomService>, private val roomDirectoryService: Lazy<RoomDirectoryService>, - private val groupService: Lazy<GroupService>, private val userService: Lazy<UserService>, private val filterService: Lazy<FilterService>, private val federationService: Lazy<FederationService>, @@ -209,7 +207,6 @@ internal class DefaultSession @Inject constructor( override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = homeServerCapabilitiesService.get() override fun roomService(): RoomService = roomService.get() override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get() - override fun groupService(): GroupService = groupService.get() override fun userService(): UserService = userService.get() override fun signOutService(): SignOutService = signOutService.get() override fun filterService(): FilterService = filterService.get() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index d3cae3ac2d84cc85bb474608f9daa910408fdd36..a79f35bcb648add749ce68b4e32f890019801b78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -35,8 +35,6 @@ import org.matrix.android.sdk.internal.session.content.ContentModule import org.matrix.android.sdk.internal.session.content.UploadContentWorker import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerModule import org.matrix.android.sdk.internal.session.filter.FilterModule -import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker -import org.matrix.android.sdk.internal.session.group.GroupModule import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesModule import org.matrix.android.sdk.internal.session.identity.IdentityModule import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule @@ -74,10 +72,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule SyncModule::class, HomeServerCapabilitiesModule::class, SignOutModule::class, - GroupModule::class, UserModule::class, FilterModule::class, - GroupModule::class, ContentModule::class, CacheModule::class, MediaModule::class, @@ -124,8 +120,6 @@ internal interface SessionComponent { fun inject(worker: RedactEventWorker) - fun inject(worker: GetGroupDataWorker) - fun inject(worker: UploadContentWorker) fun inject(worker: SyncWorker) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index f8a52f0b7ed5ac51f4473a32f9a6b607fde41d7b..b9f56cbc9f1af3df96572d12c534c506bfa29314 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -88,6 +88,7 @@ import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationPro import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor +import org.matrix.android.sdk.internal.session.room.location.LiveLocationShareRedactionEventProcessor import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessorCoroutine @@ -321,6 +322,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventRedactionProcessor(processor: RedactionEventProcessor): EventInsertLiveProcessor + @Binds + @IntoSet + abstract fun bindLiveLocationShareRedactionEventProcessor(processor: LiveLocationShareRedactionEventProcessor): EventInsertLiveProcessor + @Binds @IntoSet abstract fun bindEventRelationsAggregationProcessor(processor: EventRelationsAggregationProcessor): EventInsertLiveProcessor diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/displayname/DisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/displayname/DisplayNameResolver.kt index 76d956f9a5cc914b95379d100c4113223826bc01..3b0799438eb2e5f1437f0d8b58f52bc537e02ff6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/displayname/DisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/displayname/DisplayNameResolver.kt @@ -25,7 +25,7 @@ internal class DisplayNameResolver @Inject constructor( private val matrixConfiguration: MatrixConfiguration ) { fun getBestName(matrixItem: MatrixItem): String { - return if (matrixItem is MatrixItem.GroupItem || matrixItem is MatrixItem.RoomAliasItem) { + return if (matrixItem is MatrixItem.RoomAliasItem) { // Best name is the id, and we keep the displayName of the room for the case we need the first letter matrixItem.id } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt deleted file mode 100644 index 9334d09377228be0ff3bd5d0050a9fd4499bbd4c..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt +++ /dev/null @@ -1,80 +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.session.group - -import androidx.lifecycle.LiveData -import com.zhuinden.monarchy.Monarchy -import io.realm.Realm -import io.realm.RealmQuery -import org.matrix.android.sdk.api.session.group.Group -import org.matrix.android.sdk.api.session.group.GroupService -import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams -import org.matrix.android.sdk.api.session.group.model.GroupSummary -import org.matrix.android.sdk.internal.database.mapper.asDomain -import org.matrix.android.sdk.internal.database.model.GroupEntity -import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity -import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.query.QueryStringValueProcessor -import org.matrix.android.sdk.internal.query.process -import org.matrix.android.sdk.internal.util.fetchCopyMap -import javax.inject.Inject - -internal class DefaultGroupService @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, - private val groupFactory: GroupFactory, - private val queryStringValueProcessor: QueryStringValueProcessor, -) : GroupService { - - override fun getGroup(groupId: String): Group? { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> - GroupEntity.where(realm, groupId).findFirst()?.let { - groupFactory.create(groupId) - } - } - } - - override fun getGroupSummary(groupId: String): GroupSummary? { - return monarchy.fetchCopyMap( - { realm -> GroupSummaryEntity.where(realm, groupId).findFirst() }, - { it, _ -> it.asDomain() } - ) - } - - override fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List<GroupSummary> { - return monarchy.fetchAllMappedSync( - { groupSummariesQuery(it, groupSummaryQueryParams) }, - { it.asDomain() } - ) - } - - override fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>> { - return monarchy.findAllMappedWithChanges( - { groupSummariesQuery(it, groupSummaryQueryParams) }, - { it.asDomain() } - ) - } - - private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery<GroupSummaryEntity> { - return with(queryStringValueProcessor) { - GroupSummaryEntity.where(realm) - .process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - .process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt deleted file mode 100644 index 235291d0617251bc28925e872fc7e555f9c09e1e..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt +++ /dev/null @@ -1,110 +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.session.group - -import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.internal.database.model.GroupEntity -import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity -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.SessionDatabase -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.session.group.model.GroupRooms -import org.matrix.android.sdk.internal.session.group.model.GroupSummaryResponse -import org.matrix.android.sdk.internal.session.group.model.GroupUsers -import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.awaitTransaction -import timber.log.Timber -import javax.inject.Inject - -internal interface GetGroupDataTask : Task<GetGroupDataTask.Params, Unit> { - sealed class Params { - object FetchAllActive : Params() - data class FetchWithIds(val groupIds: List<String>) : Params() - } -} - -internal class DefaultGetGroupDataTask @Inject constructor( - private val groupAPI: GroupAPI, - @SessionDatabase private val monarchy: Monarchy, - private val globalErrorReceiver: GlobalErrorReceiver -) : GetGroupDataTask { - - private data class GroupData( - val groupId: String, - val groupSummary: GroupSummaryResponse, - val groupRooms: GroupRooms, - val groupUsers: GroupUsers - ) - - override suspend fun execute(params: GetGroupDataTask.Params) { - val groupIds = when (params) { - is GetGroupDataTask.Params.FetchAllActive -> { - getActiveGroupIds() - } - is GetGroupDataTask.Params.FetchWithIds -> { - params.groupIds - } - } - Timber.v("Fetch data for group with ids: ${groupIds.joinToString(";")}") - val data = groupIds.map { groupId -> - val groupSummary = executeRequest(globalErrorReceiver) { - groupAPI.getSummary(groupId) - } - val groupRooms = executeRequest(globalErrorReceiver) { - groupAPI.getRooms(groupId) - } - val groupUsers = executeRequest(globalErrorReceiver) { - groupAPI.getUsers(groupId) - } - GroupData(groupId, groupSummary, groupRooms, groupUsers) - } - insertInDb(data) - } - - private fun getActiveGroupIds(): List<String> { - return monarchy.fetchAllMappedSync( - { realm -> - GroupEntity.where(realm, Membership.activeMemberships()) - }, - { it.groupId } - ) - } - - private suspend fun insertInDb(groupDataList: List<GroupData>) { - monarchy - .awaitTransaction { realm -> - groupDataList.forEach { groupData -> - - val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupData.groupId) - - groupSummaryEntity.avatarUrl = groupData.groupSummary.profile?.avatarUrl ?: "" - val name = groupData.groupSummary.profile?.name - groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupData.groupId else name - groupSummaryEntity.shortDescription = groupData.groupSummary.profile?.shortDescription ?: "" - - groupSummaryEntity.roomIds.clear() - groupData.groupRooms.rooms.mapTo(groupSummaryEntity.roomIds) { it.roomId } - - groupSummaryEntity.userIds.clear() - groupData.groupUsers.users.mapTo(groupSummaryEntity.userIds) { it.userId } - } - } - } -} 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 deleted file mode 100644 index 21582cb4bed7b2867768c73e768b44b469d08f96..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt +++ /dev/null @@ -1,59 +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.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 -import javax.inject.Inject - -/** - * Possible previous worker: None. - * Possible next worker : None. - */ -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( - override val sessionId: String, - override val lastFailureMessage: String? = null - ) : SessionWorkerParams - - @Inject lateinit var getGroupDataTask: GetGroupDataTask - - override fun injectWith(injector: SessionComponent) { - injector.inject(this) - } - - override suspend fun doSafeWork(params: Params): Result { - return runCatching { - getGroupDataTask.execute(GetGroupDataTask.Params.FetchAllActive) - }.fold( - { Result.success() }, - { Result.retry() } - ) - } - - override fun buildErrorParams(params: Params, message: String): Params { - return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt deleted file mode 100644 index c9d25b910441388d331af54a81c1da767d16a5b6..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt +++ /dev/null @@ -1,51 +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.session.group - -import org.matrix.android.sdk.internal.network.NetworkConstants -import org.matrix.android.sdk.internal.session.group.model.GroupRooms -import org.matrix.android.sdk.internal.session.group.model.GroupSummaryResponse -import org.matrix.android.sdk.internal.session.group.model.GroupUsers -import retrofit2.http.GET -import retrofit2.http.Path - -internal interface GroupAPI { - - /** - * Request a group summary. - * - * @param groupId the group id - */ - @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary") - suspend fun getSummary(@Path("groupId") groupId: String): GroupSummaryResponse - - /** - * Request the rooms list. - * - * @param groupId the group id - */ - @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms") - suspend fun getRooms(@Path("groupId") groupId: String): GroupRooms - - /** - * Request the users list. - * - * @param groupId the group id - */ - @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/users") - suspend fun getUsers(@Path("groupId") groupId: String): GroupUsers -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupFactory.kt deleted file mode 100644 index 653d2a69331615b24ba985dfaa85ca6ffb11d6ce..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupFactory.kt +++ /dev/null @@ -1,37 +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.session.group - -import org.matrix.android.sdk.api.session.group.Group -import org.matrix.android.sdk.internal.session.SessionScope -import javax.inject.Inject - -internal interface GroupFactory { - fun create(groupId: String): Group -} - -@SessionScope -internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask) : - GroupFactory { - - override fun create(groupId: String): Group { - return DefaultGroup( - groupId = groupId, - getGroupDataTask = getGroupDataTask - ) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupModule.kt deleted file mode 100644 index 4dd61aa91410115403a2a7c13e6e188af0341796..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupModule.kt +++ /dev/null @@ -1,47 +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.session.group - -import dagger.Binds -import dagger.Module -import dagger.Provides -import org.matrix.android.sdk.api.session.group.GroupService -import org.matrix.android.sdk.internal.session.SessionScope -import retrofit2.Retrofit - -@Module -internal abstract class GroupModule { - - @Module - companion object { - @Provides - @JvmStatic - @SessionScope - fun providesGroupAPI(retrofit: Retrofit): GroupAPI { - return retrofit.create(GroupAPI::class.java) - } - } - - @Binds - abstract fun bindGroupFactory(factory: DefaultGroupFactory): GroupFactory - - @Binds - abstract fun bindGetGroupDataTask(task: DefaultGetGroupDataTask): GetGroupDataTask - - @Binds - abstract fun bindGroupService(service: DefaultGroupService): GroupService -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupProfile.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupProfile.kt deleted file mode 100644 index 30eeb74b2e28d60236f3159b8045735331425462..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupProfile.kt +++ /dev/null @@ -1,49 +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.session.group.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/** - * This class represents a community profile in the server responses. - */ -@JsonClass(generateAdapter = true) -internal data class GroupProfile( - - @Json(name = "short_description") val shortDescription: String? = null, - - /** - * Tell whether the group is public. - */ - @Json(name = "is_public") val isPublic: Boolean? = null, - - /** - * The URL for the group's avatar. May be nil. - */ - @Json(name = "avatar_url") val avatarUrl: String? = null, - - /** - * The group's name. - */ - @Json(name = "name") val name: String? = null, - - /** - * The optional HTML formatted string used to described the group. - */ - @Json(name = "long_description") val longDescription: String? = null -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupRoom.kt deleted file mode 100644 index 86e64f6797841b399b59467c13f496c583b03c0c..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupRoom.kt +++ /dev/null @@ -1,35 +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.session.group.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -internal data class GroupRoom( - - @Json(name = "aliases") val aliases: List<String> = emptyList(), - @Json(name = "canonical_alias") val canonicalAlias: String? = null, - @Json(name = "name") val name: String? = null, - @Json(name = "num_joined_members") val numJoinedMembers: Int = 0, - @Json(name = "room_id") val roomId: String, - @Json(name = "topic") val topic: String? = null, - @Json(name = "world_readable") val worldReadable: Boolean = false, - @Json(name = "guest_can_join") val guestCanJoin: Boolean = false, - @Json(name = "avatar_url") val avatarUrl: String? = null - -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryResponse.kt deleted file mode 100644 index bf287e982c09b4cfcbd7918d385a52d2dcb67b7b..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryResponse.kt +++ /dev/null @@ -1,46 +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.session.group.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/** - * This class represents the summary of a community in the server response. - */ -@JsonClass(generateAdapter = true) -internal data class GroupSummaryResponse( - /** - * The group profile. - */ - @Json(name = "profile") val profile: GroupProfile? = null, - - /** - * The group users. - */ - @Json(name = "users_section") val usersSection: GroupSummaryUsersSection? = null, - - /** - * The current user status. - */ - @Json(name = "user") val user: GroupSummaryUser? = null, - - /** - * The rooms linked to the community. - */ - @Json(name = "rooms_section") val roomsSection: GroupSummaryRoomsSection? = null -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryRoomsSection.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryRoomsSection.kt deleted file mode 100644 index 87d07167ce2aad46469c1cc8bb95a54b1346b182..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryRoomsSection.kt +++ /dev/null @@ -1,34 +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.session.group.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/** - * This class represents the community rooms in a group summary response. - */ -@JsonClass(generateAdapter = true) -internal data class GroupSummaryRoomsSection( - - @Json(name = "total_room_count_estimate") val totalRoomCountEstimate: Int? = null, - - @Json(name = "rooms") val rooms: List<String> = emptyList() - - // TODO Check the meaning and the usage of these categories. This dictionary is empty FTM. - // public Map<Object, Object> categories; -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUser.kt deleted file mode 100644 index 121ae6fd7d6833df5e2452169c9f970ea97c91ec..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUser.kt +++ /dev/null @@ -1,37 +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.session.group.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/** - * This class represents the current user status in a group summary response. - */ -@JsonClass(generateAdapter = true) -internal data class GroupSummaryUser( - - /** - * The current user membership in this community. - */ - @Json(name = "membership") val membership: String? = null, - - /** - * Tell whether the user published this community on his profile. - */ - @Json(name = "is_publicised") val isPublicised: Boolean? = null -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUsersSection.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUsersSection.kt deleted file mode 100644 index 63608c582a66db0d2f7a96899999235583e747e3..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUsersSection.kt +++ /dev/null @@ -1,35 +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.session.group.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/** - * This class represents the community members in a group summary response. - */ - -@JsonClass(generateAdapter = true) -internal data class GroupSummaryUsersSection( - - @Json(name = "total_user_count_estimate") val totalUserCountEstimate: Int, - - @Json(name = "users") val users: List<String> = emptyList() - - // TODO Check the meaning and the usage of these roles. This dictionary is empty FTM. - // public Map<Object, Object> roles; -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupUser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupUser.kt deleted file mode 100644 index a54c66535eb4049a3e4cbd07165a6932664f48f4..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupUser.kt +++ /dev/null @@ -1,29 +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.session.group.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -internal data class GroupUser( - @Json(name = "display_name") val displayName: String = "", - @Json(name = "user_id") val userId: String, - @Json(name = "is_privileged") val isPrivileged: Boolean = false, - @Json(name = "avatar_url") val avatarUrl: String? = "", - @Json(name = "is_public") val isPublic: Boolean = false -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt index e731f9f3472e473b810bba0c3b9be2bb05d02a30..4756b41f4c8feb5ac2102ea2448da23742aa2260 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt @@ -17,12 +17,14 @@ package org.matrix.android.sdk.internal.session.identity.db import io.realm.DynamicRealm -import io.realm.RealmMigration import org.matrix.android.sdk.internal.session.identity.db.migration.MigrateIdentityTo001 -import timber.log.Timber +import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject -internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration { +internal class RealmIdentityStoreMigration @Inject constructor() : MatrixRealmMigration( + dbName = "Identity", + schemaVersion = 1L, +) { /** * Forces all RealmIdentityStoreMigration instances to be equal. * Avoids Realm throwing when multiple instances of the migration are set. @@ -30,11 +32,7 @@ internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigratio override fun equals(other: Any?) = other is RealmIdentityStoreMigration override fun hashCode() = 3000 - val schemaVersion = 1L - - override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { - Timber.d("Migrating Realm Identity from $oldVersion to $newVersion") - + override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { if (oldVersion < 1) MigrateIdentityTo001(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index 8e20199135a336d6a43e5edbf670370cc1e12706..3ecd47787f647c42c59898fb90a7fc1c09c8a2c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -97,7 +97,6 @@ internal class PermalinkFactory @Inject constructor( url.startsWith(MATRIX_TO_URL_BASE) -> url.substring(MATRIX_TO_URL_BASE.length) clientBaseUrl != null && url.startsWith(clientBaseUrl) -> { when (PermalinkParser.parse(url)) { - is PermalinkData.GroupLink -> url.substring(clientBaseUrl.length + GROUP_PATH.length) is PermalinkData.RoomLink -> url.substring(clientBaseUrl.length + ROOM_PATH.length) is PermalinkData.UserLink -> url.substring(clientBaseUrl.length + USER_PATH.length) else -> null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 5e6d052443d0c54b0742eaf3f12692bf51592d74..989bcaee4474ad2b8bf33f944ed95d33e0c4f2f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -43,7 +43,9 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask +import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask +import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask @@ -60,6 +62,8 @@ import javax.inject.Inject internal class DefaultRoomService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val createRoomTask: CreateRoomTask, + private val createLocalRoomTask: CreateLocalRoomTask, + private val deleteLocalRoomTask: DeleteLocalRoomTask, private val joinRoomTask: JoinRoomTask, private val markAllRoomsReadTask: MarkAllRoomsReadTask, private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, @@ -78,6 +82,14 @@ internal class DefaultRoomService @Inject constructor( return createRoomTask.executeRetry(createRoomParams, 3) } + override suspend fun createLocalRoom(createRoomParams: CreateRoomParams): String { + return createLocalRoomTask.execute(createRoomParams) + } + + override suspend fun deleteLocalRoom(roomId: String) { + deleteLocalRoomTask.execute(DeleteLocalRoomTask.Params(roomId)) + } + override fun getRoom(roomId: String): Room? { return roomGetter.getRoom(roomId) } @@ -140,9 +152,8 @@ internal class DefaultRoomService @Inject constructor( queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder, - getFlattenParents: Boolean ): UpdatableLivePageResult { - return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenParents) + return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder) } override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index ac2880de69946da33fc1ddb58db47ce03a2d33f2..9bcb7b8e4c7fba65c7171c1b16c95ca0bfaa6fac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -377,7 +377,7 @@ internal interface RoomAPI { * Get a list of aliases maintained by the local server for the given room. * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases */ - @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases") + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/aliases") suspend fun getAliases(@Path("roomId") roomId: String): GetAliasesResponse /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..bcbc53f95e2e9ad6a087466eb9f8819027b16aa5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomDataSource.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2022 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.session.room + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import javax.inject.Inject + +internal class RoomDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, +) { + fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType { + var result: RoomMembersLoadStatusType? + Realm.getInstance(monarchy.realmConfiguration).use { + result = RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus + } + return result ?: RoomMembersLoadStatusType.NONE + } + + fun getRoomMembersLoadStatusLive(roomId: String): LiveData<Boolean> { + val liveData = monarchy.findAllMappedWithChanges( + { + RoomEntity.where(it, roomId) + }, + { + it.membersLoadStatus == RoomMembersLoadStatusType.LOADED + } + ) + + return Transformations.map(liveData) { results -> + results.firstOrNull().orFalse() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index c4d37d124b2f2d664762205f25742d79ba315dd2..d01324a35f91b57cb69596653fdf972d77db838b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -43,8 +43,12 @@ import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAli import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask +import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask +import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask +import org.matrix.android.sdk.internal.session.room.delete.DefaultDeleteLocalRoomTask +import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDirectoryVisibilityTask @@ -54,11 +58,13 @@ import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVi import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.internal.session.room.location.DefaultRedactLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.DefaultSendLiveLocationTask import org.matrix.android.sdk.internal.session.room.location.DefaultSendStaticLocationTask import org.matrix.android.sdk.internal.session.room.location.DefaultStartLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.DefaultStopLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.internal.session.room.location.RedactLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.SendLiveLocationTask import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask @@ -204,6 +210,12 @@ internal abstract class RoomModule { @Binds abstract fun bindCreateRoomTask(task: DefaultCreateRoomTask): CreateRoomTask + @Binds + abstract fun bindCreateLocalRoomTask(task: DefaultCreateLocalRoomTask): CreateLocalRoomTask + + @Binds + abstract fun bindDeleteLocalRoomTask(task: DefaultDeleteLocalRoomTask): DeleteLocalRoomTask + @Binds abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask @@ -329,4 +341,7 @@ internal abstract class RoomModule { @Binds abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask + + @Binds + abstract fun bindRedactLiveLocationShareTask(task: DefaultRedactLiveLocationShareTask): RedactLiveLocationShareTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt index 921749122b3e511dcf7246ab05f3fc9161d75d03..510c20497b1aee8e1605f776447c563271f091b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import androidx.work.ExistingWorkPolicy import io.realm.Realm +import io.realm.RealmList import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toContent @@ -73,16 +74,22 @@ internal class LiveLocationAggregationProcessor @Inject constructor( eventId = targetEventId ) + if (!isLive && !event.eventId.isNullOrEmpty()) { + // in this case, the received event is a new state event related to the previous one + addRelatedEventId(event.eventId, aggregatedSummary) + } + // remote event can stay with isLive == true while the local summary is no more active val isActive = aggregatedSummary.isActive.orTrue() && isLive val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } Timber.d("updating summary of id=$targetEventId with isActive=$isActive and endTimestamp=$endOfLiveTimestampMillis") + aggregatedSummary.startOfLiveTimestampMillis = content.getBestTimestampMillis() aggregatedSummary.endOfLiveTimestampMillis = endOfLiveTimestampMillis aggregatedSummary.isActive = isActive aggregatedSummary.userId = event.senderId - deactivateAllPreviousBeacons(realm, roomId, event.senderId, targetEventId) + deactivateAllPreviousBeacons(realm, roomId, event.senderId, targetEventId, content.getBestTimestampMillis() ?: 0) if (isActive) { scheduleDeactivationAfterTimeout(targetEventId, roomId, endOfLiveTimestampMillis) @@ -144,6 +151,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor( roomId = roomId, eventId = relatedEventId ) + + if (!event.eventId.isNullOrEmpty()) { + addRelatedEventId(event.eventId, aggregatedSummary) + } + val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 val currentLocationTimestamp = ContentMapper .map(aggregatedSummary.lastLocationContent) @@ -160,13 +172,31 @@ internal class LiveLocationAggregationProcessor @Inject constructor( } } - private fun deactivateAllPreviousBeacons(realm: Realm, roomId: String, userId: String, currentEventId: String) { + private fun addRelatedEventId( + eventId: String, + aggregatedSummary: LiveLocationShareAggregatedSummaryEntity + ) { + Timber.d("adding related event id $eventId to summary of id ${aggregatedSummary.eventId}") + val updatedEventIds = aggregatedSummary.relatedEventIds.toMutableList().also { + it.add(eventId) + } + aggregatedSummary.relatedEventIds = RealmList(*updatedEventIds.toTypedArray()) + } + + private fun deactivateAllPreviousBeacons( + realm: Realm, + roomId: String, + userId: String, + currentEventId: String, + currentEventTimestamp: Long + ) { LiveLocationShareAggregatedSummaryEntity .findActiveLiveInRoomForUser( realm = realm, roomId = roomId, userId = userId, - ignoredEventId = currentEventId + ignoredEventId = currentEventId, + startOfLiveTimestampThreshold = currentEventTimestamp ) .forEach { it.isActive = false } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt index 1ff4156ed3e857b2439c007af34ca91018383587..62681c89d87ee90b67fdee2c05121adf51010ea0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt @@ -34,7 +34,6 @@ internal class DefaultGetRoomLocalAliasesTask @Inject constructor( ) : GetRoomLocalAliasesTask { override suspend fun execute(params: GetRoomLocalAliasesTask.Params): List<String> { - // We do not check for "org.matrix.msc2432", so the API may be missing val response = executeRequest(globalErrorReceiver) { roomAPI.getAliases(roomId = params.roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..d57491a4c86f22b92fd167518074cba4a54140db --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -0,0 +1,267 @@ +/* + * Copyright 2022 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.session.room.create + +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.kotlin.createObject +import kotlinx.coroutines.TimeoutCancellationException +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.events.model.Content +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.toContent +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.helper.addTimelineEvent +import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.getOrNull +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent +import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater +import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +internal interface CreateLocalRoomTask : Task<CreateRoomParams, String> + +internal class DefaultCreateLocalRoomTask @Inject constructor( + @UserId private val userId: String, + @SessionDatabase private val monarchy: Monarchy, + private val roomMemberEventHandler: RoomMemberEventHandler, + private val roomSummaryUpdater: RoomSummaryUpdater, + @SessionDatabase private val realmConfiguration: RealmConfiguration, + private val createRoomBodyBuilder: CreateRoomBodyBuilder, + private val userService: UserService, + private val clock: Clock, +) : CreateLocalRoomTask { + + override suspend fun execute(params: CreateRoomParams): String { + val createRoomBody = createRoomBodyBuilder.build(params.withDefault()) + val roomId = RoomLocalEcho.createLocalEchoId() + monarchy.awaitTransaction { realm -> + createLocalRoomEntity(realm, roomId, createRoomBody) + createLocalRoomSummaryEntity(realm, roomId, createRoomBody) + } + + // Wait for room to be created in DB + try { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) + } + } catch (exception: TimeoutCancellationException) { + throw CreateRoomFailure.CreatedWithTimeout(roomId) + } + + return roomId + } + + /** + * Create a local room entity from the given room creation params. + * This will also generate and store in database the chunk and the events related to the room params in order to retrieve and display the local room. + */ + private suspend fun createLocalRoomEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { + RoomEntity.getOrCreate(realm, roomId).apply { + membership = Membership.JOIN + chunks.add(createLocalRoomChunk(realm, roomId, createRoomBody)) + membersLoadStatus = RoomMembersLoadStatusType.LOADED + } + } + + private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { + val otherUserId = createRoomBody.getDirectUserId() + if (otherUserId != null) { + RoomSummaryEntity.getOrCreate(realm, roomId).apply { + isDirect = true + directUserId = otherUserId + } + } + roomSummaryUpdater.update( + realm = realm, + roomId = roomId, + membership = Membership.JOIN, + roomSummary = RoomSyncSummary( + heroes = createRoomBody.invitedUserIds.orEmpty().take(5), + joinedMembersCount = 1, + invitedMembersCount = createRoomBody.invitedUserIds?.size ?: 0 + ), + updateMembers = !createRoomBody.invitedUserIds.isNullOrEmpty() + ) + } + + /** + * Create a single chunk containing the necessary events to display the local room. + * + * @param realm the current instance of realm + * @param roomId the id of the local room + * @param createRoomBody the room creation params + * + * @return a chunk entity + */ + private suspend fun createLocalRoomChunk(realm: Realm, roomId: String, createRoomBody: CreateRoomBody): ChunkEntity { + val chunkEntity = realm.createObject<ChunkEntity>().apply { + isLastBackward = true + isLastForward = true + } + + val eventList = createLocalRoomEvents(createRoomBody) + val roomMemberContentsByUser = HashMap<String, RoomMemberContent?>() + + for (event in eventList) { + if (event.eventId == null || event.senderId == null || event.type == null) { + continue + } + + val now = clock.epochMillis() + val eventEntity = event.toEntity(roomId, SendState.SYNCED, now).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) + if (event.stateKey != null) { + CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { + eventId = event.eventId + root = eventEntity + } + if (event.type == EventType.STATE_ROOM_MEMBER) { + roomMemberContentsByUser[event.stateKey] = event.getFixedRoomMemberContent() + roomMemberEventHandler.handle(realm, roomId, event, false) + } + } + + roomMemberContentsByUser.getOrPut(event.senderId) { + // If we don't have any new state on this user, get it from db + val rootStateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, event.senderId, EventType.STATE_ROOM_MEMBER)?.root + rootStateEvent?.asDomain()?.getFixedRoomMemberContent() + } + + chunkEntity.addTimelineEvent( + roomId = roomId, + eventEntity = eventEntity, + direction = PaginationDirection.FORWARDS, + roomMemberContentsByUser = roomMemberContentsByUser + ) + } + + return chunkEntity + } + + /** + * Build the list of the events related to the room creation params. + * + * @param createRoomBody the room creation params + * + * @return the list of events + */ + private suspend fun createLocalRoomEvents(createRoomBody: CreateRoomBody): List<Event> { + val myUser = userService.getUser(userId) ?: User(userId) + val invitedUsers = createRoomBody.invitedUserIds.orEmpty() + .mapNotNull { tryOrNull { userService.resolveUser(it) } } + + val createRoomEvent = createLocalEvent( + type = EventType.STATE_ROOM_CREATE, + content = RoomCreateContent( + creator = userId + ).toContent() + ) + val myRoomMemberEvent = createLocalEvent( + type = EventType.STATE_ROOM_MEMBER, + content = RoomMemberContent( + membership = Membership.JOIN, + displayName = myUser.displayName, + avatarUrl = myUser.avatarUrl + ).toContent(), + stateKey = userId + ) + val roomMemberEvents = invitedUsers.map { + createLocalEvent( + type = EventType.STATE_ROOM_MEMBER, + content = RoomMemberContent( + isDirect = createRoomBody.isDirect.orFalse(), + membership = Membership.INVITE, + displayName = it.displayName, + avatarUrl = it.avatarUrl + ).toContent(), + stateKey = it.userId + ) + } + + return buildList { + add(createRoomEvent) + add(myRoomMemberEvent) + addAll(createRoomBody.initialStates.orEmpty().map { createLocalEvent(it.type, it.content, it.stateKey) }) + addAll(roomMemberEvents) + } + } + + /** + * Generate a local event from the given parameters. + * + * @param type the event type, see [EventType] + * @param content the content of the Event + * @param stateKey the stateKey, if any + * + * @return a fake event + */ + private fun createLocalEvent(type: String?, content: Content?, stateKey: String? = ""): Event { + return Event( + type = type, + senderId = userId, + stateKey = stateKey, + content = content, + originServerTs = clock.epochMillis(), + eventId = LocalEcho.createLocalEchoId() + ) + } + + /** + * Setup default values to the CreateRoomParams as the room is created locally (the default values will not be defined by the server). + */ + private fun CreateRoomParams.withDefault() = this.apply { + if (visibility == null) visibility = RoomDirectoryVisibility.PRIVATE + if (historyVisibility == null) historyVisibility = RoomHistoryVisibility.SHARED + if (guestAccess == null) guestAccess = GuestAccess.Forbidden + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt index cffa632768ca7f2e3fbc02645fa56c8f721877a8..b326c3618c46ab54d53037c05c3a4ed406258f97 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt @@ -120,3 +120,20 @@ internal data class CreateRoomBody( @Json(name = "room_version") val roomVersion: String? ) + +/** + * Tells if the created room can be a direct chat one. + * + * @return true if it is a direct chat + */ +private fun CreateRoomBody.isDirect(): Boolean { + return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT && isDirect == true +} + +internal fun CreateRoomBody.getDirectUserId(): String? { + return if (isDirect()) { + invitedUserIds?.firstOrNull() + ?: invite3pids?.firstOrNull()?.address + ?: throw IllegalStateException("You can't create a direct room without an invitedUser") + } else null +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 6dd2c91048519738626cd6e85fc5db4e2ff72aca..d76640573fc27916bae112cc71249e482cba167c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -62,11 +62,6 @@ internal class DefaultCreateRoomTask @Inject constructor( ) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val otherUserId = if (params.isDirect()) { - params.getFirstInvitedUserId() - ?: throw IllegalStateException("You can't create a direct room without an invitedUser") - } else null - if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) { try { aliasAvailabilityChecker.check(params.roomAliasName) @@ -111,14 +106,13 @@ internal class DefaultCreateRoomTask @Inject constructor( RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis() } - if (otherUserId != null) { - handleDirectChatCreation(roomId, otherUserId) - } + handleDirectChatCreation(roomId, createRoomBody.getDirectUserId()) setReadMarkers(roomId) return roomId } - private suspend fun handleDirectChatCreation(roomId: String, otherUserId: String) { + private suspend fun handleDirectChatCreation(roomId: String, otherUserId: String?) { + otherUserId ?: return // This is not a direct room monarchy.awaitTransaction { realm -> RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { this.directUserId = otherUserId @@ -133,21 +127,4 @@ internal class DefaultCreateRoomTask @Inject constructor( val setReadMarkerParams = SetReadMarkersTask.Params(roomId, forceReadReceipt = true, forceReadMarker = true) return readMarkersTask.execute(setReadMarkerParams) } - - /** - * Tells if the created room can be a direct chat one. - * - * @return true if it is a direct chat - */ - private fun CreateRoomParams.isDirect(): Boolean { - return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT && - isDirect == true - } - - /** - * @return the first invited user id - */ - private fun CreateRoomParams.getFirstInvitedUserId(): String? { - return invitedUserIds.firstOrNull() ?: invite3pids.firstOrNull()?.value - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..936c94e520f6be63319ef5446ff643045bbd9b1d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2022 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.session.room.delete + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho +import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.deleteOnCascade +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask.Params +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import timber.log.Timber +import javax.inject.Inject + +internal interface DeleteLocalRoomTask : Task<Params, Unit> { + data class Params(val roomId: String) +} + +internal class DefaultDeleteLocalRoomTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, +) : DeleteLocalRoomTask { + + override suspend fun execute(params: Params) { + val roomId = params.roomId + + if (RoomLocalEcho.isLocalEchoId(roomId)) { + monarchy.awaitTransaction { realm -> + Timber.i("## DeleteLocalRoomTask - delete local room id $roomId") + RoomMemberSummaryEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - RoomMemberSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + CurrentStateEventEntity.whereRoomId(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - CurrentStateEventEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + EventEntity.whereRoomId(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - EventEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + TimelineEventEntity.whereRoomId(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - TimelineEventEntity - delete ${it.size} entries") } + ?.forEach { it.deleteOnCascade(true) } + ChunkEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - ChunkEntity - delete ${it.size} entries") } + ?.forEach { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } + RoomSummaryEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - RoomSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + RoomEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - RoomEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + } + } else { + Timber.i("## DeleteLocalRoomTask - Failed to remove room with id $roomId: not a local room") + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt index 20320cad230919b30ddfe0225e18999b69e4b265..60312071d7010f7859e6bcb915202d306d65a0cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt @@ -42,6 +42,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor( private val startLiveLocationShareTask: StartLiveLocationShareTask, private val stopLiveLocationShareTask: StopLiveLocationShareTask, private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask, + private val redactLiveLocationShareTask: RedactLiveLocationShareTask, private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper, ) : LocationSharingService { @@ -72,7 +73,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor( return sendLiveLocationTask.execute(params) } - override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult { + override suspend fun startLiveLocationShare(timeoutMillis: Long, description: String): UpdateLiveLocationShareResult { // Ensure to stop any active live before starting a new one if (checkIfExistingActiveLive()) { val result = stopLiveLocationShare() @@ -82,7 +83,8 @@ internal class DefaultLocationSharingService @AssistedInject constructor( } val params = StartLiveLocationShareTask.Params( roomId = roomId, - timeoutMillis = timeoutMillis + timeoutMillis = timeoutMillis, + description = description ) return startLiveLocationShareTask.execute(params) } @@ -101,6 +103,15 @@ internal class DefaultLocationSharingService @AssistedInject constructor( return stopLiveLocationShareTask.execute(params) } + override suspend fun redactLiveLocationShare(beaconInfoEventId: String, reason: String?) { + val params = RedactLiveLocationShareTask.Params( + roomId = roomId, + beaconInfoEventId = beaconInfoEventId, + reason = reason + ) + return redactLiveLocationShareTask.execute(params) + } + override fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> { return monarchy.findAllMappedWithChanges( { LiveLocationShareAggregatedSummaryEntity.findRunningLiveInRoom(it, roomId = roomId) }, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt new file mode 100644 index 0000000000000000000000000000000000000000..fa3479ed3c3155d625a499ff55f8d89890e92cc8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2022 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.session.room.location + +import io.realm.Realm +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.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor +import timber.log.Timber +import javax.inject.Inject + +/** + * Listens to the database for the insertion of any redaction event. + * Delete specifically the aggregated summary related to a redacted live location share event. + */ +internal class LiveLocationShareRedactionEventProcessor @Inject constructor() : EventInsertLiveProcessor { + + override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { + return eventType == EventType.REDACTION && insertType != EventInsertType.LOCAL_ECHO + } + + override suspend fun process(realm: Realm, event: Event) { + if (event.redacts.isNullOrBlank() || LocalEcho.isLocalEchoId(event.eventId.orEmpty())) { + return + } + + val redactedEvent = EventEntity.where(realm, eventId = event.redacts).findFirst() + ?: return + + if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) { + val liveSummary = LiveLocationShareAggregatedSummaryEntity.get(realm, eventId = redactedEvent.eventId) + + if (liveSummary != null) { + Timber.d("deleting live summary with id: ${liveSummary.eventId}") + liveSummary.deleteFromRealm() + val annotationsSummary = EventAnnotationsSummaryEntity.get(realm, eventId = redactedEvent.eventId) + if (annotationsSummary != null) { + Timber.d("deleting annotation summary with id: ${annotationsSummary.eventId}") + annotationsSummary.deleteFromRealm() + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..ac855b81e7ce701192f60eefe1313177bdbd5027 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 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.session.room.location + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.internal.database.awaitTransaction +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory +import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor +import org.matrix.android.sdk.internal.task.Task +import timber.log.Timber +import javax.inject.Inject + +internal interface RedactLiveLocationShareTask : Task<RedactLiveLocationShareTask.Params, Unit> { + data class Params( + val roomId: String, + val beaconInfoEventId: String, + val reason: String? + ) +} + +internal class DefaultRedactLiveLocationShareTask @Inject constructor( + @SessionDatabase private val realmConfiguration: RealmConfiguration, + private val localEchoEventFactory: LocalEchoEventFactory, + private val eventSenderProcessor: EventSenderProcessor, +) : RedactLiveLocationShareTask { + + override suspend fun execute(params: RedactLiveLocationShareTask.Params) { + val relatedEventIds = getRelatedEventIdsOfLive(params.beaconInfoEventId) + Timber.d("beacon with id ${params.beaconInfoEventId} has related event ids: ${relatedEventIds.joinToString(", ")}") + + postRedactionWithLocalEcho( + eventId = params.beaconInfoEventId, + roomId = params.roomId, + reason = params.reason + ) + relatedEventIds.forEach { eventId -> + postRedactionWithLocalEcho( + eventId = eventId, + roomId = params.roomId, + reason = params.reason + ) + } + } + + private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List<String> { + return awaitTransaction(realmConfiguration) { realm -> + val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get( + realm = realm, + eventId = beaconInfoEventId + ) + aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList() + } + } + + private fun postRedactionWithLocalEcho(eventId: String, roomId: String, reason: String?) { + Timber.d("posting redaction for event of id $eventId") + val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, eventId, reason) + localEchoEventFactory.createLocalEcho(redactionEcho) + eventSenderProcessor.postRedaction(redactionEcho, reason) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt index b943c279778b1ef043b7bc4222ee306fb791ac90..79019e47658123085b9abb3bcf632170251812a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt @@ -30,6 +30,7 @@ internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask. data class Params( val roomId: String, val timeoutMillis: Long, + val description: String, ) } @@ -41,6 +42,7 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor( override suspend fun execute(params: StartLiveLocationShareTask.Params): UpdateLiveLocationShareResult { val beaconContent = MessageBeaconInfoContent( + body = params.description, timeout = params.timeoutMillis, isLive = true, unstableTimestampMillis = clock.epochMillis() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index ef89ca33a723f71b52f60b82479573ad505fb40d..20708b38142f65c87df88e257d861cd783ffce69 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -17,24 +17,31 @@ package org.matrix.android.sdk.internal.session.room.membership import androidx.lifecycle.LiveData +import com.otaliastudios.opengl.core.use import com.zhuinden.monarchy.Monarchy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.realm.Realm import io.realm.RealmQuery +import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary +import org.matrix.android.sdk.internal.database.helper.findLatestSessionInfo import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process +import org.matrix.android.sdk.internal.session.room.RoomDataSource import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipAdminTask import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask @@ -47,8 +54,11 @@ internal class DefaultMembershipService @AssistedInject constructor( private val inviteTask: InviteTask, private val inviteThreePidTask: InviteThreePidTask, private val membershipAdminTask: MembershipAdminTask, + private val roomDataSource: RoomDataSource, + private val cryptoService: CryptoService, @UserId private val userId: String, + private val matrixConfiguration: MatrixConfiguration, private val queryStringValueProcessor: QueryStringValueProcessor ) : MembershipService { @@ -62,6 +72,15 @@ internal class DefaultMembershipService @AssistedInject constructor( loadRoomMembersTask.execute(params) } + override suspend fun areAllMembersLoaded(): Boolean { + val status = roomDataSource.getRoomMembersLoadStatus(roomId) + return status == RoomMembersLoadStatusType.LOADED + } + + override fun areAllMembersLoadedLive(): LiveData<Boolean> { + return roomDataSource.getRoomMembersLoadStatusLive(roomId) + } + override fun getRoomMember(userId: String): RoomMemberSummary? { val roomMemberEntity = monarchy.fetchCopied { RoomMemberHelper(it, roomId).getLastRoomMember(userId) @@ -127,10 +146,20 @@ internal class DefaultMembershipService @AssistedInject constructor( } override suspend fun invite(userId: String, reason: String?) { + sendShareHistoryKeysIfNeeded(userId) val params = InviteTask.Params(roomId, userId, reason) inviteTask.execute(params) } + private suspend fun sendShareHistoryKeysIfNeeded(userId: String) { + if (!cryptoService.isShareKeysOnInviteEnabled()) return + // TODO not sure it's the right way to get the latest messages in a room + val sessionInfo = Realm.getInstance(monarchy.realmConfiguration).use { + ChunkEntity.findLatestSessionInfo(it, roomId) + } + cryptoService.sendSharedHistoryKeys(roomId, userId, sessionInfo) + } + override suspend fun invite3pid(threePid: ThreePid) { val params = InviteThreePidTask.Params(roomId, threePid) return inviteThreePidTask.execute(params) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 7052eb23e247c99c4d195a0850588e3fdf85f334..c02049f40d9813022c4461f50aa1580d99848820 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.room.membership import com.zhuinden.monarchy.Monarchy -import io.realm.Realm import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.room.model.Membership @@ -38,6 +37,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.RoomDataSource import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.Task @@ -58,6 +58,7 @@ internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Unit> internal class DefaultLoadRoomMembersTask @Inject constructor( private val roomAPI: RoomAPI, @SessionDatabase private val monarchy: Monarchy, + private val roomDataSource: RoomDataSource, private val syncTokenStore: SyncTokenStore, private val roomSummaryUpdater: RoomSummaryUpdater, private val roomMemberEventHandler: RoomMemberEventHandler, @@ -68,7 +69,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { - when (getRoomMembersLoadStatus(params.roomId)) { + when (roomDataSource.getRoomMembersLoadStatus(params.roomId)) { RoomMembersLoadStatusType.NONE -> doRequest(params) RoomMembersLoadStatusType.LOADING -> waitPreviousRequestToFinish(params) RoomMembersLoadStatusType.LOADED -> Unit @@ -142,14 +143,6 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( } } - private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType { - var result: RoomMembersLoadStatusType? - Realm.getInstance(monarchy.realmConfiguration).use { - result = RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus - } - return result ?: RoomMembersLoadStatusType.NONE - } - private suspend fun setRoomMembersLoadStatus(roomId: String, status: RoomMembersLoadStatusType) { monarchy.awaitTransaction { realm -> val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) 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 e33fbb56b19d72b3124d880c6dd15cb9ede3b0a3..cc86679cbc79c8cbf2f0c5495420487a8f4dd0d7 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 @@ -74,6 +74,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr when (typeToPrune) { EventType.ENCRYPTED, EventType.MESSAGE, + in EventType.STATE_ROOM_BEACON_INFO, + in EventType.BEACON_LOCATION_DATA, in EventType.POLL_START -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") val unsignedData = EventMapper.map(eventToPrune).unsignedData 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 f52500de1b58638ba3d0c26021007653e7e10d78..4fbc91e9ec2d788036ce1ac3c5ca92d7af261458 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 @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.Bitmap import android.media.MediaMetadataRetriever import androidx.exifinterface.media.ExifInterface +import org.matrix.android.sdk.api.extensions.ensureNotEmpty import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event @@ -700,6 +701,7 @@ internal class LocalEchoEventFactory @Inject constructor( 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_BEACON_INFO -> return TextContent(content.body.ensureNotEmpty() ?: "Live location") MessageType.MSGTYPE_POLL_START -> { return TextContent((content as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt index c5b13043d7c5f49399ba4d18c624b5cb8c589494..51107c96557bd6737287e1d67f5b6fd1b8c6a02b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt @@ -119,7 +119,7 @@ internal class EventSenderProcessorThread @Inject constructor( override fun cancel(eventId: String, roomId: String) { (currentTask as? SendEventQueuedTask) - ?.takeIf { it -> it.event.eventId == eventId && it.event.roomId == roomId } + ?.takeIf { it.event.eventId == eventId && it.event.roomId == roomId } ?.cancel() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index cb7dc270e8c47085e6501631d0a30a77738ccc56..82fc94df7c8fcdb80714d5e684a70e47b54ebd63 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -200,14 +200,13 @@ internal class RoomSummaryDataSource @Inject constructor( queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder, - getFlattenedParents: Boolean = false ): UpdatableLivePageResult { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams).process(sortOrder) } val dataSourceFactory = realmDataSourceFactory.map { roomSummaryMapper.map(it) - }.map { if (getFlattenedParents) it.getWithParents() else it } + } val boundaries = MutableLiveData(ResultBoundaries()) @@ -246,13 +245,6 @@ internal class RoomSummaryDataSource @Inject constructor( } } - private fun RoomSummary.getWithParents(): RoomSummary { - val parents = flattenParentIds.mapNotNull { parentId -> - getRoomSummary(parentId) - } - return copy(flattenParents = parents) - } - fun getCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> { val liveRooms = monarchy.findAllManagedWithChanges { roomSummariesQuery(it, queryParams) @@ -325,12 +317,9 @@ internal class RoomSummaryDataSource @Inject constructor( is SpaceFilter.ExcludeSpace -> { query.not().contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.spaceFilter.spaceId) } - null -> Unit // nop + SpaceFilter.NoFilter -> Unit // nop } - queryParams.activeGroupId?.let { activeGroupId -> - query.contains(RoomSummaryEntityFields.GROUP_IDS, activeGroupId) - } return query } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index e4afe7aa49dadee1905ead0a64c499e3a1dcd33f..7e064a84ecf039a60b475b7f7104950be70d01ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -44,7 +44,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningSe import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity -import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields @@ -224,6 +223,7 @@ internal class RoomSummaryUpdater @Inject constructor( .sort(RoomSummaryEntityFields.ROOM_ID) .findAll().map { it.flattenParentIds = null + it.directParentNames.clear() it to emptyList<RoomSummaryEntity>().toMutableSet() } .toMap() @@ -351,39 +351,29 @@ internal class RoomSummaryUpdater @Inject constructor( } val acyclicGraph = graph.withoutEdges(backEdges) -// Timber.v("## SPACES: acyclicGraph $acyclicGraph") val flattenSpaceParents = acyclicGraph.flattenDestination().map { it.key.name to it.value.map { it.name } }.toMap() -// Timber.v("## SPACES: flattenSpaceParents ${flattenSpaceParents.map { it.key.name to it.value.map { it.name } }.joinToString("\n") { -// it.first + ": [" + it.second.joinToString(",") + "]" -// }}") - -// Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}") lookupMap.entries .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN } .forEach { entry -> val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst() if (parent != null) { -// Timber.v("## SPACES: check hierarchy of ${parent.name} id ${parent.roomId}") -// Timber.v("## SPACES: flat known parents of ${parent.name} are ${flattenSpaceParents[parent.roomId]}") val flattenParentsIds = (flattenSpaceParents[parent.roomId] ?: emptyList()) + listOf(parent.roomId) -// Timber.v("## SPACES: flatten known parents of children of ${parent.name} are ${flattenParentsIds}") + entry.value.forEach { child -> RoomSummaryEntity.where(realm, child.roomId).findFirst()?.let { childSum -> + childSum.directParentNames.add(parent.displayName()) -// Timber.w("## SPACES: ${childSum.name} is ${childSum.roomId} fc: ${childSum.flattenParentIds}") -// var allParents = childSum.flattenParentIds ?: "" - if (childSum.flattenParentIds == null) childSum.flattenParentIds = "" + if (childSum.flattenParentIds == null) { + childSum.flattenParentIds = "" + } flattenParentsIds.forEach { if (childSum.flattenParentIds?.contains(it) != true) { childSum.flattenParentIds += "|$it" } } -// childSum.flattenParentIds = "$allParents|" - -// Timber.v("## SPACES: flatten of ${childSum.name} is ${childSum.flattenParentIds}") } } } @@ -438,38 +428,6 @@ internal class RoomSummaryUpdater @Inject constructor( space.notificationCount = notificationCount } // xxx invites?? - - // LEGACY GROUPS - // lets mark rooms that belongs to groups - val existingGroups = GroupSummaryEntity.where(realm).findAll() - - // For rooms - realm.where(RoomSummaryEntity::class.java) - .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) - .equalTo(RoomSummaryEntityFields.IS_DIRECT, false) - .findAll().forEach { room -> - val belongsTo = existingGroups.filter { it.roomIds.contains(room.roomId) } - room.groupIds = if (belongsTo.isEmpty()) { - null - } else { - "|${belongsTo.joinToString("|")}|" - } - } - - // For DMS - realm.where(RoomSummaryEntity::class.java) - .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) - .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) - .findAll().forEach { room -> - val belongsTo = existingGroups.filter { - it.userIds.intersect(room.otherMemberIds).isNotEmpty() - } - room.groupIds = if (belongsTo.isEmpty()) { - null - } else { - "|${belongsTo.joinToString("|")}|" - } - } }.also { Timber.v("## SPACES: Finish checking room hierarchy in $it ms") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt index 37869b88f9d2688a0a53aba849998bf60aad173d..691dd7b20d31add110d5f2a251108ee7e44ab989 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.internal.session.sync -import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.api.session.sync.SyncService import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.WorkManagerProvider @@ -75,9 +73,7 @@ internal class DefaultSyncService @Inject constructor( override fun getSyncState() = getSyncThread().currentState() - override fun getSyncRequestStateLive(): LiveData<SyncRequestState> { - return syncRequestStateTracker.syncRequestState - } + override fun getSyncRequestStateFlow() = syncRequestStateTracker.syncRequestState override fun hasAlreadySynced(): Boolean { return syncTokenStore.getLastToken() != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt index bcc5fcf9abe0d059e76f993e7a24352301ba3e30..03ce8cb3f2dc4054b3dc84c8cd2a7d45e2df831f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt @@ -16,23 +16,26 @@ package org.matrix.android.sdk.internal.session.sync -import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.sync.InitialSyncStep import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.internal.session.SessionScope import javax.inject.Inject @SessionScope -internal class SyncRequestStateTracker @Inject constructor() : - ProgressReporter { +internal class SyncRequestStateTracker @Inject constructor( + private val coroutineScope: CoroutineScope +) : ProgressReporter { - val syncRequestState = MutableLiveData<SyncRequestState>() + val syncRequestState = MutableSharedFlow<SyncRequestState>() private var rootTask: TaskInfo? = null // Only to be used for incremental sync fun setSyncRequestState(newSyncRequestState: SyncRequestState.IncrementalSyncRequestState) { - syncRequestState.postValue(newSyncRequestState) + emitSyncState(newSyncRequestState) } /** @@ -42,7 +45,9 @@ internal class SyncRequestStateTracker @Inject constructor() : initialSyncStep: InitialSyncStep, totalProgress: Int ) { - endAll() + if (rootTask != null) { + endAll() + } rootTask = TaskInfo(initialSyncStep, totalProgress, null, 1F) reportProgress(0F) } @@ -71,7 +76,7 @@ internal class SyncRequestStateTracker @Inject constructor() : // Update the progress of the leaf and all its parents leaf.setProgress(progress) // Then update the live data using leaf wording and root progress - syncRequestState.postValue(SyncRequestState.InitialSyncProgressing(leaf.initialSyncStep, root.currentProgress.toInt())) + emitSyncState(SyncRequestState.InitialSyncProgressing(leaf.initialSyncStep, root.currentProgress.toInt())) } } } @@ -86,13 +91,19 @@ internal class SyncRequestStateTracker @Inject constructor() : // And close it endedTask.parent.child = null } else { - syncRequestState.postValue(SyncRequestState.Idle) + emitSyncState(SyncRequestState.Idle) } } } fun endAll() { rootTask = null - syncRequestState.postValue(SyncRequestState.Idle) + emitSyncState(SyncRequestState.Idle) + } + + private fun emitSyncState(state: SyncRequestState) { + coroutineScope.launch { + syncRequestState.emit(state) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 9e5302222ab3571e9c7964ce58752538874eb223..392c73bd835aadf4b316bcba5ab76950a758cf7c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -16,47 +16,36 @@ package org.matrix.android.sdk.internal.session.sync -import androidx.work.ExistingPeriodicWorkPolicy import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.pushrules.RuleScope import org.matrix.android.sdk.api.session.sync.InitialSyncStep -import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionId -import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.SessionListeners import org.matrix.android.sdk.internal.session.dispatchTo -import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler -import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler import org.matrix.android.sdk.internal.util.awaitTransaction -import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.system.measureTimeMillis -private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" - internal class SyncResponseHandler @Inject constructor( @SessionDatabase private val monarchy: Monarchy, @SessionId private val sessionId: String, private val sessionManager: SessionManager, private val sessionListeners: SessionListeners, - private val workManagerProvider: WorkManagerProvider, private val roomSyncHandler: RoomSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler, - private val groupSyncHandler: GroupSyncHandler, private val cryptoSyncHandler: CryptoSyncHandler, private val aggregatorHandler: SyncResponsePostTreatmentAggregatorHandler, private val cryptoService: DefaultCryptoService, @@ -109,7 +98,7 @@ internal class SyncResponseHandler @Inject constructor( // IMPORTANT nothing should be suspend here as we are accessing the realm instance (thread local) measureTimeMillis { Timber.v("Handle rooms") - reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.7f) { + reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.8f) { if (syncResponse.rooms != null) { roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator, reporter) } @@ -118,17 +107,6 @@ internal class SyncResponseHandler @Inject constructor( Timber.v("Finish handling rooms in $it ms") } - measureTimeMillis { - reportSubtask(reporter, InitialSyncStep.ImportingAccountGroups, 1, 0.1f) { - Timber.v("Handle groups") - if (syncResponse.groups != null) { - groupSyncHandler.handle(realm, syncResponse.groups, reporter) - } - } - }.also { - Timber.v("Finish handling groups in $it ms") - } - measureTimeMillis { reportSubtask(reporter, InitialSyncStep.ImportingAccountData, 1, 0.1f) { Timber.v("Handle accountData") @@ -155,9 +133,6 @@ internal class SyncResponseHandler @Inject constructor( userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) dispatchInvitedRoom(it) } - syncResponse.groups?.let { - scheduleGroupDataFetchingIfNeeded(it) - } Timber.v("On sync completed") cryptoSyncHandler.onSyncCompleted(syncResponse) @@ -177,31 +152,6 @@ internal class SyncResponseHandler @Inject constructor( } } - /** - * At the moment we don't get any group data through the sync, so we poll where every hour. - * You can also force to refetch group data using [Group] API. - */ - private fun scheduleGroupDataFetchingIfNeeded(groupsSyncResponse: GroupsSyncResponse) { - val groupIds = ArrayList<String>() - groupIds.addAll(groupsSyncResponse.join.keys) - groupIds.addAll(groupsSyncResponse.invite.keys) - if (groupIds.isEmpty()) { - Timber.v("No new groups to fetch data for.") - return - } - Timber.v("There are ${groupIds.size} new groups to fetch data for.") - val getGroupDataWorkerParams = GetGroupDataWorker.Params(sessionId) - val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) - - val getGroupWork = workManagerProvider.matrixPeriodicWorkRequestBuilder<GetGroupDataWorker>(1, TimeUnit.HOURS) - .setInputData(workData) - .setConstraints(WorkManagerProvider.workConstraints) - .build() - - workManagerProvider.workManager - .enqueueUniquePeriodicWork(GET_GROUP_DATA_WORKER, ExistingPeriodicWorkPolicy.REPLACE, getGroupWork) - } - private suspend fun checkPushRules(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean) { Timber.v("[PushRules] --> checkPushRules") if (isInitialSync) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt deleted file mode 100644 index 1983d9f433576e1ff5bd124e33b2a0b54c0de460..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt +++ /dev/null @@ -1,104 +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.session.sync.handler - -import io.realm.Realm -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.sync.InitialSyncStep -import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse -import org.matrix.android.sdk.api.session.sync.model.InvitedGroupSync -import org.matrix.android.sdk.internal.database.model.GroupEntity -import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity -import org.matrix.android.sdk.internal.database.query.getOrCreate -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.session.sync.ProgressReporter -import org.matrix.android.sdk.internal.session.sync.mapWithProgress -import javax.inject.Inject - -internal class GroupSyncHandler @Inject constructor() { - - sealed class HandlingStrategy { - data class JOINED(val data: Map<String, Any>) : HandlingStrategy() - data class INVITED(val data: Map<String, InvitedGroupSync>) : HandlingStrategy() - data class LEFT(val data: Map<String, Any>) : HandlingStrategy() - } - - fun handle( - realm: Realm, - roomsSyncResponse: GroupsSyncResponse, - reporter: ProgressReporter? = null - ) { - handleGroupSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter) - handleGroupSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter) - handleGroupSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter) - } - - // PRIVATE METHODS ***************************************************************************** - - private fun handleGroupSync(realm: Realm, handlingStrategy: HandlingStrategy, reporter: ProgressReporter?) { - val groups = when (handlingStrategy) { - is HandlingStrategy.JOINED -> - handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.6f) { - handleJoinedGroup(realm, it.key) - } - - is HandlingStrategy.INVITED -> - handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.3f) { - handleInvitedGroup(realm, it.key) - } - - is HandlingStrategy.LEFT -> - handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.1f) { - handleLeftGroup(realm, it.key) - } - } - realm.insertOrUpdate(groups) - } - - private fun handleJoinedGroup( - realm: Realm, - groupId: String - ): GroupEntity { - val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) - val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) - groupEntity.membership = Membership.JOIN - groupSummaryEntity.membership = Membership.JOIN - return groupEntity - } - - private fun handleInvitedGroup( - realm: Realm, - groupId: String - ): GroupEntity { - val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) - val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) - groupEntity.membership = Membership.INVITE - groupSummaryEntity.membership = Membership.INVITE - return groupEntity - } - - private fun handleLeftGroup( - realm: Realm, - groupId: String - ): GroupEntity { - val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) - val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) - groupEntity.membership = Membership.LEAVE - groupSummaryEntity.membership = Membership.LEAVE - return groupEntity - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/MatrixRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/MatrixRealmMigration.kt new file mode 100644 index 0000000000000000000000000000000000000000..4dff466de222f671d6ba6ce49e22050b058a17d8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/MatrixRealmMigration.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 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.util.database + +import io.realm.DynamicRealm +import io.realm.RealmMigration +import timber.log.Timber +import kotlin.system.measureTimeMillis + +internal abstract class MatrixRealmMigration( + private val dbName: String, + val schemaVersion: Long, +) : RealmMigration { + final override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { + Timber.d("Migrating Realm $dbName from $oldVersion to $newVersion") + val duration = measureTimeMillis { + doMigrate(realm, oldVersion) + } + Timber.d("Migrating Realm $dbName from $oldVersion to $newVersion took $duration ms.") + } + + abstract fun doMigrate(realm: DynamicRealm, oldVersion: Long) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt index 8da1bed97e0c5385e9aae4133fb278c16b078816..1992d3b918c1b5917e9be0d663257ef95ed98e55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.util.database import io.realm.DynamicRealm import io.realm.RealmObjectSchema import timber.log.Timber +import kotlin.system.measureTimeMillis internal abstract class RealmMigrator( private val realm: DynamicRealm, @@ -26,7 +27,10 @@ internal abstract class RealmMigrator( ) { fun perform() { Timber.d("Migrate ${realm.configuration.realmFileName} to $targetSchemaVersion") - doMigrate(realm) + val duration = measureTimeMillis { + doMigrate(realm) + } + Timber.d("Migrate ${realm.configuration.realmFileName} to $targetSchemaVersion took $duration ms.") } abstract fun doMigrate(realm: DynamicRealm) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/fatal.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/fatal.kt new file mode 100644 index 0000000000000000000000000000000000000000..7ed807d7cc7ef5644b568c9dae6e1b22a355a9a1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/fatal.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 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.util + +import org.matrix.android.sdk.BuildConfig +import timber.log.Timber + +/** + * Throws in debug, only log in production. + * As this method does not always throw, next statement should be a return. + */ +internal fun fatalError(message: String) { + if (BuildConfig.DEBUG) { + error(message) + } else { + Timber.e(message) + } +} 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 52146ef484b81746b89c66be779d260bbd1e1ef7..83f9532870217c7d0a98703c4d321fd0b9168911 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 @@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker 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.aggregation.livelocation.DeactivateLiveLocationShareWorker import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker @@ -53,8 +52,6 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage CheckFactoryWorker(appContext, workerParameters, true) AddPusherWorker::class.java.name -> AddPusherWorker(appContext, workerParameters, sessionManager) - GetGroupDataWorker::class.java.name -> - GetGroupDataWorker(appContext, workerParameters, sessionManager) MultipleEventSendingDispatcherWorker::class.java.name -> MultipleEventSendingDispatcherWorker(appContext, workerParameters, sessionManager) RedactEventWorker::class.java.name -> diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt index 0d0450adc2b9aab323a55c97a3627d0ddb8fbd81..ec587e0536ecea8af9978995e81c8127c69f909d 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt @@ -35,6 +35,23 @@ class MatrixPatternsTest { MatrixPatterns.isUserId(input) shouldBeEqualTo expected } } + + @Test + fun `given matrix id cases, when extracting userName, then returns expected`() { + val cases = listOf( + MatrixIdCase("foobar", userName = null), + MatrixIdCase("@foobar", userName = null), + MatrixIdCase("foobar@matrix.org", userName = null), + MatrixIdCase("@foobar: matrix.org", userName = null), + MatrixIdCase("foobar:matrix.org", userName = null), + MatrixIdCase("@foobar:matrix.org", userName = "foobar"), + ) + + cases.forEach { (input, expected) -> + MatrixPatterns.extractUserNameFromId(input) shouldBeEqualTo expected + } + } } private data class UserIdCase(val input: String, val isUserId: Boolean) +private data class MatrixIdCase(val input: String, val userName: String?) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo005Test.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo005Test.kt new file mode 100644 index 0000000000000000000000000000000000000000..95b226411b28983a45cc75c86f91de3306158f18 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo005Test.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.db.migration + +import org.junit.Test +import org.matrix.android.sdk.test.fakes.internal.auth.db.migration.Fake005MigrationRealm + +class MigrateAuthTo005Test { + + private val fakeRealm = Fake005MigrationRealm() + private val migrator = MigrateAuthTo005(fakeRealm.instance) + + @Test + fun `when doMigrate, then LoginType field added`() { + migrator.doMigrate(fakeRealm.instance) + + fakeRealm.verifyLoginTypeAdded() + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/login/LoginTypeTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/login/LoginTypeTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..495302acb25c4318e8f65b1d5991c24b3a94d923 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/login/LoginTypeTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.login + +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldNotBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.auth.LoginType + +class LoginTypeTest { + + @Test + fun `when getting type fromName, then map correctly`() { + LoginType.fromName(LoginType.PASSWORD.name) shouldBeEqualTo LoginType.PASSWORD + LoginType.fromName(LoginType.SSO.name) shouldBeEqualTo LoginType.SSO + LoginType.fromName(LoginType.UNSUPPORTED.name) shouldBeEqualTo LoginType.UNSUPPORTED + LoginType.fromName(LoginType.CUSTOM.name) shouldBeEqualTo LoginType.CUSTOM + LoginType.fromName(LoginType.DIRECT.name) shouldBeEqualTo LoginType.DIRECT + LoginType.fromName(LoginType.UNKNOWN.name) shouldBeEqualTo LoginType.UNKNOWN + } + + @Test // The failure of this test means that an existing type has not been correctly added to fromValue + fun `given non-unknown type name, when getting type fromName, then type is not UNKNOWN`() { + val types = LoginType.values() + + types.forEach { type -> + if (type != LoginType.UNKNOWN) { + LoginType.fromName(type.name) shouldNotBeEqualTo LoginType.UNKNOWN + } + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt index 933087af2b6027fb264ed584973dd882095fc85a..25d441ef5c2e901ed128dad0afdca3de97666843 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import androidx.work.ExistingWorkPolicy import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldContain import org.junit.Test import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.UnsignedData @@ -35,6 +36,7 @@ import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider import org.matrix.android.sdk.test.fakes.givenEqualTo import org.matrix.android.sdk.test.fakes.givenFindAll import org.matrix.android.sdk.test.fakes.givenFindFirst +import org.matrix.android.sdk.test.fakes.givenLessThan import org.matrix.android.sdk.test.fakes.givenNotEqualTo private const val A_SESSION_ID = "session_id" @@ -182,6 +184,7 @@ internal class LiveLocationAggregationProcessorTest { aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID aggregatedEntity.isActive shouldBeEqualTo true + aggregatedEntity.startOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS aggregatedEntity.lastLocationContent shouldBeEqualTo null previousEntities.forEach { entity -> @@ -199,9 +202,10 @@ internal class LiveLocationAggregationProcessorTest { age = 123, replacesState = AN_EVENT_ID ) + val stateEventId = "state-event-id" val event = Event( senderId = A_SENDER_ID, - eventId = "", + eventId = stateEventId, unsignedData = unsignedData ) val beaconInfo = MessageBeaconInfoContent( @@ -237,6 +241,7 @@ internal class LiveLocationAggregationProcessorTest { aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID aggregatedEntity.isActive shouldBeEqualTo false + aggregatedEntity.relatedEventIds shouldContain stateEventId aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS aggregatedEntity.lastLocationContent shouldBeEqualTo null previousEntities.forEach { entity -> @@ -324,7 +329,7 @@ internal class LiveLocationAggregationProcessorTest { val lastBeaconLocationContent = MessageBeaconLocationDataContent( unstableTimestampMillis = A_TIMESTAMP ) - givenLastSummaryQueryReturns( + val aggregatedEntity = givenLastSummaryQueryReturns( eventId = AN_EVENT_ID, roomId = A_ROOM_ID, beaconLocationContent = lastBeaconLocationContent @@ -340,6 +345,7 @@ internal class LiveLocationAggregationProcessorTest { ) result shouldBeEqualTo false + aggregatedEntity.relatedEventIds shouldContain AN_EVENT_ID } @Test @@ -353,7 +359,7 @@ internal class LiveLocationAggregationProcessorTest { val lastBeaconLocationContent = MessageBeaconLocationDataContent( unstableTimestampMillis = A_TIMESTAMP - 60_000 ) - val entity = givenLastSummaryQueryReturns( + val aggregatedEntity = givenLastSummaryQueryReturns( eventId = AN_EVENT_ID, roomId = A_ROOM_ID, beaconLocationContent = lastBeaconLocationContent @@ -369,7 +375,8 @@ internal class LiveLocationAggregationProcessorTest { ) result shouldBeEqualTo true - val savedLocationData = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>() + aggregatedEntity.relatedEventIds shouldContain AN_EVENT_ID + val savedLocationData = ContentMapper.map(aggregatedEntity.lastLocationContent).toModel<MessageBeaconLocationDataContent>() savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI } @@ -399,6 +406,7 @@ internal class LiveLocationAggregationProcessorTest { .givenNotEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID) .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, A_SENDER_ID) .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) + .givenLessThan(LiveLocationShareAggregatedSummaryEntityFields.START_OF_LIVE_TIMESTAMP_MILLIS, A_TIMESTAMP) .givenFindAll(summaryList) return summaryList } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt index de9120653160a866563253f94ae3d8e9794bea71..a01f51604ca6600a562a7135d187f40c9ef69b1b 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt @@ -22,8 +22,10 @@ import androidx.lifecycle.Transformations import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every +import io.mockk.just import io.mockk.mockk import io.mockk.mockkStatic +import io.mockk.runs import io.mockk.slot import io.mockk.unmockkAll import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -51,6 +53,8 @@ private const val A_LATITUDE = 1.4 private const val A_LONGITUDE = 40.0 private const val AN_UNCERTAINTY = 5.0 private const val A_TIMEOUT = 15_000L +private const val A_DESCRIPTION = "description" +private const val A_REASON = "reason" @ExperimentalCoroutinesApi internal class DefaultLocationSharingServiceTest { @@ -61,6 +65,7 @@ internal class DefaultLocationSharingServiceTest { private val startLiveLocationShareTask = mockk<StartLiveLocationShareTask>() private val stopLiveLocationShareTask = mockk<StopLiveLocationShareTask>() private val checkIfExistingActiveLiveTask = mockk<CheckIfExistingActiveLiveTask>() + private val redactLiveLocationShareTask = mockk<RedactLiveLocationShareTask>() private val fakeLiveLocationShareAggregatedSummaryMapper = mockk<LiveLocationShareAggregatedSummaryMapper>() private val defaultLocationSharingService = DefaultLocationSharingService( @@ -71,6 +76,7 @@ internal class DefaultLocationSharingServiceTest { startLiveLocationShareTask = startLiveLocationShareTask, stopLiveLocationShareTask = stopLiveLocationShareTask, checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask, + redactLiveLocationShareTask = redactLiveLocationShareTask, liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper ) @@ -137,7 +143,7 @@ internal class DefaultLocationSharingServiceTest { coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success("stopped-event-id") coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) - val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION) result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( @@ -150,7 +156,8 @@ internal class DefaultLocationSharingServiceTest { coVerify { stopLiveLocationShareTask.execute(expectedStopParams) } val expectedStartParams = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, - timeoutMillis = A_TIMEOUT + timeoutMillis = A_TIMEOUT, + description = A_DESCRIPTION ) coVerify { startLiveLocationShareTask.execute(expectedStartParams) } } @@ -161,7 +168,7 @@ internal class DefaultLocationSharingServiceTest { val error = Throwable() coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Failure(error) - val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION) result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( @@ -179,7 +186,7 @@ internal class DefaultLocationSharingServiceTest { coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns false coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) - val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION) result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( @@ -188,7 +195,8 @@ internal class DefaultLocationSharingServiceTest { coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } val expectedStartParams = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, - timeoutMillis = A_TIMEOUT + timeoutMillis = A_TIMEOUT, + description = A_DESCRIPTION ) coVerify { startLiveLocationShareTask.execute(expectedStartParams) } } @@ -206,6 +214,20 @@ internal class DefaultLocationSharingServiceTest { coVerify { stopLiveLocationShareTask.execute(expectedParams) } } + @Test + fun `live location share can be redacted`() = runTest { + coEvery { redactLiveLocationShareTask.execute(any()) } just runs + + defaultLocationSharingService.redactLiveLocationShare(beaconInfoEventId = AN_EVENT_ID, reason = A_REASON) + + val expectedParams = RedactLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + reason = A_REASON + ) + coVerify { redactLiveLocationShareTask.execute(expectedParams) } + } + @Test fun `livedata of live summaries is correctly computed`() { val entity = LiveLocationShareAggregatedSummaryEntity() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b8618d1a791cb5340dde97e4d540c498415a9d45 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022 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.session.room.location + +import io.mockk.unmockkAll +import io.realm.RealmList +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeEventSenderProcessor +import org.matrix.android.sdk.test.fakes.FakeLocalEchoEventFactory +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.FakeRealmConfiguration +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst + +private const val A_ROOM_ID = "room-id" +private const val AN_EVENT_ID = "event-id" +private const val AN_EVENT_ID_1 = "event-id-1" +private const val AN_EVENT_ID_2 = "event-id-2" +private const val AN_EVENT_ID_3 = "event-id-3" +private const val A_REASON = "reason" + +@ExperimentalCoroutinesApi +class DefaultRedactLiveLocationShareTaskTest { + + private val fakeRealmConfiguration = FakeRealmConfiguration() + private val fakeLocalEchoEventFactory = FakeLocalEchoEventFactory() + private val fakeEventSenderProcessor = FakeEventSenderProcessor() + private val fakeRealm = FakeRealm() + + private val defaultRedactLiveLocationShareTask = DefaultRedactLiveLocationShareTask( + realmConfiguration = fakeRealmConfiguration.instance, + localEchoEventFactory = fakeLocalEchoEventFactory.instance, + eventSenderProcessor = fakeEventSenderProcessor + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters when redacting then post redact events and related and creates redact local echos`() = runTest { + val params = createParams() + val relatedEventIds = listOf(AN_EVENT_ID_1, AN_EVENT_ID_2, AN_EVENT_ID_3) + val aggregatedSummaryEntity = createSummary(relatedEventIds) + givenSummaryForId(AN_EVENT_ID, aggregatedSummaryEntity) + fakeRealmConfiguration.givenAwaitTransaction<List<String>>(fakeRealm.instance) + val redactEvents = givenCreateRedactEventWithLocalEcho(relatedEventIds + AN_EVENT_ID) + givenPostRedaction(redactEvents) + + defaultRedactLiveLocationShareTask.execute(params) + + verifyCreateRedactEventForEventIds(relatedEventIds + AN_EVENT_ID) + verifyCreateLocalEchoForEvents(redactEvents) + } + + private fun createParams() = RedactLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + reason = A_REASON + ) + + private fun createSummary(relatedEventIds: List<String>): LiveLocationShareAggregatedSummaryEntity { + return LiveLocationShareAggregatedSummaryEntity( + eventId = AN_EVENT_ID, + relatedEventIds = RealmList(*relatedEventIds.toTypedArray()), + ) + } + + private fun givenSummaryForId(eventId: String, aggregatedSummaryEntity: LiveLocationShareAggregatedSummaryEntity) { + fakeRealm.givenWhere<LiveLocationShareAggregatedSummaryEntity>() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId) + .givenFindFirst(aggregatedSummaryEntity) + } + + private fun givenCreateRedactEventWithLocalEcho(eventIds: List<String>): List<Event> { + return eventIds.map { eventId -> + fakeLocalEchoEventFactory.givenCreateRedactEvent( + eventId = eventId, + withLocalEcho = true + ) + } + } + + private fun givenPostRedaction(redactEvents: List<Event>) { + redactEvents.forEach { + fakeEventSenderProcessor.givenPostRedaction(event = it, reason = A_REASON) + } + } + + private fun verifyCreateRedactEventForEventIds(eventIds: List<String>) { + eventIds.forEach { eventId -> + fakeLocalEchoEventFactory.verifyCreateRedactEvent( + roomId = A_ROOM_ID, + eventId = eventId, + reason = A_REASON + ) + } + } + + private fun verifyCreateLocalEchoForEvents(events: List<Event>) { + events.forEach { redactionEvent -> + fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactionEvent) + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt index 909ba5d048eb93e39e130a9f59c33dd9fb5c2c01..aa8826243fcf7050fe934f4421f27cf6833bc4be 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.test.fakes.FakeSendStateTask private const val A_USER_ID = "user-id" private const val A_ROOM_ID = "room-id" private const val AN_EVENT_ID = "event-id" +private const val A_DESCRIPTION = "description" private const val A_TIMEOUT = 15_000L private const val AN_EPOCH = 1655210176L @@ -58,7 +59,8 @@ internal class DefaultStartLiveLocationShareTaskTest { fun `given parameters and no error when calling the task then result is success`() = runTest { val params = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, - timeoutMillis = A_TIMEOUT + timeoutMillis = A_TIMEOUT, + description = A_DESCRIPTION ) fakeClock.givenEpoch(AN_EPOCH) fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) @@ -67,6 +69,7 @@ internal class DefaultStartLiveLocationShareTaskTest { result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedBeaconContent = MessageBeaconInfoContent( + body = A_DESCRIPTION, timeout = params.timeoutMillis, isLive = true, unstableTimestampMillis = AN_EPOCH @@ -87,7 +90,8 @@ internal class DefaultStartLiveLocationShareTaskTest { fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest { val params = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, - timeoutMillis = A_TIMEOUT + timeoutMillis = A_TIMEOUT, + description = A_DESCRIPTION ) fakeClock.givenEpoch(AN_EPOCH) fakeSendStateTask.givenExecuteRetryReturns("") @@ -101,7 +105,8 @@ internal class DefaultStartLiveLocationShareTaskTest { fun `given parameters and error during event sending when calling the task then result is failure`() = runTest { val params = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, - timeoutMillis = A_TIMEOUT + timeoutMillis = A_TIMEOUT, + description = A_DESCRIPTION ) fakeClock.givenEpoch(AN_EPOCH) val error = Throwable() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..24d9c30039f497188ae6bf583e62a9ae7d70d387 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 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.session.room.location + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.junit.Test +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.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.givenDelete +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst + +private const val AN_EVENT_ID = "event-id" +private const val A_REDACTED_EVENT_ID = "redacted-event-id" + +@ExperimentalCoroutinesApi +class LiveLocationShareRedactionEventProcessorTest { + + private val liveLocationShareRedactionEventProcessor = LiveLocationShareRedactionEventProcessor() + private val fakeRealm = FakeRealm() + + @Test + fun `given an event when checking if it should be processed then only event of type REDACTED is processed`() { + val eventId = AN_EVENT_ID + val eventType = EventType.REDACTION + val insertType = EventInsertType.INCREMENTAL_SYNC + + val result = liveLocationShareRedactionEventProcessor.shouldProcess( + eventId = eventId, + eventType = eventType, + insertType = insertType + ) + + result shouldBe true + } + + @Test + fun `given an event when checking if it should be processed then local echo is not processed`() { + val eventId = AN_EVENT_ID + val eventType = EventType.REDACTION + val insertType = EventInsertType.LOCAL_ECHO + + val result = liveLocationShareRedactionEventProcessor.shouldProcess( + eventId = eventId, + eventType = eventType, + insertType = insertType + ) + + result shouldBe false + } + + @Test + fun `given a redacted live location share event when processing it then related summaries are deleted from database`() = runTest { + val event = Event(eventId = AN_EVENT_ID, redacts = A_REDACTED_EVENT_ID) + val redactedEventEntity = EventEntity(eventId = A_REDACTED_EVENT_ID, type = EventType.STATE_ROOM_BEACON_INFO.first()) + fakeRealm.givenWhere<EventEntity>() + .givenEqualTo(EventEntityFields.EVENT_ID, A_REDACTED_EVENT_ID) + .givenFindFirst(redactedEventEntity) + val liveSummary = mockk<LiveLocationShareAggregatedSummaryEntity>() + every { liveSummary.eventId } returns A_REDACTED_EVENT_ID + liveSummary.givenDelete() + fakeRealm.givenWhere<LiveLocationShareAggregatedSummaryEntity>() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, A_REDACTED_EVENT_ID) + .givenFindFirst(liveSummary) + val annotationsSummary = mockk<EventAnnotationsSummaryEntity>() + every { annotationsSummary.eventId } returns A_REDACTED_EVENT_ID + annotationsSummary.givenDelete() + fakeRealm.givenWhere<EventAnnotationsSummaryEntity>() + .givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_REDACTED_EVENT_ID) + .givenFindFirst(annotationsSummary) + + liveLocationShareRedactionEventProcessor.process(fakeRealm.instance, event = event) + + verify { + liveSummary.deleteFromRealm() + annotationsSummary.deleteFromRealm() + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt index fbdcf5bfd7cf6f0bf6ba97d0e726afb35c0af6b1..db04b8b8cba730d77d782e142a0d23d845234216 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt @@ -27,4 +27,8 @@ internal class FakeEventSenderProcessor : EventSenderProcessor by mockk() { fun givenPostEventReturns(event: Event, cancelable: Cancelable) { every { postEvent(event) } returns cancelable } + + fun givenPostRedaction(event: Event, reason: String?) { + every { postRedaction(event, reason) } returns mockk() + } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt index 50ec85f14a2e186c3033c3f3f508111157ef5c60..f484e32149d3e45bdd851b371854c8ffbce1a5a9 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt @@ -46,6 +46,24 @@ internal class FakeLocalEchoEventFactory { return event } + fun verifyCreateStaticLocationEvent( + roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?, + isUserLocation: Boolean + ) { + verify { + instance.createStaticLocationEvent( + roomId = roomId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty, + isUserLocation = isUserLocation + ) + } + } + fun givenCreateLiveLocationEvent(withLocalEcho: Boolean): Event { val event = Event() every { @@ -64,38 +82,50 @@ internal class FakeLocalEchoEventFactory { return event } - fun verifyCreateStaticLocationEvent( + fun verifyCreateLiveLocationEvent( roomId: String, + beaconInfoEventId: String, latitude: Double, longitude: Double, - uncertainty: Double?, - isUserLocation: Boolean + uncertainty: Double? ) { verify { - instance.createStaticLocationEvent( + instance.createLiveLocationEvent( roomId = roomId, + beaconInfoEventId = beaconInfoEventId, latitude = latitude, longitude = longitude, - uncertainty = uncertainty, - isUserLocation = isUserLocation + uncertainty = uncertainty ) } } - fun verifyCreateLiveLocationEvent( + fun givenCreateRedactEvent(eventId: String, withLocalEcho: Boolean): Event { + val event = Event() + every { + instance.createRedactEvent( + roomId = any(), + eventId = eventId, + reason = any() + ) + } returns event + + if (withLocalEcho) { + every { instance.createLocalEcho(event) } just runs + } + return event + } + + fun verifyCreateRedactEvent( roomId: String, - beaconInfoEventId: String, - latitude: Double, - longitude: Double, - uncertainty: Double? + eventId: String, + reason: String? ) { verify { - instance.createLiveLocationEvent( + instance.createRedactEvent( roomId = roomId, - beaconInfoEventId = beaconInfoEventId, - latitude = latitude, - longitude = longitude, - uncertainty = uncertainty + eventId = eventId, + reason = reason ) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index 0ebff8727889dd42832674f3e5ab2bbacfa39843..afdcf111f897c13f52ce2fb000d3428830afb6bb 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -18,10 +18,13 @@ package org.matrix.android.sdk.test.fakes import io.mockk.MockKVerificationScope import io.mockk.every +import io.mockk.just import io.mockk.mockk +import io.mockk.runs import io.mockk.verify import io.realm.Realm import io.realm.RealmModel +import io.realm.RealmObject import io.realm.RealmQuery import io.realm.RealmResults import io.realm.kotlin.where @@ -97,3 +100,18 @@ inline fun <reified T : RealmModel> RealmQuery<T>.givenIsNotNull( every { isNotNull(fieldName) } returns this return this } + +inline fun <reified T : RealmModel> RealmQuery<T>.givenLessThan( + fieldName: String, + value: Long +): RealmQuery<T> { + every { lessThan(fieldName, value) } returns this + return this +} + +/** + * Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked. + */ +fun RealmObject.givenDelete() { + every { deleteFromRealm() } just runs +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt new file mode 100644 index 0000000000000000000000000000000000000000..15a9823c79daddcddd0a6c1b72cfc8201a95be2f --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.coEvery +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.realm.Realm +import io.realm.RealmConfiguration +import org.matrix.android.sdk.internal.database.awaitTransaction + +internal class FakeRealmConfiguration { + + init { + mockkStatic("org.matrix.android.sdk.internal.database.AsyncTransactionKt") + } + + val instance = mockk<RealmConfiguration>() + + fun <T> givenAwaitTransaction(realm: Realm) { + val transaction = slot<suspend (Realm) -> T>() + coEvery { awaitTransaction(instance, capture(transaction)) } coAnswers { + secondArg<suspend (Realm) -> T>().invoke(realm) + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/api/FakeSession.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/api/FakeSession.kt new file mode 100644 index 0000000000000000000000000000000000000000..df22455fb1deff44dd5f6009cc1f5799f731a123 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/api/FakeSession.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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.test.fakes.api + +import io.mockk.mockk +import org.matrix.android.sdk.api.session.Session + +class FakeSession { + + val instance: Session = mockk() +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeSessionManager.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeSessionManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..2232ad9b4f2ad172e28467bb76aad2b6d7aa9710 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeSessionManager.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.amshove.kluent.shouldBeEqualTo +import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.test.fakes.api.FakeSession + +internal class FakeSessionManager { + + val instance: SessionManager = mockk() + + init { + every { instance.getOrCreateSession(any()) } returns fakeSession.instance + } + + fun assertSessionCreatedWithParams(session: Session, sessionParams: SessionParams) { + verify { instance.getOrCreateSession(sessionParams) } + + session shouldBeEqualTo fakeSession.instance + } + + companion object { + private val fakeSession = FakeSession() + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeIsValidClientServerApiTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeIsValidClientServerApiTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..40681748c1e5fff9a109aec64de30fbff1a0affd --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeIsValidClientServerApiTask.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.auth + +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.internal.auth.IsValidClientServerApiTask +import org.matrix.android.sdk.internal.auth.IsValidClientServerApiTask.Params + +internal class FakeIsValidClientServerApiTask { + + init { + coEvery { instance.execute(any()) } returns true + } + + val instance: IsValidClientServerApiTask = mockk() + + fun givenValidationFails() { + coEvery { instance.execute(any()) } returns false + } + + fun verifyExecutionWithConfig(config: HomeServerConnectionConfig) { + coVerify { instance.execute(Params(config)) } + } + + fun verifyNoExecution() { + coVerify(inverse = true) { instance.execute(any()) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakePendingSessionStore.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakePendingSessionStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..8a18b75ca2914f2ce82fda47f94a38f0d0bdd1e2 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakePendingSessionStore.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.auth + +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.mockk +import org.matrix.android.sdk.internal.auth.PendingSessionStore + +internal class FakePendingSessionStore { + + val instance: PendingSessionStore = mockk() + + init { + coJustRun { instance.delete() } + } + + fun verifyPendingSessionDataCleared() { + coVerify { instance.delete() } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeSessionParamsCreator.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeSessionParamsCreator.kt new file mode 100644 index 0000000000000000000000000000000000000000..f64e5a451d2ed4121464202a78602755b985b56b --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeSessionParamsCreator.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.auth + +import android.net.Uri +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.internal.auth.SessionParamsCreator +import org.matrix.android.sdk.test.fixtures.SessionParamsFixture.aSessionParams + +internal class FakeSessionParamsCreator { + + val instance: SessionParamsCreator = mockk() + + init { + mockkStatic(Uri::class) + every { Uri.parse(any()) } returns mockk() + coEvery { instance.create(any(), any(), any()) } returns sessionParams + } + + fun verifyCreatedWithParameters( + credentials: Credentials, + homeServerConnectionConfig: HomeServerConnectionConfig, + loginType: LoginType, + ) { + coVerify { instance.create(credentials, homeServerConnectionConfig, loginType) } + } + + companion object { + val sessionParams = aSessionParams() + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeSessionParamsStore.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeSessionParamsStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..22e8a32a326565cfda94347066312455e5b4c56e --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/FakeSessionParamsStore.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.auth + +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.mockk +import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.internal.auth.SessionParamsStore + +internal class FakeSessionParamsStore { + + val instance: SessionParamsStore = mockk() + + init { + coJustRun { instance.save(any()) } + } + + fun verifyParamsSaved(sessionParams: SessionParams) { + coVerify { instance.save(sessionParams) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/migration/Fake005MigrationRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/migration/Fake005MigrationRealm.kt new file mode 100644 index 0000000000000000000000000000000000000000..13fd4a972c8e7ff069add95ac02d6f793dc9e90c --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/migration/Fake005MigrationRealm.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.auth.db.migration + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verifyOrder +import io.realm.DynamicRealm +import io.realm.RealmObjectSchema +import io.realm.RealmSchema +import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields + +class Fake005MigrationRealm { + + val instance: DynamicRealm = mockk() + + private val schema: RealmSchema = mockk() + private val objectSchema: RealmObjectSchema = mockk() + + init { + every { instance.schema } returns schema + every { schema.get("SessionParamsEntity") } returns objectSchema + every { objectSchema.addField(any(), any()) } returns objectSchema + every { objectSchema.setRequired(any(), any()) } returns objectSchema + every { objectSchema.transform(any()) } returns objectSchema + } + + fun verifyLoginTypeAdded() { + verifyLoginTypeFieldAddedAndTransformed() + } + + private fun verifyLoginTypeFieldAddedAndTransformed() { + verifyOrder { + objectSchema["SessionParamsEntity"] + objectSchema.addField(SessionParamsEntityFields.LOGIN_TYPE, String::class.java) + objectSchema.setRequired(SessionParamsEntityFields.LOGIN_TYPE, true) + objectSchema.transform(any()) + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeCredentialsJsonAdapter.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeCredentialsJsonAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..f1cb4071fdb2db65159f5e84c1f231b5199db4dd --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeCredentialsJsonAdapter.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.auth.db.sessionparams + +import com.squareup.moshi.JsonAdapter +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeSessionParamsMapperMoshi.Companion.sessionParams +import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeSessionParamsMapperMoshi.Companion.sessionParamsEntity +import org.matrix.android.sdk.test.fixtures.CredentialsFixture.aCredentials + +internal class FakeCredentialsJsonAdapter { + + val instance: JsonAdapter<Credentials> = mockk() + + init { + every { instance.fromJson(sessionParamsEntity.credentialsJson) } returns credentials + every { instance.toJson(sessionParams.credentials) } returns CREDENTIALS_JSON + } + + fun givenNullDeserialization() { + every { instance.fromJson(sessionParamsEntity.credentialsJson) } returns null + } + + fun givenNullSerialization() { + every { instance.toJson(credentials) } returns null + } + + companion object { + val credentials = aCredentials() + const val CREDENTIALS_JSON = "credentials_json" + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeHomeServerConnectionConfigJsonAdapter.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeHomeServerConnectionConfigJsonAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..f85d6e27781fc7872ee82bfcc6af03f8a8d7f027 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeHomeServerConnectionConfigJsonAdapter.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.auth.db.sessionparams + +import com.squareup.moshi.JsonAdapter +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeSessionParamsMapperMoshi.Companion.sessionParams +import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeSessionParamsMapperMoshi.Companion.sessionParamsEntity + +internal class FakeHomeServerConnectionConfigJsonAdapter { + + val instance: JsonAdapter<HomeServerConnectionConfig> = mockk() + + init { + every { instance.fromJson(sessionParamsEntity.homeServerConnectionConfigJson) } returns homeServerConnectionConfig + every { instance.toJson(sessionParams.homeServerConnectionConfig) } returns HOME_SERVER_CONNECTION_CONFIG_JSON + } + + fun givenNullDeserialization() { + every { instance.fromJson(sessionParamsEntity.credentialsJson) } returns null + } + + fun givenNullSerialization() { + every { instance.toJson(homeServerConnectionConfig) } returns null + } + + companion object { + val homeServerConnectionConfig = HomeServerConnectionConfig.Builder().withHomeServerUri("homeserver").build() + const val HOME_SERVER_CONNECTION_CONFIG_JSON = "home_server_connection_config_json" + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeSessionParamsMapperMoshi.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeSessionParamsMapperMoshi.kt new file mode 100644 index 0000000000000000000000000000000000000000..ed0ddb11793e37a58f187032027763ba40535fda --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/auth/db/sessionparams/FakeSessionParamsMapperMoshi.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.auth.db.sessionparams + +import android.net.Uri +import com.squareup.moshi.Moshi +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeNull +import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.api.auth.data.sessionId +import org.matrix.android.sdk.internal.auth.db.SessionParamsEntity +import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeCredentialsJsonAdapter.Companion.CREDENTIALS_JSON +import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeCredentialsJsonAdapter.Companion.credentials +import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeHomeServerConnectionConfigJsonAdapter.Companion.HOME_SERVER_CONNECTION_CONFIG_JSON +import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeHomeServerConnectionConfigJsonAdapter.Companion.homeServerConnectionConfig +import org.matrix.android.sdk.test.fixtures.SessionParamsEntityFixture.aSessionParamsEntity +import org.matrix.android.sdk.test.fixtures.SessionParamsFixture.aSessionParams + +internal class FakeSessionParamsMapperMoshi { + + val instance: Moshi = mockk() + private val credentialsJsonAdapter = FakeCredentialsJsonAdapter() + private val homeServerConnectionConfigAdapter = FakeHomeServerConnectionConfigJsonAdapter() + + init { + mockkStatic(Uri::class) + every { Uri.parse(any()) } returns mockk() + every { instance.adapter(Credentials::class.java) } returns credentialsJsonAdapter.instance + every { instance.adapter(HomeServerConnectionConfig::class.java) } returns homeServerConnectionConfigAdapter.instance + } + + fun assertSessionParamsWasMappedSuccessfully(sessionParams: SessionParams?) { + sessionParams shouldBeEqualTo SessionParams( + credentials, + homeServerConnectionConfig, + sessionParamsEntity.isTokenValid, + LoginType.fromName(sessionParamsEntity.loginType) + ) + } + + fun assertSessionParamsIsNull(sessionParams: SessionParams?) { + sessionParams.shouldBeNull() + } + + fun assertSessionParamsEntityWasMappedSuccessfully(sessionParamsEntity: SessionParamsEntity?) { + sessionParamsEntity shouldBeEqualTo SessionParamsEntity( + sessionParams.credentials.sessionId(), + sessionParams.userId, + CREDENTIALS_JSON, + HOME_SERVER_CONNECTION_CONFIG_JSON, + sessionParams.isTokenValid, + sessionParams.loginType.name, + ) + } + + fun assertSessionParamsEntityIsNull(sessionParamsEntity: SessionParamsEntity?) { + sessionParamsEntity.shouldBeNull() + } + + companion object { + val sessionParams = aSessionParams() + val sessionParamsEntity = aSessionParamsEntity() + val nullSessionParams: SessionParams? = null + val nullSessionParamsEntity: SessionParamsEntity? = null + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/CredentialsFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/CredentialsFixture.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e7b36ff638646b5783c7484f18e43c0956e49fb --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/CredentialsFixture.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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.test.fixtures + +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.auth.data.DiscoveryInformation + +object CredentialsFixture { + fun aCredentials( + userId: String = "", + accessToken: String = "", + refreshToken: String? = null, + homeServer: String? = null, + deviceId: String? = null, + discoveryInformation: DiscoveryInformation? = null, + ) = Credentials( + userId, + accessToken, + refreshToken, + homeServer, + deviceId, + discoveryInformation, + ) +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/DiscoveryInformationFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/DiscoveryInformationFixture.kt new file mode 100644 index 0000000000000000000000000000000000000000..c929a27d23dbeba334a3806ff11ce311950cb554 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/DiscoveryInformationFixture.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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.test.fixtures + +import org.matrix.android.sdk.api.auth.data.DiscoveryInformation +import org.matrix.android.sdk.api.auth.data.WellKnownBaseConfig + +object DiscoveryInformationFixture { + fun aDiscoveryInformation( + homeServer: WellKnownBaseConfig? = null, + identityServer: WellKnownBaseConfig? = null, + ) = DiscoveryInformation( + homeServer, + identityServer + ) +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SessionParamsEntityFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SessionParamsEntityFixture.kt new file mode 100644 index 0000000000000000000000000000000000000000..bbea232a223a334e4ff7251d9dd3d35b83bfcbf1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SessionParamsEntityFixture.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 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.test.fixtures + +import org.matrix.android.sdk.internal.auth.db.SessionParamsEntity + +internal object SessionParamsEntityFixture { + fun aSessionParamsEntity( + sessionId: String = "", + userId: String = "", + credentialsJson: String = "", + homeServerConnectionConfigJson: String = "", + isTokenValid: Boolean = true, + loginType: String = "", + ) = SessionParamsEntity( + sessionId, + userId, + credentialsJson, + homeServerConnectionConfigJson, + isTokenValid, + loginType, + ) +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SessionParamsFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SessionParamsFixture.kt new file mode 100644 index 0000000000000000000000000000000000000000..5cbbe1a47a20153036bb03fd1796683bca9725e3 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SessionParamsFixture.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 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.test.fixtures + +import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.test.fixtures.CredentialsFixture.aCredentials + +object SessionParamsFixture { + fun aSessionParams( + credentials: Credentials = aCredentials(), + homeServerConnectionConfig: HomeServerConnectionConfig = HomeServerConnectionConfig.Builder().withHomeServerUri("homeserver").build(), + isTokenValid: Boolean = false, + loginType: LoginType = LoginType.UNKNOWN, + ) = SessionParams( + credentials, + homeServerConnectionConfig, + isTokenValid, + loginType, + ) +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/WellKnownBaseConfigFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/WellKnownBaseConfigFixture.kt new file mode 100644 index 0000000000000000000000000000000000000000..a33308dbd68040ac4120ba37efdf5a6b79927be1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/WellKnownBaseConfigFixture.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 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.test.fixtures + +import org.matrix.android.sdk.api.auth.data.WellKnownBaseConfig + +object WellKnownBaseConfigFixture { + fun aWellKnownBaseConfig( + baseUrl: String? = null, + ) = WellKnownBaseConfig( + baseUrl, + ) +}