From 9b332f7a32f0d81126c94c481b5997da24f46724 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 14 Dec 2020 17:32:54 +0300 Subject: [PATCH 01/19] Test message decryption in a room with 3 members. --- .../timeline/TimelineWithManyMembersTest.kt | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt new file mode 100644 index 0000000000..a0271cb5b9 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.session.room.timeline + +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.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CryptoTestHelper +import java.util.concurrent.CountDownLatch + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class TimelineWithManyMembersTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + /** + * Ensures when someone sends a message to a crowded room, everyone can decrypt the message. + */ + @Test + fun everyoneShouldDecryptMessage3Members() { + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobAndSamInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession!! + val samSession = cryptoTestData.thirdSession!! + + val aliceRoomId = cryptoTestData.roomId + + aliceSession.cryptoService().setWarnOnUnknownDevices(false) + bobSession.cryptoService().setWarnOnUnknownDevices(false) + samSession.cryptoService().setWarnOnUnknownDevices(false) + + val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! + val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! + val roomFromSamPOV = samSession.getRoom(aliceRoomId)!! + + val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) + val samTimeline = roomFromSamPOV.createTimeline(null, TimelineSettings(30)) + bobTimeline.start() + samTimeline.start() + + val firstMessage = "First messages from Alice" + commonTestHelper.sendTextMessage( + roomFromAlicePOV, + firstMessage, + 1) + + bobSession.startSync(true) + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + snapshot.firstOrNull()?.root?.getClearContent()?.toModel()?.body?.startsWith(firstMessage).orFalse() + } + bobTimeline.addListener(eventsListener) + commonTestHelper.await(lock) + } + bobSession.stopSync() + + samSession.startSync(true) + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + snapshot.firstOrNull()?.root?.getClearContent()?.toModel()?.body?.startsWith(firstMessage).orFalse() + } + samTimeline.addListener(eventsListener) + commonTestHelper.await(lock) + } + samSession.stopSync() + } +} From 7e4725c091246c3bb97fb7e45863d31a3b637f1c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 14 Dec 2020 18:07:20 +0300 Subject: [PATCH 02/19] Update CryptoTestData to handle more than 3 sessions. --- .../android/sdk/common/CryptoTestData.kt | 21 ++++++++++++------- .../android/sdk/common/CryptoTestHelper.kt | 6 +++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt index 76e59d9a90..e01aaef639 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt @@ -18,14 +18,21 @@ package org.matrix.android.sdk.common import org.matrix.android.sdk.api.session.Session -data class CryptoTestData(val firstSession: Session, - val roomId: String, - val secondSession: Session? = null, - val thirdSession: Session? = null) { +data class CryptoTestData(val roomId: String, + val sessions: List = emptyList()) { + + val firstSession: Session + get() = sessions.first() + + val secondSession: Session? + get() = sessions.getOrNull(1) + + val thirdSession: Session? + get() = sessions.getOrNull(2) fun cleanUp(testHelper: CommonTestHelper) { - testHelper.signOutAndClose(firstSession) - secondSession?.let { testHelper.signOutAndClose(it) } - thirdSession?.let { testHelper.signOutAndClose(it) } + sessions.forEach { + testHelper.signOutAndClose(it) + } } } 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 cbb22daf0f..f108cbb105 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 @@ -73,7 +73,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } } - return CryptoTestData(aliceSession, roomId) + return CryptoTestData(roomId, listOf(aliceSession)) } /** @@ -139,7 +139,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { // assertNotNull(roomFromBobPOV.powerLevels) // assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId)) - return CryptoTestData(aliceSession, aliceRoomId, bobSession) + return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession)) } /** @@ -157,7 +157,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { // wait the initial sync SystemClock.sleep(1000) - return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession) + return CryptoTestData(aliceRoomId, listOf(aliceSession, cryptoTestData.secondSession!!, samSession)) } /** From 427dc784fe2d1f131be8e29f930efb70fb74a4e6 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 15 Dec 2020 16:50:49 +0300 Subject: [PATCH 03/19] Support testing a room with many members. --- .../android/sdk/common/CryptoTestData.kt | 2 +- .../android/sdk/common/CryptoTestHelper.kt | 33 ++++++++++++-- .../timeline/TimelineWithManyMembersTest.kt | 43 +++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt index e01aaef639..693ab28da1 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.common import org.matrix.android.sdk.api.session.Session data class CryptoTestData(val roomId: String, - val sessions: List = emptyList()) { + val sessions: MutableList = mutableListOf()) { val firstSession: Session get() = sessions.first() 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 f108cbb105..66fc1348d5 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 @@ -73,7 +73,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } } - return CryptoTestData(roomId, listOf(aliceSession)) + return CryptoTestData(roomId, mutableListOf(aliceSession)) } /** @@ -139,7 +139,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { // assertNotNull(roomFromBobPOV.powerLevels) // assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId)) - return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession)) + return CryptoTestData(aliceRoomId, mutableListOf(aliceSession, bobSession)) } /** @@ -157,7 +157,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { // wait the initial sync SystemClock.sleep(1000) - return CryptoTestData(aliceRoomId, listOf(aliceSession, cryptoTestData.secondSession!!, samSession)) + return CryptoTestData(aliceRoomId, mutableListOf(aliceSession, cryptoTestData.secondSession!!, samSession)) } /** @@ -381,4 +381,31 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } } } + + fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData { + val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) + aliceSession.cryptoService().setWarnOnUnknownDevices(false) + + val roomId = mTestHelper.doSync { + aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it) + } + val room = aliceSession.getRoom(roomId)!! + + mTestHelper.runBlockingTest { + room.enableEncryption() + } + + val cryptoTestData = CryptoTestData(roomId, mutableListOf(aliceSession)) + for (index in 1 until numberOfMembers) { + mTestHelper + .createAccount("User_$index", defaultSessionParams) + .also { session -> mTestHelper.doSync { room.invite(session.myUserId, null, it) } } + .also { println("TEST -> " + it.myUserId + " invited") } + .also { session -> mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) } } + .also { println("TEST -> " + it.myUserId + " joined") } + .also { session -> cryptoTestData.sessions.add(session) } + } + + return cryptoTestData + } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt index a0271cb5b9..1a397c1d1c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.session.room.timeline +import android.os.SystemClock +import android.util.Log import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -29,11 +31,14 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import java.util.concurrent.CountDownLatch +import kotlin.test.assertEquals @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) class TimelineWithManyMembersTest : InstrumentedTest { + private val NUMBER_OF_MEMBERS = 5 + private val commonTestHelper = CommonTestHelper(context()) private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) @@ -91,4 +96,42 @@ class TimelineWithManyMembersTest : InstrumentedTest { } samSession.stopSync() } + + /** + * Ensures when someone sends a message to a crowded room, everyone can decrypt the message. + */ + @Test + fun everyone_should_decrypt_message_in_a_crowded_room() { + val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS) + + val sessionForFirstMember = cryptoTestData.firstSession + val roomForFirstMember = sessionForFirstMember.getRoom(cryptoTestData.roomId)!! + + val firstMessage = "First messages from Alice" + commonTestHelper.sendTextMessage( + roomForFirstMember, + firstMessage, + 1) + + for (index in 1 until cryptoTestData.sessions.size) { + val session = cryptoTestData.sessions[index] + val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!! + val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30)) + timelineForCurrentMember.start() + + session.startSync(true) + + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + val decryptedMessage = snapshot.firstOrNull()?.root?.getClearContent()?.toModel()?.body + println("Decrypted Message: $decryptedMessage") + return@createEventListener decryptedMessage?.startsWith(firstMessage).orFalse() + } + timelineForCurrentMember.addListener(eventsListener) + commonTestHelper.await(lock) + } + session.stopSync() + } + } } From b263273c878806ba3f474380f438349c7f8b70ac Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 16 Dec 2020 13:45:26 +0300 Subject: [PATCH 04/19] Improve test with detailed CryptoError message. --- .../android/sdk/common/CommonTestHelper.kt | 4 ++-- .../android/sdk/common/CryptoTestHelper.kt | 2 +- .../timeline/TimelineWithManyMembersTest.kt | 23 ++++++++++++------- 3 files changed, 18 insertions(+), 11 deletions(-) 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 0e7088a6a5..7dcf43ba0f 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 @@ -119,7 +119,7 @@ class CommonTestHelper(context: Context) { * @param message the message to send * @param nbOfMessages the number of time the message will be sent */ - fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List { + fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List { val timeline = room.createTimeline(null, TimelineSettings(10)) val sentEvents = ArrayList(nbOfMessages) val latch = CountDownLatch(1) @@ -151,7 +151,7 @@ class CommonTestHelper(context: Context) { room.sendTextMessage(message + " #" + (i + 1)) } // Wait 3 second more per message - await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages) + await(latch, timeout = timeout + 3_000L * nbOfMessages) timeline.dispose() // Check that all events has been created 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 66fc1348d5..5d3407dde1 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 @@ -399,7 +399,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { for (index in 1 until numberOfMembers) { mTestHelper .createAccount("User_$index", defaultSessionParams) - .also { session -> mTestHelper.doSync { room.invite(session.myUserId, null, it) } } + .also { session -> mTestHelper.doSync(timeout = 600_000) { room.invite(session.myUserId, null, it) } } .also { println("TEST -> " + it.myUserId + " invited") } .also { session -> mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) } } .also { println("TEST -> " + it.myUserId + " joined") } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt index 1a397c1d1c..dacde121b3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.session.room.timeline -import android.os.SystemClock -import android.util.Log import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -31,7 +29,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import java.util.concurrent.CountDownLatch -import kotlin.test.assertEquals +import kotlin.test.fail @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @@ -111,7 +109,9 @@ class TimelineWithManyMembersTest : InstrumentedTest { commonTestHelper.sendTextMessage( roomForFirstMember, firstMessage, - 1) + 1, + 600_000 + ) for (index in 1 until cryptoTestData.sessions.size) { val session = cryptoTestData.sessions[index] @@ -124,12 +124,19 @@ class TimelineWithManyMembersTest : InstrumentedTest { run { val lock = CountDownLatch(1) val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - val decryptedMessage = snapshot.firstOrNull()?.root?.getClearContent()?.toModel()?.body - println("Decrypted Message: $decryptedMessage") - return@createEventListener decryptedMessage?.startsWith(firstMessage).orFalse() + snapshot + .find { it.isEncrypted() } + ?.let { + val body = it.root.getClearContent()?.toModel()?.body + if (body?.startsWith(firstMessage).orFalse()) { + return@createEventListener true + } else { + fail("User " + session.myUserId + " decrypted as " + body + " CryptoError: " + it.root.mCryptoError) + } + } ?: return@createEventListener false } timelineForCurrentMember.addListener(eventsListener) - commonTestHelper.await(lock) + commonTestHelper.await(lock, 600_000) } session.stopSync() } From 7b97981bb57ad7b651700ecacc878f4c1324967f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 16 Dec 2020 15:45:50 +0300 Subject: [PATCH 05/19] Make sure to load all members in the room before sending the event. --- .../android/sdk/common/CommonTestHelper.kt | 10 ++-- .../timeline/TimelineWithManyMembersTest.kt | 58 +------------------ .../internal/crypto/tasks/SendEventTask.kt | 9 +++ 3 files changed, 16 insertions(+), 61 deletions(-) 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 7dcf43ba0f..cb49ee8818 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 @@ -86,7 +86,7 @@ class CommonTestHelper(context: Context) { * * @param session the session to sync */ - fun syncSession(session: Session) { + fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) { val lock = CountDownLatch(1) val job = GlobalScope.launch(Dispatchers.Main) { @@ -109,7 +109,7 @@ class CommonTestHelper(context: Context) { } GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) } - await(lock) + await(lock, timeout) } /** @@ -215,14 +215,14 @@ class CommonTestHelper(context: Context) { .getLoginFlow(hs, it) } - doSync { + doSync(timeout = 60_000) { matrix.authenticationService .getRegistrationWizard() .createAccount(userName, password, null, it) } // Perform dummy step - val registrationResult = doSync { + val registrationResult = doSync(timeout = 60_000) { matrix.authenticationService .getRegistrationWizard() .dummy(it) @@ -231,7 +231,7 @@ class CommonTestHelper(context: Context) { assertTrue(registrationResult is RegistrationResult.Success) val session = (registrationResult as RegistrationResult.Success).session if (sessionTestParams.withInitialSync) { - syncSession(session) + syncSession(session, 60_000) } return session diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt index dacde121b3..ca74acdee7 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -35,66 +35,11 @@ import kotlin.test.fail @FixMethodOrder(MethodSorters.JVM) class TimelineWithManyMembersTest : InstrumentedTest { - private val NUMBER_OF_MEMBERS = 5 + private val NUMBER_OF_MEMBERS = 6 private val commonTestHelper = CommonTestHelper(context()) private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - /** - * Ensures when someone sends a message to a crowded room, everyone can decrypt the message. - */ - @Test - fun everyoneShouldDecryptMessage3Members() { - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobAndSamInARoom() - - val aliceSession = cryptoTestData.firstSession - val bobSession = cryptoTestData.secondSession!! - val samSession = cryptoTestData.thirdSession!! - - val aliceRoomId = cryptoTestData.roomId - - aliceSession.cryptoService().setWarnOnUnknownDevices(false) - bobSession.cryptoService().setWarnOnUnknownDevices(false) - samSession.cryptoService().setWarnOnUnknownDevices(false) - - val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! - val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! - val roomFromSamPOV = samSession.getRoom(aliceRoomId)!! - - val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) - val samTimeline = roomFromSamPOV.createTimeline(null, TimelineSettings(30)) - bobTimeline.start() - samTimeline.start() - - val firstMessage = "First messages from Alice" - commonTestHelper.sendTextMessage( - roomFromAlicePOV, - firstMessage, - 1) - - bobSession.startSync(true) - run { - val lock = CountDownLatch(1) - val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - snapshot.firstOrNull()?.root?.getClearContent()?.toModel()?.body?.startsWith(firstMessage).orFalse() - } - bobTimeline.addListener(eventsListener) - commonTestHelper.await(lock) - } - bobSession.stopSync() - - samSession.startSync(true) - run { - val lock = CountDownLatch(1) - val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - snapshot.firstOrNull()?.root?.getClearContent()?.toModel()?.body?.startsWith(firstMessage).orFalse() - } - samTimeline.addListener(eventsListener) - commonTestHelper.await(lock) - } - samSession.stopSync() - } - /** * Ensures when someone sends a message to a crowded room, everyone can decrypt the message. */ @@ -129,6 +74,7 @@ class TimelineWithManyMembersTest : InstrumentedTest { ?.let { val body = it.root.getClearContent()?.toModel()?.body if (body?.startsWith(firstMessage).orFalse()) { + println("User " + session.myUserId + " decrypted as " + body) return@createEventListener true } else { fail("User " + session.myUserId + " decrypted as " + body + " CryptoError: " + it.root.mCryptoError) 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 8b739c4b64..5c8c7dfb25 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 @@ -20,6 +20,7 @@ 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.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task @@ -35,11 +36,19 @@ internal interface SendEventTask : Task { internal class DefaultSendEventTask @Inject constructor( private val localEchoRepository: LocalEchoRepository, private val encryptEventTask: DefaultEncryptEventTask, + private val loadRoomMembersTask: LoadRoomMembersTask, private val roomAPI: RoomAPI, private val eventBus: EventBus) : SendEventTask { override suspend fun execute(params: SendEventTask.Params): String { try { + // Make sure to load all members in the room before sending the event. + params.event.roomId + ?.takeIf { params.encrypt } + ?.let { roomId -> + loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId)) + } + val event = handleEncryption(params) val localId = event.eventId!! From 80396fcd391d195be206dedd914115fd6bc8722c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 16 Dec 2020 15:46:14 +0300 Subject: [PATCH 06/19] Changelog added. --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 0fb7d72c13..9ab5276ff3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -57,6 +57,7 @@ Bugfix 🐛: - No known servers error is given when joining rooms on new Gitter bridge (#2516) - Show preview when sending attachment from the keyboard (#2440) - Do not compress GIFs (#1616, #1254) + - Wait for all room members to be known before sending a message to a e2e room (#2518) SDK API changes ⚠️: - StateService now exposes suspendable function instead of using MatrixCallback. From 938cd32ddd97a3608a1497d0cf83b97150a33a3a Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 17 Dec 2020 18:25:40 +0300 Subject: [PATCH 07/19] Do not load room members if there is an ongoing request. --- .../database/RealmSessionStoreMigration.kt | 18 ++++++++++++++- .../sdk/internal/database/model/RoomEntity.kt | 10 +++++++- .../model/RoomMembersLoadStatusType.java | 23 +++++++++++++++++++ .../room/membership/LoadRoomMembersTask.kt | 5 ++-- 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.java 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 b970ec60e2..fd922ef0e5 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 @@ -21,6 +21,7 @@ import io.realm.RealmMigration import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import timber.log.Timber import javax.inject.Inject @@ -28,7 +29,7 @@ import javax.inject.Inject class RealmSessionStoreMigration @Inject constructor() : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 6L + const val SESSION_STORE_SCHEMA_VERSION = 7L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -40,6 +41,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 3) migrateTo4(realm) if (oldVersion <= 4) migrateTo5(realm) if (oldVersion <= 5) migrateTo6(realm) + if (oldVersion <= 6) migrateTo7(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -105,4 +107,18 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { .addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java) .addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java) } + + private fun migrateTo7(realm: DynamicRealm) { + Timber.d("Step 6 -> 7") + realm.schema.get("RoomEntity") + ?.addField("membersLoadStatusStr", String::class.java) + ?.transform { obj -> + if (obj.getBoolean("areAllMembersLoaded")) { + obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name) + } else { + obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.NONE.name) + } + } + ?.removeField("areAllMembersLoaded") + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt index 9af1646a4c..a0a6060d36 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt @@ -24,7 +24,6 @@ import io.realm.annotations.PrimaryKey internal open class RoomEntity(@PrimaryKey var roomId: String = "", var chunks: RealmList = RealmList(), var sendingTimelineEvents: RealmList = RealmList(), - var areAllMembersLoaded: Boolean = false ) : RealmObject() { private var membershipStr: String = Membership.NONE.name @@ -36,5 +35,14 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "", membershipStr = value.name } + private var membersLoadStatusStr: String = RoomMembersLoadStatusType.NONE.name + var membersLoadStatus: RoomMembersLoadStatusType + get() { + return RoomMembersLoadStatusType.valueOf(membersLoadStatusStr) + } + set(value) { + membersLoadStatusStr = value.name + } + companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.java new file mode 100644 index 0000000000..d7063082bb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.java @@ -0,0 +1,23 @@ +/* + * 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; + +public enum RoomMembersLoadStatusType { + NONE, + LOADING, + LOADED +} 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 627f927ad8..69530c5c0e 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 @@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.util.awaitTransaction import io.realm.Realm import io.realm.kotlin.createObject import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import javax.inject.Inject internal interface LoadRoomMembersTask : Task { @@ -84,14 +85,14 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( } roomMemberEventHandler.handle(realm, roomId, roomMemberEvent) } - roomEntity.areAllMembersLoaded = true + roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED roomSummaryUpdater.update(realm, roomId, updateMembers = true) } } private fun areAllMembersAlreadyLoaded(roomId: String): Boolean { return Realm.getInstance(monarchy.realmConfiguration).use { - RoomEntity.where(it, roomId).findFirst()?.areAllMembersLoaded ?: false + RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus == RoomMembersLoadStatusType.LOADED } } } From 5d8f365520a0f48d94346d3e13b5fc431738e06c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 17 Dec 2020 18:55:31 +0300 Subject: [PATCH 08/19] Load room members seamlessly when timeline is starting. --- .../sdk/internal/database/model/RoomEntity.kt | 2 +- .../session/room/timeline/DefaultTimeline.kt | 12 +++++++++++- .../session/room/timeline/DefaultTimelineService.kt | 7 +++++-- .../features/home/room/detail/RoomDetailViewModel.kt | 2 -- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt index a0a6060d36..3ff2532604 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt @@ -23,7 +23,7 @@ import io.realm.annotations.PrimaryKey internal open class RoomEntity(@PrimaryKey var roomId: String = "", var chunks: RealmList = RealmList(), - var sendingTimelineEvents: RealmList = RealmList(), + var sendingTimelineEvents: RealmList = RealmList() ) : RealmObject() { private var membershipStr: String = Membership.NONE.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 86b0497bd0..dd58529412 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -27,6 +27,7 @@ import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType @@ -53,6 +54,7 @@ import org.matrix.android.sdk.internal.database.query.filterEvents import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.Debouncer @@ -81,7 +83,8 @@ internal class DefaultTimeline( private val hiddenReadReceipts: TimelineHiddenReadReceipts, private val eventBus: EventBus, private val eventDecryptor: TimelineEventDecryptor, - private val realmSessionProvider: RealmSessionProvider + private val realmSessionProvider: RealmSessionProvider, + private val loadRoomMembersTask: LoadRoomMembersTask ) : Timeline, TimelineHiddenReadReceipts.Delegate { data class OnNewTimelineEvents(val roomId: String, val eventIds: List) @@ -184,6 +187,13 @@ internal class DefaultTimeline( if (settings.shouldHandleHiddenReadReceipts()) { hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this) } + + loadRoomMembersTask + .configureWith(LoadRoomMembersTask.Params(roomId)) { + this.callback = NoOpMatrixCallback() + } + .executeBy(taskExecutor) + isReady.set(true) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 783aa53ddf..d02e906d00 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -39,6 +39,7 @@ 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.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.task.TaskExecutor internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String, @@ -51,7 +52,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv private val paginationTask: PaginationTask, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper + private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, + private val loadRoomMembersTask: LoadRoomMembersTask ) : TimelineService { @AssistedInject.Factory @@ -73,7 +75,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv eventBus = eventBus, eventDecryptor = eventDecryptor, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, - realmSessionProvider = realmSessionProvider + realmSessionProvider = realmSessionProvider, + loadRoomMembersTask = loadRoomMembersTask ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index e4e7177e4f..1e6e7c9d14 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -31,7 +31,6 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.subscribeLogError import im.vector.app.features.call.WebRtcPeerConnectionManager import im.vector.app.features.command.CommandParser import im.vector.app.features.command.ParsedCommand @@ -168,7 +167,6 @@ class RoomDetailViewModel @AssistedInject constructor( observePowerLevel() room.getRoomSummaryLive() room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback()) - room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() // Inform the SDK that the room is displayed session.onRoomDisplayed(initialState.roomId) chatEffectManager.delegate = this From 42a5680374ef8fd8f71ee85b5a9c7c198be970ea Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 18 Dec 2020 12:27:51 +0300 Subject: [PATCH 09/19] Fix copyright. --- .../sdk/session/room/timeline/TimelineWithManyMembersTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt index ca74acdee7..6dd139390c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * 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. From 7732bd47cebbcaadb53c84615a491ce271cf25db Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Dec 2020 16:01:39 +0100 Subject: [PATCH 10/19] Update Changelog after release --- CHANGES.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9ab5276ff3..38868ed822 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Bugfix 🐛: - - + - Wait for all room members to be known before sending a message to a e2e room (#2518) Translations 🗣: - @@ -57,7 +57,6 @@ Bugfix 🐛: - No known servers error is given when joining rooms on new Gitter bridge (#2516) - Show preview when sending attachment from the keyboard (#2440) - Do not compress GIFs (#1616, #1254) - - Wait for all room members to be known before sending a message to a e2e room (#2518) SDK API changes ⚠️: - StateService now exposes suspendable function instead of using MatrixCallback. From ff8a20801253969911f7fde8faba3265cca58cb2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Dec 2020 16:04:46 +0100 Subject: [PATCH 11/19] Change to immutable list --- .../org/matrix/android/sdk/common/CryptoTestData.kt | 2 +- .../matrix/android/sdk/common/CryptoTestHelper.kt | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt index 693ab28da1..b6bedbd719 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.common import org.matrix.android.sdk.api.session.Session data class CryptoTestData(val roomId: String, - val sessions: MutableList = mutableListOf()) { + val sessions: List) { val firstSession: Session get() = sessions.first() 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 5d3407dde1..f219986c49 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 @@ -73,7 +73,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } } - return CryptoTestData(roomId, mutableListOf(aliceSession)) + return CryptoTestData(roomId, listOf(aliceSession)) } /** @@ -139,7 +139,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { // assertNotNull(roomFromBobPOV.powerLevels) // assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId)) - return CryptoTestData(aliceRoomId, mutableListOf(aliceSession, bobSession)) + return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession)) } /** @@ -157,7 +157,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { // wait the initial sync SystemClock.sleep(1000) - return CryptoTestData(aliceRoomId, mutableListOf(aliceSession, cryptoTestData.secondSession!!, samSession)) + return CryptoTestData(aliceRoomId, listOf(aliceSession, cryptoTestData.secondSession!!, samSession)) } /** @@ -395,7 +395,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { room.enableEncryption() } - val cryptoTestData = CryptoTestData(roomId, mutableListOf(aliceSession)) + val sessions = mutableListOf(aliceSession) for (index in 1 until numberOfMembers) { mTestHelper .createAccount("User_$index", defaultSessionParams) @@ -403,9 +403,9 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { .also { println("TEST -> " + it.myUserId + " invited") } .also { session -> mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) } } .also { println("TEST -> " + it.myUserId + " joined") } - .also { session -> cryptoTestData.sessions.add(session) } + .also { session -> sessions.add(session) } } - return cryptoTestData + return CryptoTestData(roomId, sessions) } } From 00b16db7cc823e8ef127fbc3e43d250cb428d03f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Dec 2020 16:06:30 +0100 Subject: [PATCH 12/19] Simplification of code --- .../matrix/android/sdk/common/CryptoTestHelper.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 f219986c49..3d5856fc64 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 @@ -397,13 +397,12 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val sessions = mutableListOf(aliceSession) for (index in 1 until numberOfMembers) { - mTestHelper - .createAccount("User_$index", defaultSessionParams) - .also { session -> mTestHelper.doSync(timeout = 600_000) { room.invite(session.myUserId, null, it) } } - .also { println("TEST -> " + it.myUserId + " invited") } - .also { session -> mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) } } - .also { println("TEST -> " + it.myUserId + " joined") } - .also { session -> sessions.add(session) } + val session = mTestHelper.createAccount("User_$index", defaultSessionParams) + mTestHelper.doSync(timeout = 600_000) { room.invite(session.myUserId, null, it) } + println("TEST -> " + session.myUserId + " invited") + mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) } + println("TEST -> " + session.myUserId + " joined") + sessions.add(session) } return CryptoTestData(roomId, sessions) From 15597eb041899df40c582f2d9793fb9bf07d8845 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Dec 2020 16:10:36 +0100 Subject: [PATCH 13/19] Rename .java to .kt --- ...oomMembersLoadStatusType.java => RoomMembersLoadStatusType.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/{RoomMembersLoadStatusType.java => RoomMembersLoadStatusType.kt} (100%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.java rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt From abf763f454ac7e89f1d6fe314c112394749c7ac2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Dec 2020 16:10:36 +0100 Subject: [PATCH 14/19] Convert to internal Kotlin class --- .../sdk/internal/database/model/RoomMembersLoadStatusType.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt index d7063082bb..79fe17253b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.database.model; +package org.matrix.android.sdk.internal.database.model -public enum RoomMembersLoadStatusType { +internal enum class RoomMembersLoadStatusType { NONE, LOADING, LOADED From b0ba62aa312c9847277f47489b7e892aa34cba3f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Dec 2020 16:12:01 +0100 Subject: [PATCH 15/19] Use const --- .../sdk/internal/database/RealmSessionStoreMigration.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 fd922ef0e5..57002b5a60 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 @@ -21,6 +21,7 @@ import io.realm.RealmMigration import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields +import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import timber.log.Timber @@ -111,7 +112,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { private fun migrateTo7(realm: DynamicRealm) { Timber.d("Step 6 -> 7") realm.schema.get("RoomEntity") - ?.addField("membersLoadStatusStr", String::class.java) + ?.addField(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, String::class.java) ?.transform { obj -> if (obj.getBoolean("areAllMembersLoaded")) { obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name) From ca4b91a98f3086a4ce167d9942023233e039aa0c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Dec 2020 16:38:27 +0100 Subject: [PATCH 16/19] Use the new RoomMembersLoadStatusType.LOADING value --- .../room/membership/LoadRoomMembersTask.kt | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) 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 69530c5c0e..55dd747fdb 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,12 +17,19 @@ 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.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.mapper.toEntity 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.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType 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.where @@ -33,10 +40,7 @@ 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 import org.matrix.android.sdk.internal.util.awaitTransaction -import io.realm.Realm -import io.realm.kotlin.createObject -import org.greenrobot.eventbus.EventBus -import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType +import java.util.concurrent.TimeUnit import javax.inject.Inject internal interface LoadRoomMembersTask : Task { @@ -57,9 +61,33 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { - if (areAllMembersAlreadyLoaded(params.roomId)) { - return + when (getRoomMembersLoadStatus(params.roomId)) { + RoomMembersLoadStatusType.NONE -> doRequest(params) + RoomMembersLoadStatusType.LOADING -> waitPreviousRequestToFinish(params) + RoomMembersLoadStatusType.LOADED -> Unit } + } + + private suspend fun waitPreviousRequestToFinish(params: LoadRoomMembersTask.Params) { + try { + awaitNotEmptyResult(monarchy.realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomEntity::class.java) + .equalTo(RoomEntityFields.ROOM_ID, params.roomId) + .equalTo(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, RoomMembersLoadStatusType.LOADED.name) + } + } catch (exception: TimeoutCancellationException) { + // Timeout, do the request anyway (?) + doRequest(params) + } + } + + private suspend fun doRequest(params: LoadRoomMembersTask.Params) { + monarchy.awaitTransaction { realm -> + val roomEntity = RoomEntity.where(realm, params.roomId).findFirst() + ?: realm.createObject(params.roomId) + roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADING + } + val lastToken = syncTokenStore.getLastToken() val response = executeRequest(eventBus) { apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) @@ -90,9 +118,11 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( } } - private fun areAllMembersAlreadyLoaded(roomId: String): Boolean { - return Realm.getInstance(monarchy.realmConfiguration).use { - RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus == RoomMembersLoadStatusType.LOADED + 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 } } From 3d291c04c981e6b70336dfe35ea1836190e5bf7e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Dec 2020 16:53:26 +0100 Subject: [PATCH 17/19] const -> companion --- .../sdk/session/room/timeline/TimelineWithManyMembersTest.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt index 6dd139390c..ff07cf1d1d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -35,7 +35,9 @@ import kotlin.test.fail @FixMethodOrder(MethodSorters.JVM) class TimelineWithManyMembersTest : InstrumentedTest { - private val NUMBER_OF_MEMBERS = 6 + companion object { + private const val NUMBER_OF_MEMBERS = 6 + } private val commonTestHelper = CommonTestHelper(context()) private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) From 9f3176c49cd3a8b45e4a895e758212a0f4d3e5be Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Dec 2020 14:41:23 +0100 Subject: [PATCH 18/19] Fix code quality --- tools/check/forbidden_strings_in_code.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index b9929dfebe..2306eaed8b 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===84 +enum class===85 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 From cc01f25d8f00e6c3cdee9b1cc98155e76413ac24 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Dec 2020 14:52:49 +0100 Subject: [PATCH 19/19] Revert status to RoomMembersLoadStatusType.NONE) in case of failure --- .../room/membership/LoadRoomMembersTask.kt | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) 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 55dd747fdb..53fa73c624 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 @@ -82,16 +82,19 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( } private suspend fun doRequest(params: LoadRoomMembersTask.Params) { - monarchy.awaitTransaction { realm -> - val roomEntity = RoomEntity.where(realm, params.roomId).findFirst() - ?: realm.createObject(params.roomId) - roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADING - } + setRoomMembersLoadStatus(params.roomId, RoomMembersLoadStatusType.LOADING) val lastToken = syncTokenStore.getLastToken() - val response = executeRequest(eventBus) { - apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) + val response = try { + executeRequest(eventBus) { + apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) + } + } catch (throwable: Throwable) { + // Revert status to NONE + setRoomMembersLoadStatus(params.roomId, RoomMembersLoadStatusType.NONE) + throw throwable } + // This will also set the status to LOADED insertInDb(response, params.roomId) } @@ -125,4 +128,11 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( } 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) + roomEntity.membersLoadStatus = status + } + } }