diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index a34f4219d9..10c12796c0 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -20,6 +20,7 @@
signin
signout
signup
+ threepid
\ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
index 9be8d85871..628d9759b1 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,22 +1,41 @@
-Changes in RiotX 0.12.0 (2019-XX-XX)
+Changes in RiotX 0.13.0 (2020-XX-XX)
===================================================
Features ✨:
+ - Send and render typing events (#564)
+
+Improvements 🙌:
+ - Render events m.room.encryption and m.room.guest_access in the timeline
+
+Other changes:
-
+Bugfix 🐛:
+ -
+
+Translations 🗣:
+ -
+
+Build 🧱:
+ - Change the way versionCode is computed (#827)
+
+Changes in RiotX 0.12.0 (2020-01-09)
+===================================================
+
Improvements 🙌:
- The initial sync is now handled by a foreground service
- Render aliases and canonical alias change in the timeline
- - Fix autocompletion issues and add support for rooms and groups
- Introduce developer mode in the settings (#745, #796)
- Improve devices list screen
- Add settings for rageshake sensibility
- Fix autocompletion issues and add support for rooms, groups, and emoji (#780)
- Show skip to bottom FAB while scrolling down (#752)
+ - Enable encryption on a room, SDK part (#212)
Other changes:
- Change the way RiotX identifies a session to allow the SDK to support several sessions with the same user (#800)
- Exclude play-services-oss-licenses library from F-Droid build (#814)
+ - Email domain can be limited on some homeservers, i18n of the displayed error (#754)
Bugfix 🐛:
- Fix crash when opening room creation screen from the room filtering screen
@@ -26,12 +45,6 @@ Bugfix 🐛:
- Fix matrix.org room directory not being browsable (#807)
- Hide non working settings (#751)
-Translations 🗣:
- -
-
-Build 🧱:
- -
-
Changes in RiotX 0.11.0 (2019-12-19)
===================================================
@@ -290,7 +303,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
=======================================================
-Changes in RiotX 0.0.0 (2019-XX-XX)
+Changes in RiotX 0.0.0 (2020-XX-XX)
===================================================
Features ✨:
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt
new file mode 100644
index 0000000000..c44ac9c47b
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 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 im.vector.matrix.android.account
+
+import im.vector.matrix.android.InstrumentedTest
+import im.vector.matrix.android.common.CommonTestHelper
+import im.vector.matrix.android.common.CryptoTestHelper
+import im.vector.matrix.android.common.SessionTestParams
+import im.vector.matrix.android.common.TestConstants
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class AccountCreationTest : InstrumentedTest {
+
+ private val commonTestHelper = CommonTestHelper(context())
+ private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
+
+ @Test
+ fun createAccountTest() {
+ val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
+
+ commonTestHelper.signout(session)
+
+ session.close()
+ }
+
+ @Test
+ fun createAccountAndLoginAgainTest() {
+ val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
+
+ // Log again to the same account
+ val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true))
+
+ session.close()
+ session2.close()
+ }
+
+ @Test
+ fun simpleE2eTest() {
+ val res = cryptoTestHelper.doE2ETestWithAliceInARoom()
+
+ res.close()
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt
new file mode 100644
index 0000000000..b16c865765
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2016 OpenMarket Ltd
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.common
+
+import android.content.Context
+import android.net.Uri
+import im.vector.matrix.android.api.Matrix
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.MatrixConfiguration
+import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
+import im.vector.matrix.android.api.auth.data.LoginFlowResult
+import im.vector.matrix.android.api.auth.registration.RegistrationResult
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.api.session.room.Room
+import im.vector.matrix.android.api.session.room.timeline.Timeline
+import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
+import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
+import org.junit.Assert.*
+import java.util.*
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * This class exposes methods to be used in common cases
+ * Registration, login, Sync, Sending messages...
+ */
+class CommonTestHelper(context: Context) {
+
+ val matrix: Matrix
+
+ init {
+ Matrix.initialize(context, MatrixConfiguration("TestFlavor"))
+
+ matrix = Matrix.getInstance(context)
+ }
+
+ fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
+ return createAccount(userNamePrefix, TestConstants.PASSWORD, testParams)
+ }
+
+ fun logIntoAccount(userId: String, testParams: SessionTestParams): Session {
+ return logIntoAccount(userId, TestConstants.PASSWORD, testParams)
+ }
+
+ /**
+ * Create a Home server configuration, with Http connection allowed for test
+ */
+ fun createHomeServerConfig(): HomeServerConnectionConfig {
+ return HomeServerConnectionConfig.Builder()
+ .withHomeServerUri(Uri.parse(TestConstants.TESTS_HOME_SERVER_URL))
+ .build()
+ }
+
+ /**
+ * This methods init the event stream and check for initial sync
+ *
+ * @param session the session to sync
+ */
+ fun syncSession(session: Session) {
+ // val lock = CountDownLatch(1)
+
+ // val observer = androidx.lifecycle.Observer { syncState ->
+ // if (syncState is SyncState.Idle) {
+ // lock.countDown()
+ // }
+ // }
+
+ // TODO observe?
+ // while (session.syncState().value !is SyncState.Idle) {
+ // sleep(100)
+ // }
+
+ session.open()
+ session.startSync(true)
+ // await(lock)
+ // session.syncState().removeObserver(observer)
+ }
+
+ /**
+ * Sends text messages in a room
+ *
+ * @param room the room where to send the messages
+ * @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 {
+ val sentEvents = ArrayList(nbOfMessages)
+ val latch = CountDownLatch(nbOfMessages)
+ val onEventSentListener = object : Timeline.Listener {
+ override fun onTimelineFailure(throwable: Throwable) {
+ }
+
+ override fun onTimelineUpdated(snapshot: List) {
+ // TODO Count only new messages?
+ if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {
+ sentEvents.addAll(snapshot.filter { it.root.type == EventType.MESSAGE })
+ latch.countDown()
+ }
+ }
+ }
+ val timeline = room.createTimeline(null, TimelineSettings(10))
+ timeline.addListener(onEventSentListener)
+ for (i in 0 until nbOfMessages) {
+ room.sendTextMessage(message + " #" + (i + 1))
+ }
+ await(latch)
+ timeline.removeListener(onEventSentListener)
+
+ // Check that all events has been created
+ assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong())
+
+ return sentEvents
+ }
+
+ // PRIVATE METHODS *****************************************************************************
+
+ /**
+ * Creates a unique account
+ *
+ * @param userNamePrefix the user name prefix
+ * @param password the password
+ * @param testParams test params about the session
+ * @return the session associated with the newly created account
+ */
+ private fun createAccount(userNamePrefix: String,
+ password: String,
+ testParams: SessionTestParams): Session {
+ val session = createAccountAndSync(
+ userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(),
+ password,
+ testParams
+ )
+ assertNotNull(session)
+ return session
+ }
+
+ /**
+ * Logs into an existing account
+ *
+ * @param userId the userId to log in
+ * @param password the password to log in
+ * @param testParams test params about the session
+ * @return the session associated with the existing account
+ */
+ private fun logIntoAccount(userId: String,
+ password: String,
+ testParams: SessionTestParams): Session {
+ val session = logAccountAndSync(userId, password, testParams)
+ assertNotNull(session)
+ return session
+ }
+
+ /**
+ * Create an account and a dedicated session
+ *
+ * @param userName the account username
+ * @param password the password
+ * @param sessionTestParams parameters for the test
+ */
+ private fun createAccountAndSync(userName: String,
+ password: String,
+ sessionTestParams: SessionTestParams): Session {
+ val hs = createHomeServerConfig()
+
+ doSync {
+ matrix.authenticationService
+ .getLoginFlow(hs, it)
+ }
+
+ doSync {
+ matrix.authenticationService
+ .getRegistrationWizard()
+ .createAccount(userName, password, null, it)
+ }
+
+ // Preform dummy step
+ val registrationResult = doSync {
+ matrix.authenticationService
+ .getRegistrationWizard()
+ .dummy(it)
+ }
+
+ assertTrue(registrationResult is RegistrationResult.Success)
+ val session = (registrationResult as RegistrationResult.Success).session
+ if (sessionTestParams.withInitialSync) {
+ syncSession(session)
+ }
+
+ return session
+ }
+
+ /**
+ * Start an account login
+ *
+ * @param userName the account username
+ * @param password the password
+ * @param sessionTestParams session test params
+ */
+ private fun logAccountAndSync(userName: String,
+ password: String,
+ sessionTestParams: SessionTestParams): Session {
+ val hs = createHomeServerConfig()
+
+ doSync {
+ matrix.authenticationService
+ .getLoginFlow(hs, it)
+ }
+
+ val session = doSync {
+ matrix.authenticationService
+ .getLoginWizard()
+ .login(userName, password, "myDevice", it)
+ }
+
+ if (sessionTestParams.withInitialSync) {
+ syncSession(session)
+ }
+
+ return session
+ }
+
+ /**
+ * Await for a latch and ensure the result is true
+ *
+ * @param latch
+ * @throws InterruptedException
+ */
+ fun await(latch: CountDownLatch) {
+ assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
+ }
+
+ // Transform a method with a MatrixCallback to a synchronous method
+ inline fun doSync(block: (MatrixCallback) -> Unit): T {
+ val lock = CountDownLatch(1)
+ var result: T? = null
+
+ val callback = object : TestMatrixCallback(lock) {
+ override fun onSuccess(data: T) {
+ result = data
+ super.onSuccess(data)
+ }
+ }
+
+ block.invoke(callback)
+
+ await(lock)
+
+ assertNotNull(result)
+ return result!!
+ }
+
+ /**
+ * Clear all provided sessions
+ */
+ fun Iterable.close() = forEach { it.close() }
+
+ fun signout(session: Session) {
+ val lock = CountDownLatch(1)
+ session.signOut(true, object : TestMatrixCallback(lock) {})
+ await(lock)
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt
new file mode 100644
index 0000000000..8ad9f1ec6f
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.common
+
+import im.vector.matrix.android.api.session.Session
+
+data class CryptoTestData(val firstSession: Session,
+ val roomId: String,
+ val secondSession: Session? = null,
+ val thirdSession: Session? = null) {
+
+ fun close() {
+ firstSession.close()
+ secondSession?.close()
+ secondSession?.close()
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt
new file mode 100644
index 0000000000..df45249265
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.common
+
+import android.os.SystemClock
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.api.session.events.model.toContent
+import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
+import im.vector.matrix.android.api.session.room.timeline.Timeline
+import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
+import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
+import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
+import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
+import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
+import org.junit.Assert.*
+import java.util.*
+import java.util.concurrent.CountDownLatch
+
+class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
+
+ val messagesFromAlice: List = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!")
+ val messagesFromBob: List = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
+
+ val defaultSessionParams = SessionTestParams(true)
+
+ /**
+ * @return alice session
+ */
+ fun doE2ETestWithAliceInARoom(): CryptoTestData {
+ val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
+
+ var roomId: String? = null
+ val lock1 = CountDownLatch(1)
+
+ aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, object : TestMatrixCallback(lock1) {
+ override fun onSuccess(data: String) {
+ roomId = data
+ super.onSuccess(data)
+ }
+ })
+
+ mTestHelper.await(lock1)
+ assertNotNull(roomId)
+
+ val room = aliceSession.getRoom(roomId!!)!!
+
+ val lock2 = CountDownLatch(1)
+ room.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM, object : TestMatrixCallback(lock2) {})
+ mTestHelper.await(lock2)
+
+ return CryptoTestData(aliceSession, roomId!!)
+ }
+
+ /**
+ * @return alice and bob sessions
+ */
+ fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
+ val statuses = HashMap()
+
+ val cryptoTestData = doE2ETestWithAliceInARoom()
+ val aliceSession = cryptoTestData.firstSession
+ val aliceRoomId = cryptoTestData.roomId
+
+ val room = aliceSession.getRoom(aliceRoomId)!!
+
+ val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
+
+ val lock1 = CountDownLatch(2)
+
+// val bobEventListener = object : MXEventListener() {
+// override fun onNewRoom(roomId: String) {
+// if (TextUtils.equals(roomId, aliceRoomId)) {
+// if (!statuses.containsKey("onNewRoom")) {
+// statuses["onNewRoom"] = "onNewRoom"
+// lock1.countDown()
+// }
+// }
+// }
+// }
+//
+// bobSession.dataHandler.addListener(bobEventListener)
+
+ room.invite(bobSession.myUserId, callback = object : TestMatrixCallback(lock1) {
+ override fun onSuccess(data: Unit) {
+ statuses["invite"] = "invite"
+ super.onSuccess(data)
+ }
+ })
+
+ mTestHelper.await(lock1)
+
+ assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
+
+// bobSession.dataHandler.removeListener(bobEventListener)
+
+ val lock2 = CountDownLatch(2)
+
+ bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
+
+// room.addEventListener(object : MXEventListener() {
+// override fun onLiveEvent(event: Event, roomState: RoomState) {
+// if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) {
+// val contentToConsider = event.contentAsJsonObject
+// val member = JsonUtils.toRoomMember(contentToConsider)
+//
+// if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) {
+// statuses["AliceJoin"] = "AliceJoin"
+// lock2.countDown()
+// }
+// }
+// }
+// })
+
+ mTestHelper.await(lock2)
+
+ // Ensure bob can send messages to the room
+// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
+// assertNotNull(roomFromBobPOV.powerLevels)
+// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
+
+ assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin"))
+
+// bobSession.dataHandler.removeListener(bobEventListener)
+
+ return CryptoTestData(aliceSession, aliceRoomId, bobSession)
+ }
+
+ /**
+ * @return Alice, Bob and Sam session
+ */
+ fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData {
+ val statuses = HashMap()
+
+ val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
+ val aliceSession = cryptoTestData.firstSession
+ val aliceRoomId = cryptoTestData.roomId
+
+ val room = aliceSession.getRoom(aliceRoomId)!!
+
+ val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
+
+ val lock1 = CountDownLatch(2)
+
+// val samEventListener = object : MXEventListener() {
+// override fun onNewRoom(roomId: String) {
+// if (TextUtils.equals(roomId, aliceRoomId)) {
+// if (!statuses.containsKey("onNewRoom")) {
+// statuses["onNewRoom"] = "onNewRoom"
+// lock1.countDown()
+// }
+// }
+// }
+// }
+//
+// samSession.dataHandler.addListener(samEventListener)
+
+ room.invite(samSession.myUserId, null, object : TestMatrixCallback(lock1) {
+ override fun onSuccess(data: Unit) {
+ statuses["invite"] = "invite"
+ super.onSuccess(data)
+ }
+ })
+
+ mTestHelper.await(lock1)
+
+ assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
+
+// samSession.dataHandler.removeListener(samEventListener)
+
+ val lock2 = CountDownLatch(1)
+
+ samSession.joinRoom(aliceRoomId, null, object : TestMatrixCallback(lock2) {
+ override fun onSuccess(data: Unit) {
+ statuses["joinRoom"] = "joinRoom"
+ super.onSuccess(data)
+ }
+ })
+
+ mTestHelper.await(lock2)
+ assertTrue(statuses.containsKey("joinRoom"))
+
+ // wait the initial sync
+ SystemClock.sleep(1000)
+
+// samSession.dataHandler.removeListener(samEventListener)
+
+ return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
+ }
+
+ /**
+ * @return Alice and Bob sessions
+ */
+ fun doE2ETestWithAliceAndBobInARoomWithEncryptedMessages(): CryptoTestData {
+ val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
+ val aliceSession = cryptoTestData.firstSession
+ val aliceRoomId = cryptoTestData.roomId
+ val bobSession = cryptoTestData.secondSession!!
+
+ bobSession.setWarnOnUnknownDevices(false)
+
+ aliceSession.setWarnOnUnknownDevices(false)
+
+ val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
+ val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
+
+ var lock = CountDownLatch(1)
+
+ val bobEventsListener = object : Timeline.Listener {
+ override fun onTimelineFailure(throwable: Throwable) {
+ }
+
+ override fun onTimelineUpdated(snapshot: List) {
+ val size = snapshot.filter { it.root.senderId != bobSession.myUserId && it.root.getClearType() == EventType.MESSAGE }
+ .size
+
+ if (size == 3) {
+ lock.countDown()
+ }
+ }
+ }
+
+ val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(10))
+ bobTimeline.addListener(bobEventsListener)
+
+ val results = HashMap()
+
+ // bobSession.dataHandler.addListener(object : MXEventListener() {
+ // override fun onToDeviceEvent(event: Event) {
+ // results["onToDeviceEvent"] = event
+ // lock.countDown()
+ // }
+ // })
+
+ // Alice sends a message
+ roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
+ assertTrue(results.containsKey("onToDeviceEvent"))
+// assertEquals(1, messagesReceivedByBobCount)
+
+ // Bob send a message
+ lock = CountDownLatch(1)
+ roomFromBobPOV.sendTextMessage(messagesFromBob[0])
+ // android does not echo the messages sent from itself
+// messagesReceivedByBobCount++
+ mTestHelper.await(lock)
+// assertEquals(2, messagesReceivedByBobCount)
+
+ // Bob send a message
+ lock = CountDownLatch(1)
+ roomFromBobPOV.sendTextMessage(messagesFromBob[1])
+ // android does not echo the messages sent from itself
+// messagesReceivedByBobCount++
+ mTestHelper.await(lock)
+// assertEquals(3, messagesReceivedByBobCount)
+
+ // Bob send a message
+ lock = CountDownLatch(1)
+ roomFromBobPOV.sendTextMessage(messagesFromBob[2])
+ // android does not echo the messages sent from itself
+// messagesReceivedByBobCount++
+ mTestHelper.await(lock)
+// assertEquals(4, messagesReceivedByBobCount)
+
+ // Alice sends a message
+ lock = CountDownLatch(2)
+ roomFromAlicePOV.sendTextMessage(messagesFromAlice[1])
+ mTestHelper.await(lock)
+// assertEquals(5, messagesReceivedByBobCount)
+
+ bobTimeline.removeListener(bobEventsListener)
+
+ return cryptoTestData
+ }
+
+ fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) {
+ assertEquals(EventType.ENCRYPTED, event.type)
+ assertNotNull(event.content)
+
+ val eventWireContent = event.content.toContent()
+ assertNotNull(eventWireContent)
+
+ assertNull(eventWireContent.get("body"))
+ assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent.get("algorithm"))
+
+ assertNotNull(eventWireContent.get("ciphertext"))
+ assertNotNull(eventWireContent.get("session_id"))
+ assertNotNull(eventWireContent.get("sender_key"))
+
+ assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id"))
+
+ assertNotNull(event.eventId)
+ assertEquals(roomId, event.roomId)
+ assertEquals(EventType.MESSAGE, event.getClearType())
+ // TODO assertTrue(event.getAge() < 10000)
+
+ val eventContent = event.toContent()
+ assertNotNull(eventContent)
+ assertEquals(clearMessage, eventContent.get("body"))
+ assertEquals(senderSession.myUserId, event.senderId)
+ }
+
+ fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData {
+ return MegolmBackupAuthData(
+ publicKey = "abcdefg",
+ signatures = HashMap>().apply {
+ this["something"] = HashMap().apply {
+ this["ed25519:something"] = "hijklmnop"
+ }
+ }
+ )
+ }
+
+ fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo {
+ return MegolmBackupCreationInfo().apply {
+ algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
+ authData = createFakeMegolmBackupAuthData()
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt
new file mode 100644
index 0000000000..a1f95424a6
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.matrix.android.common
+
+import okhttp3.Interceptor
+import okhttp3.Protocol
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.ResponseBody.Companion.toResponseBody
+import javax.net.ssl.HttpsURLConnection
+
+/**
+ * Allows to intercept network requests for test purpose by
+ * - re-writing the response
+ * - changing the response code (200/404/etc..).
+ * - Test delays..
+ *
+ * Basic usage:
+ *
+ * val mockInterceptor = MockOkHttpInterceptor()
+ * mockInterceptor.addRule(MockOkHttpInterceptor.SimpleRule(".well-known/matrix/client", 200, "{}"))
+ *
+ * RestHttpClientFactoryProvider.defaultProvider = RestClientHttpClientFactory(mockInterceptor)
+ * AutoDiscovery().findClientConfig("matrix.org", )
+ *
+ */
+class MockOkHttpInterceptor : Interceptor {
+
+ private var rules: ArrayList = ArrayList()
+
+ fun addRule(rule: Rule) {
+ rules.add(rule)
+ }
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val originalRequest = chain.request()
+
+ rules.forEach { rule ->
+ if (originalRequest.url.toString().contains(rule.match)) {
+ rule.process(originalRequest)?.let {
+ return it
+ }
+ }
+ }
+
+ return chain.proceed(originalRequest)
+ }
+
+ abstract class Rule(val match: String) {
+ abstract fun process(originalRequest: Request): Response?
+ }
+
+ /**
+ * Simple rule that reply with the given body for any request that matches the match param
+ */
+ class SimpleRule(match: String,
+ private val code: Int = HttpsURLConnection.HTTP_OK,
+ private val body: String = "{}") : Rule(match) {
+
+ override fun process(originalRequest: Request): Response? {
+ return Response.Builder()
+ .protocol(Protocol.HTTP_1_1)
+ .request(originalRequest)
+ .message("mocked answer")
+ .body(body.toResponseBody(null))
+ .code(code)
+ .build()
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt
new file mode 100644
index 0000000000..7d1d23e951
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.common
+
+data class SessionTestParams @JvmOverloads constructor(val withInitialSync: Boolean = false)
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt
new file mode 100644
index 0000000000..2a62165210
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.common
+
+import org.junit.Assert.*
+
+/**
+ * Compare two lists and their content
+ */
+fun assertListEquals(list1: List?, list2: List?) {
+ if (list1 == null) {
+ assertNull(list2)
+ } else {
+ assertNotNull(list2)
+
+ assertEquals("List sizes must match", list1.size, list2!!.size)
+
+ for (i in list1.indices) {
+ assertEquals("Elements at index $i are not equal", list1[i], list2[i])
+ }
+ }
+}
+
+/**
+ * Compare two maps and their content
+ */
+fun assertDictEquals(dict1: Map?, dict2: Map?) {
+ if (dict1 == null) {
+ assertNull(dict2)
+ } else {
+ assertNotNull(dict2)
+
+ assertEquals("Map sizes must match", dict1.size, dict2!!.size)
+
+ for (i in dict1.keys) {
+ assertEquals("Values for key $i are not equal", dict1[i], dict2[i])
+ }
+ }
+}
+
+/**
+ * Compare two byte arrays content.
+ * Note that if the arrays have not the same size, it also fails.
+ */
+fun assertByteArrayNotEqual(a1: ByteArray, a2: ByteArray) {
+ if (a1.size != a2.size) {
+ fail("Arrays have not the same size.")
+ }
+
+ for (index in a1.indices) {
+ if (a1[index] != a2[index]) {
+ // Difference found!
+ return
+ }
+ }
+
+ fail("Arrays are equals.")
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt
new file mode 100644
index 0000000000..60cc87d330
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.common
+
+import android.os.Debug
+
+object TestConstants {
+
+ const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
+
+ // Time out to use when waiting for server response. 60s
+ private const val AWAIT_TIME_OUT_MILLIS = 60000
+
+ // Time out to use when waiting for server response, when the debugger is connected. 10 minutes
+ private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60000
+
+ const val USER_ALICE = "Alice"
+ const val USER_BOB = "Bob"
+ const val USER_SAM = "Sam"
+
+ const val PASSWORD = "password"
+
+ val timeOutMillis: Long
+ get() = if (Debug.isDebuggerConnected()) {
+ // Wait more
+ AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS.toLong()
+ } else {
+ AWAIT_TIME_OUT_MILLIS.toLong()
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt
new file mode 100644
index 0000000000..c04777440b
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.common
+
+import androidx.annotation.CallSuper
+import im.vector.matrix.android.api.MatrixCallback
+import org.junit.Assert.fail
+import timber.log.Timber
+import java.util.concurrent.CountDownLatch
+
+/**
+ * Simple implementation of MatrixCallback, which count down the CountDownLatch on each API callback
+ * @param onlySuccessful true to fail if an error occurs. This is the default behavior
+ * @param
+ */
+open class TestMatrixCallback(private val countDownLatch: CountDownLatch,
+ private val onlySuccessful: Boolean = true) : MatrixCallback {
+
+ @CallSuper
+ override fun onSuccess(data: T) {
+ countDownLatch.countDown()
+ }
+
+ @CallSuper
+ override fun onFailure(failure: Throwable) {
+ Timber.e(failure, "TestApiCallback")
+
+ if (onlySuccessful) {
+ fail("onFailure " + failure.localizedMessage)
+ }
+
+ countDownLatch.countDown()
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt
new file mode 100644
index 0000000000..84b3f24191
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.crypto
+
+import android.os.MemoryFile
+import android.util.Base64
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey
+import org.junit.Assert.*
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+
+/**
+ * Unit tests AttachmentEncryptionTest.
+ */
+@Suppress("SpellCheckingInspection")
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AttachmentEncryptionTest {
+
+ private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String {
+ val `in` = Base64.decode(input, Base64.DEFAULT)
+
+ val inputStream: InputStream
+
+ inputStream = if (`in`.isEmpty()) {
+ ByteArrayInputStream(`in`)
+ } else {
+ val memoryFile = MemoryFile("file" + System.currentTimeMillis(), `in`.size)
+ memoryFile.outputStream.write(`in`)
+ memoryFile.inputStream
+ }
+
+ val decryptedStream = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo)
+
+ assertNotNull(decryptedStream)
+
+ inputStream.close()
+
+ val buffer = ByteArray(100)
+
+ val len = decryptedStream!!.read(buffer)
+
+ decryptedStream.close()
+
+ return Base64.encodeToString(buffer, 0, len, Base64.DEFAULT).replace("\n".toRegex(), "").replace("=".toRegex(), "")
+ }
+
+ @Test
+ fun checkDecrypt1() {
+ val encryptedFileInfo = EncryptedFileInfo(
+ v = "v2",
+ hashes = mapOf("sha256" to "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU"),
+ key = EncryptedFileKey(
+ alg = "A256CTR",
+ k = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+ key_ops = listOf("encrypt", "decrypt"),
+ kty = "oct",
+ ext = true
+ ),
+ iv = "AAAAAAAAAAAAAAAAAAAAAA",
+ url = "dummyUrl"
+ )
+
+ assertEquals("", checkDecryption("", encryptedFileInfo))
+ }
+
+ @Test
+ fun checkDecrypt2() {
+ val encryptedFileInfo = EncryptedFileInfo(
+ v = "v2",
+ hashes = mapOf("sha256" to "YzF08lARDdOCzJpzuSwsjTNlQc4pHxpdHcXiD/wpK6k"),
+ key = EncryptedFileKey(
+ alg = "A256CTR",
+ k = "__________________________________________8",
+ key_ops = listOf("encrypt", "decrypt"),
+ kty = "oct",
+ ext = true
+ ),
+ iv = "//////////8AAAAAAAAAAA",
+ url = "dummyUrl"
+ )
+
+ assertEquals("SGVsbG8sIFdvcmxk", checkDecryption("5xJZTt5cQicm+9f4", encryptedFileInfo))
+ }
+
+ @Test
+ fun checkDecrypt3() {
+ val encryptedFileInfo = EncryptedFileInfo(
+ v = "v2",
+ hashes = mapOf("sha256" to "IOq7/dHHB+mfHfxlRY5XMeCWEwTPmlf4cJcgrkf6fVU"),
+ key = EncryptedFileKey(
+ alg = "A256CTR",
+ k = "__________________________________________8",
+ key_ops = listOf("encrypt", "decrypt"),
+ kty = "oct",
+ ext = true
+ ),
+ iv = "//////////8AAAAAAAAAAA",
+ url = "dummyUrl"
+ )
+
+ assertEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
+ checkDecryption("zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q",
+ encryptedFileInfo))
+ }
+
+ @Test
+ fun checkDecrypt4() {
+ val encryptedFileInfo = EncryptedFileInfo(
+ v = "v2",
+ hashes = mapOf("sha256" to "LYG/orOViuFwovJpv2YMLSsmVKwLt7pY3f8SYM7KU5E"),
+ key = EncryptedFileKey(
+ alg = "A256CTR",
+ k = "__________________________________________8",
+ key_ops = listOf("encrypt", "decrypt"),
+ kty = "oct",
+ ext = true
+ ),
+ iv = "/////////////////////w",
+ url = "dummyUrl"
+ )
+
+ assertNotEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
+ checkDecryption("tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA",
+ encryptedFileInfo))
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt
new file mode 100644
index 0000000000..89ed3c2e65
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.crypto
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.*
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+
+/**
+ * Unit tests ExportEncryptionTest.
+ */
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExportEncryptionTest {
+
+ @Test
+ fun checkExportError1() {
+ val password = "password"
+ val input = "-----"
+ var failed = false
+
+ try {
+ MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
+ } catch (e: Exception) {
+ failed = true
+ }
+
+ assertTrue(failed)
+ }
+
+ @Test
+ fun checkExportError2() {
+ val password = "password"
+ val input = "-----BEGIN MEGOLM SESSION DATA-----\n" + "-----"
+ var failed = false
+
+ try {
+ MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
+ } catch (e: Exception) {
+ failed = true
+ }
+
+ assertTrue(failed)
+ }
+
+ @Test
+ fun checkExportError3() {
+ val password = "password"
+ val input = "-----BEGIN MEGOLM SESSION DATA-----\n" +
+ " AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" +
+ " cissyYBxjsfsAn\n" +
+ " -----END MEGOLM SESSION DATA-----"
+ var failed = false
+
+ try {
+ MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
+ } catch (e: Exception) {
+ failed = true
+ }
+
+ assertTrue(failed)
+ }
+
+ @Test
+ fun checkExportDecrypt1() {
+ val password = "password"
+ val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + "cissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----"
+ val expectedString = "plain"
+
+ var decodedString: String? = null
+ try {
+ decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
+ } catch (e: Exception) {
+ fail("## checkExportDecrypt1() failed : " + e.message)
+ }
+
+ assertEquals("## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString",
+ expectedString,
+ decodedString)
+ }
+
+ @Test
+ fun checkExportDecrypt2() {
+ val password = "betterpassword"
+ val input = "-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" + "KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----"
+ val expectedString = "Hello, World"
+
+ var decodedString: String? = null
+ try {
+ decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
+ } catch (e: Exception) {
+ fail("## checkExportDecrypt2() failed : " + e.message)
+ }
+
+ assertEquals("## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString",
+ expectedString,
+ decodedString)
+ }
+
+ @Test
+ fun checkExportDecrypt3() {
+ val password = "SWORDFISH"
+ val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" + "MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----"
+ val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
+
+ var decodedString: String? = null
+ try {
+ decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
+ } catch (e: Exception) {
+ fail("## checkExportDecrypt3() failed : " + e.message)
+ }
+
+ assertEquals("## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString",
+ expectedString,
+ decodedString)
+ }
+
+ @Test
+ fun checkExportEncrypt1() {
+ val password = "password"
+ val expectedString = "plain"
+ var decodedString: String? = null
+
+ try {
+ decodedString = MXMegolmExportEncryption
+ .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
+ } catch (e: Exception) {
+ fail("## checkExportEncrypt1() failed : " + e.message)
+ }
+
+ assertEquals("## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString",
+ expectedString,
+ decodedString)
+ }
+
+ @Test
+ fun checkExportEncrypt2() {
+ val password = "betterpassword"
+ val expectedString = "Hello, World"
+ var decodedString: String? = null
+
+ try {
+ decodedString = MXMegolmExportEncryption
+ .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
+ } catch (e: Exception) {
+ fail("## checkExportEncrypt2() failed : " + e.message)
+ }
+
+ assertEquals("## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString",
+ expectedString,
+ decodedString)
+ }
+
+ @Test
+ fun checkExportEncrypt3() {
+ val password = "SWORDFISH"
+ val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
+ var decodedString: String? = null
+
+ try {
+ decodedString = MXMegolmExportEncryption
+ .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
+ } catch (e: Exception) {
+ fail("## checkExportEncrypt3() failed : " + e.message)
+ }
+
+ assertEquals("## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString",
+ expectedString,
+ decodedString)
+ }
+
+ @Test
+ fun checkExportEncrypt4() {
+ val password = "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword"
+ val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
+ var decodedString: String? = null
+
+ try {
+ decodedString = MXMegolmExportEncryption
+ .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
+ } catch (e: Exception) {
+ fail("## checkExportEncrypt4() failed : " + e.message)
+ }
+
+ assertEquals("## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString",
+ expectedString,
+ decodedString)
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt
new file mode 100644
index 0000000000..53e68383ee
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.crypto.keysbackup
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import im.vector.matrix.android.InstrumentedTest
+import im.vector.matrix.android.api.listeners.ProgressListener
+import im.vector.matrix.android.common.assertByteArrayNotEqual
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.matrix.olm.OlmManager
+import org.matrix.olm.OlmPkDecryption
+
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class KeysBackupPasswordTest : InstrumentedTest {
+
+ @Before
+ fun ensureLibLoaded() {
+ OlmManager()
+ }
+
+ /**
+ * Check KeysBackupPassword utilities
+ */
+ @Test
+ fun passwordConverter_ok() {
+ val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
+
+ assertEquals(32, generatePrivateKeyResult.salt.length)
+ assertEquals(500_000, generatePrivateKeyResult.iterations)
+ assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
+
+ // Reverse operation
+ val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
+ generatePrivateKeyResult.salt,
+ generatePrivateKeyResult.iterations)
+
+ assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
+ assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
+ }
+
+ /**
+ * Check generatePrivateKeyWithPassword progress listener behavior
+ */
+ @Test
+ fun passwordConverter_progress_ok() {
+ val progressValues = ArrayList(101)
+ var lastTotal = 0
+
+ generatePrivateKeyWithPassword(PASSWORD, object : ProgressListener {
+ override fun onProgress(progress: Int, total: Int) {
+ if (!progressValues.contains(progress)) {
+ progressValues.add(progress)
+ }
+
+ lastTotal = total
+ }
+ })
+
+ assertEquals(100, lastTotal)
+
+ // Ensure all values are here
+ assertEquals(101, progressValues.size)
+
+ for (i in 0..100) {
+ assertTrue(progressValues[i] == i)
+ }
+ }
+
+ /**
+ * Check KeysBackupPassword utilities, with bad password
+ */
+ @Test
+ fun passwordConverter_badPassword_ok() {
+ val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
+
+ assertEquals(32, generatePrivateKeyResult.salt.length)
+ assertEquals(500_000, generatePrivateKeyResult.iterations)
+ assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
+
+ // Reverse operation, with bad password
+ val retrievedPrivateKey = retrievePrivateKeyWithPassword(BAD_PASSWORD,
+ generatePrivateKeyResult.salt,
+ generatePrivateKeyResult.iterations)
+
+ assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
+ assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
+ }
+
+ /**
+ * Check KeysBackupPassword utilities, with bad password
+ */
+ @Test
+ fun passwordConverter_badIteration_ok() {
+ val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
+
+ assertEquals(32, generatePrivateKeyResult.salt.length)
+ assertEquals(500_000, generatePrivateKeyResult.iterations)
+ assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
+
+ // Reverse operation, with bad iteration
+ val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
+ generatePrivateKeyResult.salt,
+ 500_001)
+
+ assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
+ assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
+ }
+
+ /**
+ * Check KeysBackupPassword utilities, with bad salt
+ */
+ @Test
+ fun passwordConverter_badSalt_ok() {
+ val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
+
+ assertEquals(32, generatePrivateKeyResult.salt.length)
+ assertEquals(500_000, generatePrivateKeyResult.iterations)
+ assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
+
+ // Reverse operation, with bad iteration
+ val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
+ BAD_SALT,
+ generatePrivateKeyResult.iterations)
+
+ assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
+ assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
+ }
+
+ /**
+ * Check [retrievePrivateKeyWithPassword] with data coming from another platform (RiotWeb).
+ */
+ @Test
+ fun passwordConverter_crossPlatform_ok() {
+ val password = "This is a passphrase!"
+ val salt = "TO0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
+ val iteration = 500_000
+
+ val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration)
+
+ assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
+
+ // Data from RiotWeb
+ val privateKeyBytes = byteArrayOf(
+ 116.toByte(), 224.toByte(), 229.toByte(), 224.toByte(), 9.toByte(), 3.toByte(), 178.toByte(), 162.toByte(),
+ 120.toByte(), 23.toByte(), 108.toByte(), 218.toByte(), 22.toByte(), 61.toByte(), 241.toByte(), 200.toByte(),
+ 235.toByte(), 173.toByte(), 236.toByte(), 100.toByte(), 115.toByte(), 247.toByte(), 33.toByte(), 132.toByte(),
+ 195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte())
+
+ assertArrayEquals(privateKeyBytes, retrievedPrivateKey)
+ }
+
+ companion object {
+ private const val PASSWORD = "password"
+ private const val BAD_PASSWORD = "passw0rd"
+
+ private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt
new file mode 100644
index 0000000000..15deebdab1
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -0,0 +1,1417 @@
+/*
+ * Copyright 2018 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.crypto.keysbackup
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import im.vector.matrix.android.InstrumentedTest
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.listeners.ProgressListener
+import im.vector.matrix.android.api.listeners.StepProgressListener
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
+import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
+import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
+import im.vector.matrix.android.common.*
+import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
+import im.vector.matrix.android.internal.crypto.MegolmSessionData
+import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
+import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
+import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
+import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
+import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
+import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
+import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
+import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
+import org.junit.Assert.*
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import java.util.*
+import java.util.concurrent.CountDownLatch
+
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class KeysBackupTest : InstrumentedTest {
+
+ private val mTestHelper = CommonTestHelper(context())
+ private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+
+ private val defaultSessionParams = SessionTestParams(withInitialSync = false)
+ private val defaultSessionParamsWithInitialSync = SessionTestParams(withInitialSync = true)
+
+ /**
+ * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
+ * - Check backup keys after having marked one as backed up
+ * - Reset keys backup markers
+ */
+ @Test
+ fun roomKeysTest_testBackupStore_ok() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
+ val cryptoStore = (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store
+ val sessions = cryptoStore.inboundGroupSessionsToBackup(100)
+ val sessionsCount = sessions.size
+
+ assertFalse(sessions.isEmpty())
+ assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false))
+ assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true))
+
+ // - Check backup keys after having marked one as backed up
+ val session = sessions[0]
+
+ cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session))
+
+ assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false))
+ assertEquals(1, cryptoTestData.firstSession.inboundGroupSessionsCount(true))
+
+ val sessions2 = cryptoStore.inboundGroupSessionsToBackup(100)
+ assertEquals(sessionsCount - 1, sessions2.size)
+
+ // - Reset keys backup markers
+ cryptoStore.resetBackupMarkers()
+
+ val sessions3 = cryptoStore.inboundGroupSessionsToBackup(100)
+ assertEquals(sessionsCount, sessions3.size)
+ assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false))
+ assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true))
+ }
+
+ /**
+ * Check that prepareKeysBackupVersionWithPassword returns valid data
+ */
+ @Test
+ fun prepareKeysBackupVersionTest() {
+ val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
+
+ assertNotNull(bobSession.getKeysBackupService())
+
+ val keysBackup = bobSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ assertFalse(keysBackup.isEnabled)
+
+ val latch = CountDownLatch(1)
+
+ keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback {
+ override fun onSuccess(data: MegolmBackupCreationInfo) {
+ assertNotNull(data)
+
+ assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, data.algorithm)
+ assertNotNull(data.authData)
+ assertNotNull(data.authData!!.publicKey)
+ assertNotNull(data.authData!!.signatures)
+ assertNotNull(data.recoveryKey)
+
+ latch.countDown()
+ }
+
+ override fun onFailure(failure: Throwable) {
+ fail(failure.localizedMessage)
+
+ latch.countDown()
+ }
+ })
+ mTestHelper.await(latch)
+
+ stateObserver.stopAndCheckStates(null)
+ bobSession.close()
+ }
+
+ /**
+ * Test creating a keys backup version and check that createKeysBackupVersion() returns valid data
+ */
+ @Test
+ fun createKeysBackupVersionTest() {
+ val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
+
+ val keysBackup = bobSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ assertFalse(keysBackup.isEnabled)
+
+ var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
+ val latch = CountDownLatch(1)
+ keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback {
+ override fun onSuccess(data: MegolmBackupCreationInfo) {
+ megolmBackupCreationInfo = data
+
+ latch.countDown()
+ }
+
+ override fun onFailure(failure: Throwable) {
+ fail(failure.localizedMessage)
+
+ latch.countDown()
+ }
+ })
+ mTestHelper.await(latch)
+
+ assertNotNull(megolmBackupCreationInfo)
+
+ assertFalse(keysBackup.isEnabled)
+
+ val latch2 = CountDownLatch(1)
+
+ // Create the version
+ keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: KeysVersion) {
+ assertNotNull(data)
+ assertNotNull(data.version)
+
+ super.onSuccess(data)
+ }
+ })
+ mTestHelper.await(latch2)
+
+ // Backup must be enable now
+ assertTrue(keysBackup.isEnabled)
+
+ stateObserver.stopAndCheckStates(null)
+ bobSession.close()
+ }
+
+ /**
+ * - Check that createKeysBackupVersion() launches the backup
+ * - Check the backup completes
+ */
+ @Test
+ fun backupAfterCreateKeysBackupVersionTest() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService()
+
+ val latch = CountDownLatch(1)
+
+ assertEquals(2, cryptoTestData.firstSession.inboundGroupSessionsCount(false))
+ assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true))
+
+ val stateObserver = StateObserver(keysBackup, latch, 5)
+
+ prepareAndCreateKeysBackupData(keysBackup)
+
+ mTestHelper.await(latch)
+
+ val nbOfKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(false)
+ val backedUpKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(true)
+
+ assertEquals(2, nbOfKeys)
+ assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
+
+ // Check the several backup state changes
+ stateObserver.stopAndCheckStates(
+ listOf(
+ KeysBackupState.Enabling,
+ KeysBackupState.ReadyToBackUp,
+ KeysBackupState.WillBackUp,
+ KeysBackupState.BackingUp,
+ KeysBackupState.ReadyToBackUp
+ )
+ )
+ cryptoTestData.close()
+ }
+
+ /**
+ * Check that backupAllGroupSessions() returns valid data
+ */
+ @Test
+ fun backupAllGroupSessionsTest() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ prepareAndCreateKeysBackupData(keysBackup)
+
+ // Check that backupAllGroupSessions returns valid data
+ val nbOfKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(false)
+
+ assertEquals(2, nbOfKeys)
+
+ val latch = CountDownLatch(1)
+
+ var lastBackedUpKeysProgress = 0
+
+ keysBackup.backupAllGroupSessions(object : ProgressListener {
+ override fun onProgress(progress: Int, total: Int) {
+ assertEquals(nbOfKeys, total)
+ lastBackedUpKeysProgress = progress
+ }
+ }, TestMatrixCallback(latch))
+
+ mTestHelper.await(latch)
+ assertEquals(nbOfKeys, lastBackedUpKeysProgress)
+
+ val backedUpKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(true)
+
+ assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
+
+ stateObserver.stopAndCheckStates(null)
+ cryptoTestData.close()
+ }
+
+ /**
+ * Check encryption and decryption of megolm keys in the backup.
+ * - Pick a megolm key
+ * - Check [MXKeyBackup encryptGroupSession] returns stg
+ * - Check [MXKeyBackup pkDecryptionFromRecoveryKey] is able to create a OLMPkDecryption
+ * - Check [MXKeyBackup decryptKeyBackupData] returns stg
+ * - Compare the decrypted megolm key with the original one
+ */
+ @Test
+ fun testEncryptAndDecryptKeysBackupData() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService() as KeysBackup
+
+ val stateObserver = StateObserver(keysBackup)
+
+ // - Pick a megolm key
+ val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0]
+
+ val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
+
+ // - Check encryptGroupSession() returns stg
+ val keyBackupData = keysBackup.encryptGroupSession(session)
+ assertNotNull(keyBackupData)
+ assertNotNull(keyBackupData.sessionData)
+
+ // - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption
+ val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey)
+ assertNotNull(decryption)
+ // - Check decryptKeyBackupData() returns stg
+ val sessionData = keysBackup.decryptKeyBackupData(keyBackupData, session.olmInboundGroupSession!!.sessionIdentifier(), cryptoTestData.roomId, decryption!!)
+ assertNotNull(sessionData)
+ // - Compare the decrypted megolm key with the original one
+ assertKeysEquals(session.exportKeys(), sessionData)
+
+ stateObserver.stopAndCheckStates(null)
+ cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a recovery key
+ * - Log Alice on a new device
+ * - Restore the e2e backup from the homeserver with the recovery key
+ * - Restore must be successful
+ */
+ @Test
+ fun restoreKeysBackupTest() {
+ val testData = createKeysBackupScenarioWithPassword(null)
+
+ // - Restore the e2e backup from the homeserver
+ val latch2 = CountDownLatch(1)
+ var importRoomKeysResult: ImportRoomKeysResult? = null
+ testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
+ null,
+ null,
+ null,
+ object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: ImportRoomKeysResult) {
+ importRoomKeysResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys)
+
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ *
+ * This is the same as `testRestoreKeyBackup` but this test checks that pending key
+ * share requests are cancelled.
+ *
+ * - Do an e2e backup to the homeserver with a recovery key
+ * - Log Alice on a new device
+ * - *** Check the SDK sent key share requests
+ * - Restore the e2e backup from the homeserver with the recovery key
+ * - Restore must be successful
+ * - *** There must be no more pending key share requests
+ */
+ @Test
+ fun restoreKeysBackupAndKeyShareRequestTest() {
+ val testData = createKeysBackupScenarioWithPassword(null)
+
+ // - Check the SDK sent key share requests
+ val cryptoStore2 = (testData.aliceSession2.getKeysBackupService() as KeysBackup).store
+ val unsentRequest = cryptoStore2
+ .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
+ val sentRequest = cryptoStore2
+ .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
+
+ // Request is either sent or unsent
+ assertTrue(unsentRequest != null || sentRequest != null)
+
+ // - Restore the e2e backup from the homeserver
+ val latch2 = CountDownLatch(1)
+ var importRoomKeysResult: ImportRoomKeysResult? = null
+ testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
+ null,
+ null,
+ null,
+ object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: ImportRoomKeysResult) {
+ importRoomKeysResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys)
+
+ // - There must be no more pending key share requests
+ val unsentRequestAfterRestoration = cryptoStore2
+ .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
+ val sentRequestAfterRestoration = cryptoStore2
+ .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
+
+ // Request is either sent or unsent
+ assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
+
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a recovery key
+ * - And log Alice on a new device
+ * - The new device must see the previous backup as not trusted
+ * - Trust the backup from the new device
+ * - Backup must be enabled on the new device
+ * - Retrieve the last version from the server
+ * - It must be the same
+ * - It must be trusted and must have with 2 signatures now
+ */
+ @Test
+ fun trustKeyBackupVersionTest() {
+ // - Do an e2e backup to the homeserver with a recovery key
+ // - And log Alice on a new device
+ val testData = createKeysBackupScenarioWithPassword(null)
+
+ val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService())
+
+ // - The new device must see the previous backup as not trusted
+ assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion)
+ assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled)
+ assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state)
+
+ // - Trust the backup from the new device
+ val latch = CountDownLatch(1)
+ testData.aliceSession2.getKeysBackupService().trustKeysBackupVersion(
+ testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ true,
+ TestMatrixCallback(latch)
+ )
+ mTestHelper.await(latch)
+
+ // Wait for backup state to be ReadyToBackUp
+ waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
+
+ // - Backup must be enabled on the new device, on the same version
+ assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version)
+ assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled)
+
+ // - Retrieve the last version from the server
+ val latch2 = CountDownLatch(1)
+ var keysVersionResult: KeysVersionResult? = null
+ testData.aliceSession2.getKeysBackupService().getCurrentVersion(
+ object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: KeysVersionResult?) {
+ keysVersionResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ // - It must be the same
+ assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
+
+ val latch3 = CountDownLatch(1)
+ var keysBackupVersionTrust: KeysBackupVersionTrust? = null
+ testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!,
+ object : TestMatrixCallback(latch3) {
+ override fun onSuccess(data: KeysBackupVersionTrust) {
+ keysBackupVersionTrust = data
+ super.onSuccess(data)
+ }
+ })
+ mTestHelper.await(latch3)
+
+ // - It must be trusted and must have 2 signatures now
+ assertTrue(keysBackupVersionTrust!!.usable)
+ assertEquals(2, keysBackupVersionTrust!!.signatures.size)
+
+ stateObserver.stopAndCheckStates(null)
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a recovery key
+ * - And log Alice on a new device
+ * - The new device must see the previous backup as not trusted
+ * - Trust the backup from the new device with the recovery key
+ * - Backup must be enabled on the new device
+ * - Retrieve the last version from the server
+ * - It must be the same
+ * - It must be trusted and must have with 2 signatures now
+ */
+ @Test
+ fun trustKeyBackupVersionWithRecoveryKeyTest() {
+ // - Do an e2e backup to the homeserver with a recovery key
+ // - And log Alice on a new device
+ val testData = createKeysBackupScenarioWithPassword(null)
+
+ val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService())
+
+ // - The new device must see the previous backup as not trusted
+ assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion)
+ assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled)
+ assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state)
+
+ // - Trust the backup from the new device with the recovery key
+ val latch = CountDownLatch(1)
+ testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithRecoveryKey(
+ testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
+ TestMatrixCallback(latch)
+ )
+ mTestHelper.await(latch)
+
+ // Wait for backup state to be ReadyToBackUp
+ waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
+
+ // - Backup must be enabled on the new device, on the same version
+ assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version)
+ assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled)
+
+ // - Retrieve the last version from the server
+ val latch2 = CountDownLatch(1)
+ var keysVersionResult: KeysVersionResult? = null
+ testData.aliceSession2.getKeysBackupService().getCurrentVersion(
+ object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: KeysVersionResult?) {
+ keysVersionResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ // - It must be the same
+ assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
+
+ val latch3 = CountDownLatch(1)
+ var keysBackupVersionTrust: KeysBackupVersionTrust? = null
+ testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!,
+ object : TestMatrixCallback(latch3) {
+ override fun onSuccess(data: KeysBackupVersionTrust) {
+ keysBackupVersionTrust = data
+ super.onSuccess(data)
+ }
+ })
+ mTestHelper.await(latch3)
+
+ // - It must be trusted and must have 2 signatures now
+ assertTrue(keysBackupVersionTrust!!.usable)
+ assertEquals(2, keysBackupVersionTrust!!.signatures.size)
+
+ stateObserver.stopAndCheckStates(null)
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a recovery key
+ * - And log Alice on a new device
+ * - The new device must see the previous backup as not trusted
+ * - Try to trust the backup from the new device with a wrong recovery key
+ * - It must fail
+ * - The backup must still be untrusted and disabled
+ */
+ @Test
+ fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
+ // - Do an e2e backup to the homeserver with a recovery key
+ // - And log Alice on a new device
+ val testData = createKeysBackupScenarioWithPassword(null)
+
+ val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService())
+
+ // - The new device must see the previous backup as not trusted
+ assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion)
+ assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled)
+ assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state)
+
+ // - Try to trust the backup from the new device with a wrong recovery key
+ val latch = CountDownLatch(1)
+ testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithRecoveryKey(
+ testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ "Bad recovery key",
+ TestMatrixCallback(latch, false)
+ )
+ mTestHelper.await(latch)
+
+ // - The new device must still see the previous backup as not trusted
+ assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion)
+ assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled)
+ assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state)
+
+ stateObserver.stopAndCheckStates(null)
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a password
+ * - And log Alice on a new device
+ * - The new device must see the previous backup as not trusted
+ * - Trust the backup from the new device with the password
+ * - Backup must be enabled on the new device
+ * - Retrieve the last version from the server
+ * - It must be the same
+ * - It must be trusted and must have with 2 signatures now
+ */
+ @Test
+ fun trustKeyBackupVersionWithPasswordTest() {
+ val password = "Password"
+
+ // - Do an e2e backup to the homeserver with a password
+ // - And log Alice on a new device
+ val testData = createKeysBackupScenarioWithPassword(password)
+
+ val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService())
+
+ // - The new device must see the previous backup as not trusted
+ assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion)
+ assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled)
+ assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state)
+
+ // - Trust the backup from the new device with the password
+ val latch = CountDownLatch(1)
+ testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithPassphrase(
+ testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ password,
+ TestMatrixCallback(latch)
+ )
+ mTestHelper.await(latch)
+
+ // Wait for backup state to be ReadyToBackUp
+ waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
+
+ // - Backup must be enabled on the new device, on the same version
+ assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version)
+ assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled)
+
+ // - Retrieve the last version from the server
+ val latch2 = CountDownLatch(1)
+ var keysVersionResult: KeysVersionResult? = null
+ testData.aliceSession2.getKeysBackupService().getCurrentVersion(
+ object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: KeysVersionResult?) {
+ keysVersionResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ // - It must be the same
+ assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
+
+ val latch3 = CountDownLatch(1)
+ var keysBackupVersionTrust: KeysBackupVersionTrust? = null
+ testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!,
+ object : TestMatrixCallback(latch3) {
+ override fun onSuccess(data: KeysBackupVersionTrust) {
+ keysBackupVersionTrust = data
+ super.onSuccess(data)
+ }
+ })
+ mTestHelper.await(latch3)
+
+ // - It must be trusted and must have 2 signatures now
+ assertTrue(keysBackupVersionTrust!!.usable)
+ assertEquals(2, keysBackupVersionTrust!!.signatures.size)
+
+ stateObserver.stopAndCheckStates(null)
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a password
+ * - And log Alice on a new device
+ * - The new device must see the previous backup as not trusted
+ * - Try to trust the backup from the new device with a wrong password
+ * - It must fail
+ * - The backup must still be untrusted and disabled
+ */
+ @Test
+ fun trustKeyBackupVersionWithWrongPasswordTest() {
+ val password = "Password"
+ val badPassword = "Bad Password"
+
+ // - Do an e2e backup to the homeserver with a password
+ // - And log Alice on a new device
+ val testData = createKeysBackupScenarioWithPassword(password)
+
+ val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService())
+
+ // - The new device must see the previous backup as not trusted
+ assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion)
+ assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled)
+ assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state)
+
+ // - Try to trust the backup from the new device with a wrong password
+ val latch = CountDownLatch(1)
+ testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithPassphrase(
+ testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ badPassword,
+ TestMatrixCallback(latch, false)
+ )
+ mTestHelper.await(latch)
+
+ // - The new device must still see the previous backup as not trusted
+ assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion)
+ assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled)
+ assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state)
+
+ stateObserver.stopAndCheckStates(null)
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a recovery key
+ * - Log Alice on a new device
+ * - Try to restore the e2e backup with a wrong recovery key
+ * - It must fail
+ */
+ @Test
+ fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
+ val testData = createKeysBackupScenarioWithPassword(null)
+
+ // - Try to restore the e2e backup with a wrong recovery key
+ val latch2 = CountDownLatch(1)
+ var importRoomKeysResult: ImportRoomKeysResult? = null
+ testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
+ null,
+ null,
+ null,
+ object : TestMatrixCallback(latch2, false) {
+ override fun onSuccess(data: ImportRoomKeysResult) {
+ importRoomKeysResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ // onSuccess may not have been called
+ assertNull(importRoomKeysResult)
+
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a password
+ * - Log Alice on a new device
+ * - Restore the e2e backup with the password
+ * - Restore must be successful
+ */
+ @Test
+ fun testBackupWithPassword() {
+ val password = "password"
+
+ val testData = createKeysBackupScenarioWithPassword(password)
+
+ // - Restore the e2e backup with the password
+ val latch2 = CountDownLatch(1)
+ var importRoomKeysResult: ImportRoomKeysResult? = null
+ val steps = ArrayList()
+
+ testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ password,
+ null,
+ null,
+ object : StepProgressListener {
+ override fun onStepProgress(step: StepProgressListener.Step) {
+ steps.add(step)
+ }
+ },
+ object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: ImportRoomKeysResult) {
+ importRoomKeysResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ // Check steps
+ assertEquals(105, steps.size)
+
+ for (i in 0..100) {
+ assertTrue(steps[i] is StepProgressListener.Step.ComputingKey)
+ assertEquals(i, (steps[i] as StepProgressListener.Step.ComputingKey).progress)
+ assertEquals(100, (steps[i] as StepProgressListener.Step.ComputingKey).total)
+ }
+
+ assertTrue(steps[101] is StepProgressListener.Step.DownloadingKey)
+
+ // 2 Keys to import, value will be 0%, 50%, 100%
+ for (i in 102..104) {
+ assertTrue(steps[i] is StepProgressListener.Step.ImportingKey)
+ assertEquals(100, (steps[i] as StepProgressListener.Step.ImportingKey).total)
+ }
+
+ assertEquals(0, (steps[102] as StepProgressListener.Step.ImportingKey).progress)
+ assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
+ assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress)
+
+ checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys)
+
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a password
+ * - Log Alice on a new device
+ * - Try to restore the e2e backup with a wrong password
+ * - It must fail
+ */
+ @Test
+ fun restoreKeysBackupWithAWrongPasswordTest() {
+ val password = "password"
+ val wrongPassword = "passw0rd"
+
+ val testData = createKeysBackupScenarioWithPassword(password)
+
+ // - Try to restore the e2e backup with a wrong password
+ val latch2 = CountDownLatch(1)
+ var importRoomKeysResult: ImportRoomKeysResult? = null
+ testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ wrongPassword,
+ null,
+ null,
+ null,
+ object : TestMatrixCallback(latch2, false) {
+ override fun onSuccess(data: ImportRoomKeysResult) {
+ importRoomKeysResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ // onSuccess may not have been called
+ assertNull(importRoomKeysResult)
+
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a password
+ * - Log Alice on a new device
+ * - Restore the e2e backup with the recovery key.
+ * - Restore must be successful
+ */
+ @Test
+ fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
+ val password = "password"
+
+ val testData = createKeysBackupScenarioWithPassword(password)
+
+ // - Restore the e2e backup with the recovery key.
+ val latch2 = CountDownLatch(1)
+ var importRoomKeysResult: ImportRoomKeysResult? = null
+ testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
+ null,
+ null,
+ null,
+ object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: ImportRoomKeysResult) {
+ importRoomKeysResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys)
+
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a recovery key
+ * - And log Alice on a new device
+ * - Try to restore the e2e backup with a password
+ * - It must fail
+ */
+ @Test
+ fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
+ val testData = createKeysBackupScenarioWithPassword(null)
+
+ // - Try to restore the e2e backup with a password
+ val latch2 = CountDownLatch(1)
+ var importRoomKeysResult: ImportRoomKeysResult? = null
+ testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!,
+ "password",
+ null,
+ null,
+ null,
+ object : TestMatrixCallback(latch2, false) {
+ override fun onSuccess(data: ImportRoomKeysResult) {
+ importRoomKeysResult = data
+ super.onSuccess(data)
+ }
+ }
+ )
+ mTestHelper.await(latch2)
+
+ // onSuccess may not have been called
+ assertNull(importRoomKeysResult)
+
+ testData.cryptoTestData.close()
+ }
+
+ /**
+ * - Create a backup version
+ * - Check the returned KeysVersionResult is trusted
+ */
+ @Test
+ fun testIsKeysBackupTrusted() {
+ // - Create a backup version
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ // - Do an e2e backup to the homeserver
+ prepareAndCreateKeysBackupData(keysBackup)
+
+ // Get key backup version from the home server
+ var keysVersionResult: KeysVersionResult? = null
+ val lock = CountDownLatch(1)
+ keysBackup.getCurrentVersion(object : TestMatrixCallback(lock) {
+ override fun onSuccess(data: KeysVersionResult?) {
+ keysVersionResult = data
+ super.onSuccess(data)
+ }
+ })
+ mTestHelper.await(lock)
+
+ assertNotNull(keysVersionResult)
+
+ // - Check the returned KeyBackupVersion is trusted
+ val latch = CountDownLatch(1)
+ var keysBackupVersionTrust: KeysBackupVersionTrust? = null
+ keysBackup.getKeysBackupTrust(keysVersionResult!!, object : MatrixCallback {
+ override fun onSuccess(data: KeysBackupVersionTrust) {
+ keysBackupVersionTrust = data
+ latch.countDown()
+ }
+
+ override fun onFailure(failure: Throwable) {
+ super.onFailure(failure)
+ latch.countDown()
+ }
+ })
+ mTestHelper.await(latch)
+
+ assertNotNull(keysBackupVersionTrust)
+ assertTrue(keysBackupVersionTrust!!.usable)
+ assertEquals(1, keysBackupVersionTrust!!.signatures.size)
+
+ val signature = keysBackupVersionTrust!!.signatures[0]
+ assertTrue(signature.valid)
+ assertNotNull(signature.device)
+ assertEquals(cryptoTestData.firstSession.getMyDevice().deviceId, signature.deviceId)
+ assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId)
+
+ stateObserver.stopAndCheckStates(null)
+ cryptoTestData.close()
+ }
+
+ /**
+ * Check backup starts automatically if there is an existing and compatible backup
+ * version on the homeserver.
+ * - Create a backup version
+ * - Restart alice session
+ * -> The new alice session must back up to the same version
+ */
+ @Test
+ fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() {
+ // - Create a backup version
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ assertFalse(keysBackup.isEnabled)
+
+ val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup)
+
+ assertTrue(keysBackup.isEnabled)
+
+ // - Restart alice session
+ // - Log Alice on a new device
+ val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync)
+
+ cryptoTestData.close()
+
+ val keysBackup2 = aliceSession2.getKeysBackupService()
+
+ val stateObserver2 = StateObserver(keysBackup2)
+
+ // -> The new alice session must back up to the same version
+ val latch = CountDownLatch(1)
+ var count = 0
+ keysBackup2.addListener(object : KeysBackupStateListener {
+ override fun onStateChange(newState: KeysBackupState) {
+ // Check the backup completes
+ if (keysBackup.state == KeysBackupState.ReadyToBackUp) {
+ count++
+
+ if (count == 2) {
+ // Remove itself from the list of listeners
+ keysBackup.removeListener(this)
+
+ latch.countDown()
+ }
+ }
+ }
+ })
+ mTestHelper.await(latch)
+
+ assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion)
+
+ stateObserver.stopAndCheckStates(null)
+ stateObserver2.stopAndCheckStates(null)
+ aliceSession2.close()
+ }
+
+ /**
+ * Check WrongBackUpVersion state
+ *
+ * - Make alice back up her keys to her homeserver
+ * - Create a new backup with fake data on the homeserver
+ * - Make alice back up all her keys again
+ * -> That must fail and her backup state must be WrongBackUpVersion
+ */
+ @Test
+ fun testBackupWhenAnotherBackupWasCreated() {
+ // - Create a backup version
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ assertFalse(keysBackup.isEnabled)
+
+ // Wait for keys backup to be finished
+ val latch0 = CountDownLatch(1)
+ var count = 0
+ keysBackup.addListener(object : KeysBackupStateListener {
+ override fun onStateChange(newState: KeysBackupState) {
+ // Check the backup completes
+ if (newState == KeysBackupState.ReadyToBackUp) {
+ count++
+
+ if (count == 2) {
+ // Remove itself from the list of listeners
+ keysBackup.removeListener(this)
+
+ latch0.countDown()
+ }
+ }
+ }
+ })
+
+ // - Make alice back up her keys to her homeserver
+ prepareAndCreateKeysBackupData(keysBackup)
+
+ assertTrue(keysBackup.isEnabled)
+
+ mTestHelper.await(latch0)
+
+ // - Create a new backup with fake data on the homeserver, directly using the rest client
+ val latch = CountDownLatch(1)
+
+ val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo()
+ (keysBackup as KeysBackup).createFakeKeysBackupVersion(megolmBackupCreationInfo, TestMatrixCallback(latch))
+ mTestHelper.await(latch)
+
+ // Reset the store backup status for keys
+ (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store.resetBackupMarkers()
+
+ // - Make alice back up all her keys again
+ val latch2 = CountDownLatch(1)
+ keysBackup.backupAllGroupSessions(object : ProgressListener {
+ override fun onProgress(progress: Int, total: Int) {
+ }
+ }, TestMatrixCallback(latch2, false))
+ mTestHelper.await(latch2)
+
+ // -> That must fail and her backup state must be WrongBackUpVersion
+ assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state)
+ assertFalse(keysBackup.isEnabled)
+
+ stateObserver.stopAndCheckStates(null)
+ cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver
+ * - Log Alice on a new device
+ * - Post a message to have a new megolm session
+ * - Try to backup all
+ * -> It must fail. Backup state must be NotTrusted
+ * - Validate the old device from the new one
+ * -> Backup should automatically enable on the new device
+ * -> It must use the same backup version
+ * - Try to backup all again
+ * -> It must success
+ */
+ @Test
+ fun testBackupAfterVerifyingADevice() {
+ // - Create a backup version
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ // - Make alice back up her keys to her homeserver
+ prepareAndCreateKeysBackupData(keysBackup)
+
+ // Wait for keys backup to finish by asking again to backup keys.
+ val latch = CountDownLatch(1)
+ keysBackup.backupAllGroupSessions(object : ProgressListener {
+ override fun onProgress(progress: Int, total: Int) {
+ }
+ }, TestMatrixCallback(latch))
+ mTestHelper.await(latch)
+
+ val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
+ val oldKeyBackupVersion = keysBackup.currentBackupVersion
+ val aliceUserId = cryptoTestData.firstSession.myUserId
+
+ // Close first Alice session, else they will share the same Crypto store and the test fails.
+ cryptoTestData.firstSession.close()
+
+ // - Log Alice on a new device
+ val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
+
+ // - Post a message to have a new megolm session
+ aliceSession2.setWarnOnUnknownDevices(false)
+
+ val room2 = aliceSession2.getRoom(cryptoTestData.roomId)!!
+
+ mTestHelper.sendTextMessage(room2, "New key", 1)
+
+ // - Try to backup all in aliceSession2, it must fail
+ val keysBackup2 = aliceSession2.getKeysBackupService()
+
+ val stateObserver2 = StateObserver(keysBackup2)
+
+ var isSuccessful = false
+ val latch2 = CountDownLatch(1)
+ keysBackup2.backupAllGroupSessions(object : ProgressListener {
+ override fun onProgress(progress: Int, total: Int) {
+ }
+ }, object : TestMatrixCallback(latch2, false) {
+ override fun onSuccess(data: Unit) {
+ isSuccessful = true
+ super.onSuccess(data)
+ }
+ })
+ mTestHelper.await(latch2)
+
+ assertFalse(isSuccessful)
+
+ // Backup state must be NotTrusted
+ assertEquals(KeysBackupState.NotTrusted, keysBackup2.state)
+ assertFalse(keysBackup2.isEnabled)
+
+ // - Validate the old device from the new one
+ aliceSession2.setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, oldDeviceId, aliceSession2.myUserId)
+
+ // -> Backup should automatically enable on the new device
+ val latch4 = CountDownLatch(1)
+ keysBackup2.addListener(object : KeysBackupStateListener {
+ override fun onStateChange(newState: KeysBackupState) {
+ // Check the backup completes
+ if (keysBackup2.state == KeysBackupState.ReadyToBackUp) {
+ // Remove itself from the list of listeners
+ keysBackup2.removeListener(this)
+
+ latch4.countDown()
+ }
+ }
+ })
+ mTestHelper.await(latch4)
+
+ // -> It must use the same backup version
+ assertEquals(oldKeyBackupVersion, aliceSession2.getKeysBackupService().currentBackupVersion)
+
+ val latch5 = CountDownLatch(1)
+ aliceSession2.getKeysBackupService().backupAllGroupSessions(null, TestMatrixCallback(latch5))
+ mTestHelper.await(latch5)
+
+ // -> It must success
+ assertTrue(aliceSession2.getKeysBackupService().isEnabled)
+
+ stateObserver.stopAndCheckStates(null)
+ stateObserver2.stopAndCheckStates(null)
+ aliceSession2.close()
+ cryptoTestData.close()
+ }
+
+ /**
+ * - Do an e2e backup to the homeserver with a recovery key
+ * - Delete the backup
+ */
+ @Test
+ fun deleteKeysBackupTest() {
+ // - Create a backup version
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ assertFalse(keysBackup.isEnabled)
+
+ val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup)
+
+ assertTrue(keysBackup.isEnabled)
+
+ val latch = CountDownLatch(1)
+
+ // Delete the backup
+ keysBackup.deleteBackup(keyBackupCreationInfo.version, TestMatrixCallback(latch))
+
+ mTestHelper.await(latch)
+
+ // Backup is now disabled
+ assertFalse(keysBackup.isEnabled)
+
+ stateObserver.stopAndCheckStates(null)
+ cryptoTestData.close()
+ }
+
+ /* ==========================================================================================
+ * Private
+ * ========================================================================================== */
+
+ /**
+ * As KeysBackup is doing asynchronous call to update its internal state, this method help to wait for the
+ * KeysBackup object to be in the specified state
+ */
+ private fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) {
+ // If already in the wanted state, return
+ if (session.getKeysBackupService().state == state) {
+ return
+ }
+
+ // Else observe state changes
+ val latch = CountDownLatch(1)
+
+ session.getKeysBackupService().addListener(object : KeysBackupStateListener {
+ override fun onStateChange(newState: KeysBackupState) {
+ if (newState == state) {
+ session.getKeysBackupService().removeListener(this)
+ latch.countDown()
+ }
+ }
+ })
+
+ mTestHelper.await(latch)
+ }
+
+ private data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo,
+ val version: String)
+
+ private fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService,
+ password: String? = null): PrepareKeysBackupDataResult {
+ val stateObserver = StateObserver(keysBackup)
+
+ var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
+ val latch = CountDownLatch(1)
+ keysBackup.prepareKeysBackupVersion(password, null, object : MatrixCallback {
+ override fun onSuccess(data: MegolmBackupCreationInfo) {
+ megolmBackupCreationInfo = data
+
+ latch.countDown()
+ }
+
+ override fun onFailure(failure: Throwable) {
+ fail(failure.localizedMessage)
+
+ latch.countDown()
+ }
+ })
+ mTestHelper.await(latch)
+
+ assertNotNull(megolmBackupCreationInfo)
+
+ assertFalse(keysBackup.isEnabled)
+
+ val latch2 = CountDownLatch(1)
+
+ // Create the version
+ var version: String? = null
+ keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback(latch2) {
+ override fun onSuccess(data: KeysVersion) {
+ assertNotNull(data)
+ assertNotNull(data.version)
+
+ version = data.version
+
+ super.onSuccess(data)
+ }
+ })
+ mTestHelper.await(latch2)
+
+ // Backup must be enable now
+ assertTrue(keysBackup.isEnabled)
+ assertNotNull(version)
+
+ stateObserver.stopAndCheckStates(null)
+ return PrepareKeysBackupDataResult(megolmBackupCreationInfo!!, version!!)
+ }
+
+ private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
+ assertNotNull(keys1)
+ assertNotNull(keys2)
+
+ assertEquals(keys1?.algorithm, keys2?.algorithm)
+ assertEquals(keys1?.roomId, keys2?.roomId)
+ // No need to compare the shortcut
+ // assertEquals(keys1?.sender_claimed_ed25519_key, keys2?.sender_claimed_ed25519_key)
+ assertEquals(keys1?.senderKey, keys2?.senderKey)
+ assertEquals(keys1?.sessionId, keys2?.sessionId)
+ assertEquals(keys1?.sessionKey, keys2?.sessionKey)
+
+ assertListEquals(keys1?.forwardingCurve25519KeyChain, keys2?.forwardingCurve25519KeyChain)
+ assertDictEquals(keys1?.senderClaimedKeys, keys2?.senderClaimedKeys)
+ }
+
+ /**
+ * Data class to store result of [createKeysBackupScenarioWithPassword]
+ */
+ private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
+ val aliceKeys: List,
+ val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
+ val aliceSession2: Session)
+
+ /**
+ * Common initial condition
+ * - Do an e2e backup to the homeserver
+ * - Log Alice on a new device, and wait for its keysBackup object to be ready (in state NotTrusted)
+ *
+ * @param password optional password
+ */
+ private fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+ val cryptoStore = (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store
+ val keysBackup = cryptoTestData.firstSession.getKeysBackupService()
+
+ val stateObserver = StateObserver(keysBackup)
+
+ val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
+
+ // - Do an e2e backup to the homeserver
+ val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
+
+ val latch = CountDownLatch(1)
+ var lastProgress = 0
+ var lastTotal = 0
+ keysBackup.backupAllGroupSessions(object : ProgressListener {
+ override fun onProgress(progress: Int, total: Int) {
+ lastProgress = progress
+ lastTotal = total
+ }
+ }, TestMatrixCallback(latch))
+ mTestHelper.await(latch)
+
+ assertEquals(2, lastProgress)
+ assertEquals(2, lastTotal)
+
+ val aliceUserId = cryptoTestData.firstSession.myUserId
+
+ // Logout first Alice session, else they will share the same Crypto store and some tests may fail.
+ val latch2 = CountDownLatch(1)
+ cryptoTestData.firstSession.signOut(true, TestMatrixCallback(latch2))
+ mTestHelper.await(latch2)
+
+ // - Log Alice on a new device
+ val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
+
+ // Test check: aliceSession2 has no keys at login
+ assertEquals(0, aliceSession2.inboundGroupSessionsCount(false))
+
+ // Wait for backup state to be NotTrusted
+ waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
+
+ stateObserver.stopAndCheckStates(null)
+
+ return KeysBackupScenarioData(cryptoTestData,
+ aliceKeys,
+ prepareKeysBackupDataResult,
+ aliceSession2)
+ }
+
+ /**
+ * Common restore success check after [createKeysBackupScenarioWithPassword]:
+ * - Imported keys number must be correct
+ * - The new device must have the same count of megolm keys
+ * - Alice must have the same keys on both devices
+ */
+ private fun checkRestoreSuccess(testData: KeysBackupScenarioData,
+ total: Int,
+ imported: Int) {
+ // - Imported keys number must be correct
+ assertEquals(testData.aliceKeys.size, total)
+ assertEquals(total, imported)
+
+ // - The new device must have the same count of megolm keys
+ assertEquals(testData.aliceKeys.size, testData.aliceSession2.inboundGroupSessionsCount(false))
+
+ // - Alice must have the same keys on both devices
+ for (aliceKey1 in testData.aliceKeys) {
+ val aliceKey2 = (testData.aliceSession2.getKeysBackupService() as KeysBackup).store
+ .getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!)
+ assertNotNull(aliceKey2)
+ assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt
new file mode 100644
index 0000000000..3f2e33d73b
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.crypto.keysbackup
+
+import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
+import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
+import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import java.util.concurrent.CountDownLatch
+
+/**
+ * This class observe the state change of a KeysBackup object and provide a method to check the several state change
+ * It checks all state transitions and detected forbidden transition
+ */
+internal class StateObserver(private val keysBackup: KeysBackupService,
+ private val latch: CountDownLatch? = null,
+ private val expectedStateChange: Int = -1) : KeysBackupStateListener {
+
+ private val allowedStateTransitions = listOf(
+ KeysBackupState.BackingUp to KeysBackupState.ReadyToBackUp,
+ KeysBackupState.BackingUp to KeysBackupState.WrongBackUpVersion,
+
+ KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Disabled,
+ KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.NotTrusted,
+ KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.ReadyToBackUp,
+ KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Unknown,
+ KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.WrongBackUpVersion,
+
+ KeysBackupState.Disabled to KeysBackupState.Enabling,
+
+ KeysBackupState.Enabling to KeysBackupState.Disabled,
+ KeysBackupState.Enabling to KeysBackupState.ReadyToBackUp,
+
+ KeysBackupState.NotTrusted to KeysBackupState.CheckingBackUpOnHomeserver,
+ // This transition happens when we trust the device
+ KeysBackupState.NotTrusted to KeysBackupState.ReadyToBackUp,
+
+ KeysBackupState.ReadyToBackUp to KeysBackupState.WillBackUp,
+
+ KeysBackupState.Unknown to KeysBackupState.CheckingBackUpOnHomeserver,
+
+ KeysBackupState.WillBackUp to KeysBackupState.BackingUp,
+
+ KeysBackupState.WrongBackUpVersion to KeysBackupState.CheckingBackUpOnHomeserver,
+
+ // FIXME These transitions are observed during test, and I'm not sure they should occur. Don't have time to investigate now
+ KeysBackupState.ReadyToBackUp to KeysBackupState.BackingUp,
+ KeysBackupState.ReadyToBackUp to KeysBackupState.ReadyToBackUp,
+ KeysBackupState.WillBackUp to KeysBackupState.ReadyToBackUp,
+ KeysBackupState.WillBackUp to KeysBackupState.Unknown
+ )
+
+ private val stateList = ArrayList()
+ private var lastTransitionError: String? = null
+
+ init {
+ keysBackup.addListener(this)
+ }
+
+ // TODO Make expectedStates mandatory to enforce test
+ fun stopAndCheckStates(expectedStates: List?) {
+ keysBackup.removeListener(this)
+
+ expectedStates?.let {
+ assertEquals(it.size, stateList.size)
+
+ for (i in it.indices) {
+ assertEquals("The state $i is not correct. states: " + stateList.joinToString(separator = " "), it[i], stateList[i])
+ }
+ }
+
+ assertNull("states: " + stateList.joinToString(separator = " "), lastTransitionError)
+ }
+
+ override fun onStateChange(newState: KeysBackupState) {
+ stateList.add(newState)
+
+ // Check that state transition is valid
+ if (stateList.size >= 2
+ && !allowedStateTransitions.contains(stateList[stateList.size - 2] to newState)) {
+ // Forbidden transition detected
+ lastTransitionError = "Forbidden transition detected from " + stateList[stateList.size - 2] + " to " + newState
+ }
+
+ if (expectedStateChange == stateList.size) {
+ latch?.countDown()
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt
new file mode 100644
index 0000000000..c05523f009
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt
@@ -0,0 +1,525 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.crypto.verification
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import im.vector.matrix.android.InstrumentedTest
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.crypto.sas.*
+import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.api.session.events.model.toModel
+import im.vector.matrix.android.common.CommonTestHelper
+import im.vector.matrix.android.common.CryptoTestHelper
+import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
+import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
+import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
+import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
+import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
+import org.junit.Assert.*
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import java.util.*
+import java.util.concurrent.CountDownLatch
+
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SASTest : InstrumentedTest {
+ private val mTestHelper = CommonTestHelper(context())
+ private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+
+ @Test
+ fun test_aliceStartThenAliceCancel() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+
+ val aliceSession = cryptoTestData.firstSession
+ val bobSession = cryptoTestData.secondSession
+
+ val aliceSasMgr = aliceSession.getSasVerificationService()
+ val bobSasMgr = bobSession!!.getSasVerificationService()
+
+ val bobTxCreatedLatch = CountDownLatch(1)
+ val bobListener = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ bobTxCreatedLatch.countDown()
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ bobSasMgr.addListener(bobListener)
+
+ val txID = aliceSasMgr.beginKeyVerificationSAS(bobSession.myUserId, bobSession.getMyDevice().deviceId)
+ assertNotNull("Alice should have a started transaction", txID)
+
+ val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!)
+ assertNotNull("Alice should have a started transaction", aliceKeyTx)
+
+ mTestHelper.await(bobTxCreatedLatch)
+ bobSasMgr.removeListener(bobListener)
+
+ val bobKeyTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID)
+
+ assertNotNull("Bob should have started verif transaction", bobKeyTx)
+ assertTrue(bobKeyTx is SASVerificationTransaction)
+ assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
+ assertTrue(aliceKeyTx is SASVerificationTransaction)
+ assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
+
+ val aliceSasTx = aliceKeyTx as SASVerificationTransaction?
+ val bobSasTx = bobKeyTx as SASVerificationTransaction?
+
+ assertEquals("Alice state should be started", SasVerificationTxState.Started, aliceSasTx!!.state)
+ assertEquals("Bob state should be started by alice", SasVerificationTxState.OnStarted, bobSasTx!!.state)
+
+ // Let's cancel from alice side
+ val cancelLatch = CountDownLatch(1)
+
+ val bobListener2 = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ if (tx.transactionId == txID) {
+ if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) {
+ cancelLatch.countDown()
+ }
+ }
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ bobSasMgr.addListener(bobListener2)
+
+ aliceSasTx.cancel(CancelCode.User)
+ mTestHelper.await(cancelLatch)
+
+ assertEquals("Should be cancelled on alice side",
+ SasVerificationTxState.Cancelled, aliceSasTx.state)
+ assertEquals("Should be cancelled on bob side",
+ SasVerificationTxState.OnCancelled, bobSasTx.state)
+
+ assertEquals("Should be User cancelled on alice side",
+ CancelCode.User, aliceSasTx.cancelledReason)
+ assertEquals("Should be User cancelled on bob side",
+ CancelCode.User, aliceSasTx.cancelledReason)
+
+ assertNull(bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID))
+ assertNull(aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID))
+
+ cryptoTestData.close()
+ }
+
+ @Test
+ fun test_key_agreement_protocols_must_include_curve25519() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+
+ val bobSession = cryptoTestData.secondSession!!
+
+ val protocols = listOf("meh_dont_know")
+ val tid = "00000000"
+
+ // Bob should receive a cancel
+ var canceledToDeviceEvent: Event? = null
+ val cancelLatch = CountDownLatch(1)
+ // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
+ // TODO override fun onToDeviceEvent(event: Event?) {
+ // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
+ // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
+ // TODO canceledToDeviceEvent = event
+ // TODO cancelLatch.countDown()
+ // TODO }
+ // TODO }
+ // TODO }
+ // TODO })
+
+ val aliceSession = cryptoTestData.firstSession
+ val aliceUserID = aliceSession.myUserId
+ val aliceDevice = aliceSession.getMyDevice().deviceId
+
+ val aliceListener = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
+ (tx as IncomingSASVerificationTransaction).performAccept()
+ }
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ aliceSession.getSasVerificationService().addListener(aliceListener)
+
+ fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
+
+ mTestHelper.await(cancelLatch)
+
+ val cancelReq = canceledToDeviceEvent!!.content.toModel()!!
+ assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
+
+ cryptoTestData.close()
+ }
+
+ @Test
+ fun test_key_agreement_macs_Must_include_hmac_sha256() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+
+ val bobSession = cryptoTestData.secondSession!!
+
+ val mac = listOf("shaBit")
+ val tid = "00000000"
+
+ // Bob should receive a cancel
+ var canceledToDeviceEvent: Event? = null
+ val cancelLatch = CountDownLatch(1)
+ // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
+ // TODO override fun onToDeviceEvent(event: Event?) {
+ // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
+ // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
+ // TODO canceledToDeviceEvent = event
+ // TODO cancelLatch.countDown()
+ // TODO }
+ // TODO }
+ // TODO }
+ // TODO })
+
+ val aliceSession = cryptoTestData.firstSession
+ val aliceUserID = aliceSession.myUserId
+ val aliceDevice = aliceSession.getMyDevice().deviceId
+
+ fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
+
+ mTestHelper.await(cancelLatch)
+
+ val cancelReq = canceledToDeviceEvent!!.content.toModel()!!
+ assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
+
+ cryptoTestData.close()
+ }
+
+ @Test
+ fun test_key_agreement_short_code_include_decimal() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+
+ val bobSession = cryptoTestData.secondSession!!
+
+ val codes = listOf("bin", "foo", "bar")
+ val tid = "00000000"
+
+ // Bob should receive a cancel
+ var canceledToDeviceEvent: Event? = null
+ val cancelLatch = CountDownLatch(1)
+ // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
+ // TODO override fun onToDeviceEvent(event: Event?) {
+ // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
+ // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
+ // TODO canceledToDeviceEvent = event
+ // TODO cancelLatch.countDown()
+ // TODO }
+ // TODO }
+ // TODO }
+ // TODO })
+
+ val aliceSession = cryptoTestData.firstSession
+ val aliceUserID = aliceSession.myUserId
+ val aliceDevice = aliceSession.getMyDevice().deviceId
+
+ fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
+
+ mTestHelper.await(cancelLatch)
+
+ val cancelReq = canceledToDeviceEvent!!.content.toModel()!!
+ assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
+
+ cryptoTestData.close()
+ }
+
+ private fun fakeBobStart(bobSession: Session,
+ aliceUserID: String?,
+ aliceDevice: String?,
+ tid: String,
+ protocols: List = SASVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
+ hashes: List = SASVerificationTransaction.KNOWN_HASHES,
+ mac: List = SASVerificationTransaction.KNOWN_MACS,
+ codes: List = SASVerificationTransaction.KNOWN_SHORT_CODES) {
+ val startMessage = KeyVerificationStart()
+ startMessage.fromDevice = bobSession.getMyDevice().deviceId
+ startMessage.method = KeyVerificationStart.VERIF_METHOD_SAS
+ startMessage.transactionID = tid
+ startMessage.keyAgreementProtocols = protocols
+ startMessage.hashes = hashes
+ startMessage.messageAuthenticationCodes = mac
+ startMessage.shortAuthenticationStrings = codes
+
+ val contentMap = MXUsersDevicesMap()
+ contentMap.setObject(aliceUserID, aliceDevice, startMessage)
+
+ // TODO val sendLatch = CountDownLatch(1)
+ // TODO bobSession.cryptoRestClient.sendToDevice(
+ // TODO EventType.KEY_VERIFICATION_START,
+ // TODO contentMap,
+ // TODO tid,
+ // TODO TestMatrixCallback(sendLatch)
+ // TODO )
+ }
+
+ // any two devices may only have at most one key verification in flight at a time.
+ // If a device has two verifications in progress with the same device, then it should cancel both verifications.
+ @Test
+ fun test_aliceStartTwoRequests() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+
+ val aliceSession = cryptoTestData.firstSession
+ val bobSession = cryptoTestData.secondSession
+
+ val aliceSasMgr = aliceSession.getSasVerificationService()
+
+ val aliceCreatedLatch = CountDownLatch(2)
+ val aliceCancelledLatch = CountDownLatch(2)
+ val createdTx = ArrayList()
+ val aliceListener = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {
+ createdTx.add(tx as SASVerificationTransaction)
+ aliceCreatedLatch.countDown()
+ }
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) {
+ aliceCancelledLatch.countDown()
+ }
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ aliceSasMgr.addListener(aliceListener)
+
+ val bobUserId = bobSession!!.myUserId
+ val bobDeviceId = bobSession.getMyDevice().deviceId
+ aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
+ aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
+
+ mTestHelper.await(aliceCreatedLatch)
+ mTestHelper.await(aliceCancelledLatch)
+
+ cryptoTestData.close()
+ }
+
+ /**
+ * Test that when alice starts a 'correct' request, bob agrees.
+ */
+ @Test
+ fun test_aliceAndBobAgreement() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+
+ val aliceSession = cryptoTestData.firstSession
+ val bobSession = cryptoTestData.secondSession
+
+ val aliceSasMgr = aliceSession.getSasVerificationService()
+ val bobSasMgr = bobSession!!.getSasVerificationService()
+
+ var accepted: KeyVerificationAccept? = null
+ var startReq: KeyVerificationStart? = null
+
+ val aliceAcceptedLatch = CountDownLatch(1)
+ val aliceListener = object : SasVerificationService.SasVerificationListener {
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnAccepted) {
+ val at = tx as SASVerificationTransaction
+ accepted = at.accepted
+ startReq = at.startReq
+ aliceAcceptedLatch.countDown()
+ }
+ }
+ }
+ aliceSasMgr.addListener(aliceListener)
+
+ val bobListener = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
+ val at = tx as IncomingSASVerificationTransaction
+ at.performAccept()
+ }
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ bobSasMgr.addListener(bobListener)
+
+ val bobUserId = bobSession.myUserId
+ val bobDeviceId = bobSession.getMyDevice().deviceId
+ aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
+ mTestHelper.await(aliceAcceptedLatch)
+
+ assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
+
+ // check that agreement is valid
+ assertTrue("Agreed Protocol should be Valid", accepted!!.isValid())
+ assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols!!.contains(accepted!!.keyAgreementProtocol))
+ assertTrue("Hash should be known by alice", startReq!!.hashes!!.contains(accepted!!.hash))
+ assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode))
+
+ accepted!!.shortAuthenticationStrings?.forEach {
+ assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
+ }
+
+ cryptoTestData.close()
+ }
+
+ @Test
+ fun test_aliceAndBobSASCode() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+
+ val aliceSession = cryptoTestData.firstSession
+ val bobSession = cryptoTestData.secondSession
+
+ val aliceSasMgr = aliceSession.getSasVerificationService()
+ val bobSasMgr = bobSession!!.getSasVerificationService()
+
+ val aliceSASLatch = CountDownLatch(1)
+ val aliceListener = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ val uxState = (tx as OutgoingSASVerificationRequest).uxState
+ when (uxState) {
+ OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
+ aliceSASLatch.countDown()
+ }
+ else -> Unit
+ }
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ aliceSasMgr.addListener(aliceListener)
+
+ val bobSASLatch = CountDownLatch(1)
+ val bobListener = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ val uxState = (tx as IncomingSASVerificationTransaction).uxState
+ when (uxState) {
+ IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
+ tx.performAccept()
+ }
+ else -> Unit
+ }
+ if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
+ bobSASLatch.countDown()
+ }
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ bobSasMgr.addListener(bobListener)
+
+ val bobUserId = bobSession.myUserId
+ val bobDeviceId = bobSession.getMyDevice().deviceId
+ val verificationSAS = aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
+ mTestHelper.await(aliceSASLatch)
+ mTestHelper.await(bobSASLatch)
+
+ val aliceTx = aliceSasMgr.getExistingTransaction(bobUserId, verificationSAS!!) as SASVerificationTransaction
+ val bobTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASVerificationTransaction
+
+ assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
+ bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
+
+ cryptoTestData.close()
+ }
+
+ @Test
+ fun test_happyPath() {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+
+ val aliceSession = cryptoTestData.firstSession
+ val bobSession = cryptoTestData.secondSession
+
+ val aliceSasMgr = aliceSession.getSasVerificationService()
+ val bobSasMgr = bobSession!!.getSasVerificationService()
+
+ val aliceSASLatch = CountDownLatch(1)
+ val aliceListener = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ val uxState = (tx as OutgoingSASVerificationRequest).uxState
+ when (uxState) {
+ OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
+ tx.userHasVerifiedShortCode()
+ }
+ OutgoingSasVerificationRequest.UxState.VERIFIED -> {
+ aliceSASLatch.countDown()
+ }
+ else -> Unit
+ }
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ aliceSasMgr.addListener(aliceListener)
+
+ val bobSASLatch = CountDownLatch(1)
+ val bobListener = object : SasVerificationService.SasVerificationListener {
+ override fun transactionCreated(tx: SasVerificationTransaction) {}
+
+ override fun transactionUpdated(tx: SasVerificationTransaction) {
+ val uxState = (tx as IncomingSASVerificationTransaction).uxState
+ when (uxState) {
+ IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
+ tx.performAccept()
+ }
+ IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
+ tx.userHasVerifiedShortCode()
+ }
+ IncomingSasVerificationTransaction.UxState.VERIFIED -> {
+ bobSASLatch.countDown()
+ }
+ else -> Unit
+ }
+ }
+
+ override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
+ }
+ bobSasMgr.addListener(bobListener)
+
+ val bobUserId = bobSession.myUserId
+ val bobDeviceId = bobSession.getMyDevice().deviceId
+ aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
+ mTestHelper.await(aliceSASLatch)
+ mTestHelper.await(bobSASLatch)
+
+ // Assert that devices are verified
+ val bobDeviceInfoFromAlicePOV: MXDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
+ val aliceDeviceInfoFromBobPOV: MXDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
+
+ // latch wait a bit again
+ Thread.sleep(1000)
+
+ assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
+ assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
+ cryptoTestData.close()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt
index cf0302166f..72affe24bb 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
+import im.vector.matrix.android.internal.util.md5
/**
* This data class hold credentials user data.
@@ -34,3 +35,7 @@ data class Credentials(
// Optional data that may contain info to override home server and/or identity server
@Json(name = "well_known") val wellKnown: WellKnown? = null
)
+
+internal fun Credentials.sessionId(): String {
+ return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5()
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt
index a0ac751a72..5bd219247c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt
@@ -75,6 +75,11 @@ interface Session :
val myUserId: String
get() = sessionParams.credentials.userId
+ /**
+ * The sessionId
+ */
+ val sessionId: String
+
/**
* This method allow to open a session. It does start some service on the background.
*/
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt
index 22bf564a8a..224b4262ab 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt
@@ -25,7 +25,6 @@ object EventType {
const val MESSAGE = "m.room.message"
const val STICKER = "m.sticker"
const val ENCRYPTED = "m.room.encrypted"
- const val ENCRYPTION = "m.room.encryption"
const val FEEDBACK = "m.room.message.feedback"
const val TYPING = "m.typing"
const val REDACTION = "m.room.redaction"
@@ -54,6 +53,7 @@ object EventType {
const val STATE_ROOM_HISTORY_VISIBILITY = "m.room.history_visibility"
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
+ const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
// Call Events
@@ -91,7 +91,8 @@ object EventType {
STATE_ROOM_CANONICAL_ALIAS,
STATE_ROOM_HISTORY_VISIBILITY,
STATE_ROOM_RELATED_GROUPS,
- STATE_ROOM_PINNED_EVENT
+ STATE_ROOM_PINNED_EVENT,
+ STATE_ROOM_ENCRYPTION
)
fun isStateEvent(type: String): Boolean {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt
index 3221c355e8..0c3316e802 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt
@@ -22,12 +22,13 @@ import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
-import im.vector.matrix.android.api.session.room.reporting.ReportingService
import im.vector.matrix.android.api.session.room.read.ReadService
+import im.vector.matrix.android.api.session.room.reporting.ReportingService
import im.vector.matrix.android.api.session.room.send.DraftService
import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService
+import im.vector.matrix.android.api.session.room.typing.TypingService
import im.vector.matrix.android.api.util.Optional
/**
@@ -38,6 +39,7 @@ interface Room :
SendService,
DraftService,
ReadService,
+ TypingService,
MembershipService,
StateService,
ReportingService,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt
index f8c15fde47..124b2aef17 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt
@@ -16,6 +16,8 @@
package im.vector.matrix.android.api.session.room.crypto
+import im.vector.matrix.android.api.MatrixCallback
+
interface RoomCryptoService {
fun isEncrypted(): Boolean
@@ -23,4 +25,6 @@ interface RoomCryptoService {
fun encryptionAlgorithm(): String?
fun shouldEncryptForInvitedMembers(): Boolean
+
+ fun enableEncryptionWithAlgorithm(algorithm: String, callback: MatrixCallback)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt
index 7a8518c5de..b3260c28fd 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt
@@ -29,7 +29,8 @@ fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {})
data class RoomMemberQueryParams(
val displayName: QueryStringValue,
val memberships: List,
- val userId: QueryStringValue
+ val userId: QueryStringValue,
+ val excludeSelf: Boolean
) {
class Builder {
@@ -37,11 +38,13 @@ data class RoomMemberQueryParams(
var userId: QueryStringValue = QueryStringValue.NoCondition
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var memberships: List = Membership.all()
+ var excludeSelf: Boolean = false
fun build() = RoomMemberQueryParams(
displayName = displayName,
memberships = memberships,
- userId = userId
+ userId = userId,
+ excludeSelf = excludeSelf
)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomGuestAccessContent.kt
new file mode 100644
index 0000000000..4c814f7914
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomGuestAccessContent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.room.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Class representing the EventType.STATE_ROOM_GUEST_ACCESS state event content
+ * Ref: https://matrix.org/docs/spec/client_server/latest#m-room-guest-access
+ */
+@JsonClass(generateAdapter = true)
+data class RoomGuestAccessContent(
+ // Required. Whether guests can join the room. One of: ["can_join", "forbidden"]
+ @Json(name = "guest_access") val guestAccess: GuestAccess? = null
+)
+
+enum class GuestAccess(val value: String) {
+ @Json(name = "can_join")
+ CanJoin("can_join"),
+ @Json(name = "forbidden")
+ Forbidden("forbidden")
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt
index 582a10a4f3..1f7a7b144a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt
@@ -44,7 +44,8 @@ data class RoomSummary(
val versioningState: VersioningState = VersioningState.NONE,
val readMarkerId: String? = null,
val userDrafts: List = emptyList(),
- var isEncrypted: Boolean
+ var isEncrypted: Boolean,
+ val typingRoomMemberIds: List = emptyList()
) {
val isVersioned: Boolean
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt
index 74298b42d5..2119c586db 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt
@@ -28,6 +28,8 @@ import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
+import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import timber.log.Timber
/**
* Parameter to create a room, with facilities functions to configure it
@@ -88,7 +90,7 @@ class CreateRoomParams {
* A list of state events to set in the new room.
* This allows the user to override the default state events set in the new room.
* The expected format of the state events are an object with type, state_key and content keys set.
- * Takes precedence over events set by presets, but gets overriden by name and topic keys.
+ * Takes precedence over events set by presets, but gets overridden by name and topic keys.
*/
@Json(name = "initial_state")
var initialStates: MutableList? = null
@@ -120,14 +122,14 @@ class CreateRoomParams {
*
* @param algorithm the algorithm
*/
- fun addCryptoAlgorithm(algorithm: String) {
- if (algorithm.isNotBlank()) {
- val contentMap = HashMap()
- contentMap["algorithm"] = algorithm
+ fun enableEncryptionWithAlgorithm(algorithm: String) {
+ if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
+ val contentMap = mapOf("algorithm" to algorithm)
- val algoEvent = Event(type = EventType.ENCRYPTION,
- stateKey = "",
- content = contentMap.toContent()
+ val algoEvent = Event(
+ type = EventType.STATE_ROOM_ENCRYPTION,
+ stateKey = "",
+ content = contentMap.toContent()
)
if (null == initialStates) {
@@ -135,6 +137,8 @@ class CreateRoomParams {
} else {
initialStates!!.add(algoEvent)
}
+ } else {
+ Timber.e("Unsupported algorithm: $algorithm")
}
}
@@ -145,15 +149,15 @@ class CreateRoomParams {
*/
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
// Remove the existing value if any.
- initialStates?.removeAll { it.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY }
+ initialStates?.removeAll { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }
if (historyVisibility != null) {
- val contentMap = HashMap()
- contentMap["history_visibility"] = historyVisibility
+ val contentMap = mapOf("history_visibility" to historyVisibility)
- val historyVisibilityEvent = Event(type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
- stateKey = "",
- content = contentMap.toContent())
+ val historyVisibilityEvent = Event(
+ type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
+ stateKey = "",
+ content = contentMap.toContent())
if (null == initialStates) {
initialStates = mutableListOf(historyVisibilityEvent)
@@ -192,8 +196,8 @@ class CreateRoomParams {
*/
fun isDirect(): Boolean {
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
- && isDirect == true
- && (1 == getInviteCount() || 1 == getInvite3PidCount())
+ && isDirect == true
+ && (1 == getInviteCount() || 1 == getInvite3PidCount())
}
/**
@@ -218,8 +222,8 @@ class CreateRoomParams {
invite3pids = ArrayList()
}
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
- medium = ThreePidMedium.EMAIL,
- address = id)
+ medium = ThreePidMedium.EMAIL,
+ address = id)
invite3pids!!.add(pid)
} else if (isUserId(id)) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt
index 09c77e9860..55bf816b7a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt
@@ -28,6 +28,11 @@ interface StateService {
*/
fun updateTopic(topic: String, callback: MatrixCallback)
+ /**
+ * Enable encryption of the room
+ */
+ fun enableEncryption(algorithm: String, callback: MatrixCallback)
+
fun getStateEvent(eventType: String): Event?
fun getStateEventLive(eventType: String): LiveData>
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/typing/TypingService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/typing/TypingService.kt
new file mode 100644
index 0000000000..8ef550531e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/typing/TypingService.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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 im.vector.matrix.android.api.session.room.typing
+
+/**
+ * This interface defines methods to handle typing data. It's implemented at the room level.
+ */
+interface TypingService {
+
+ /**
+ * To call when user is typing a message in the room
+ * The SDK will handle the requests scheduling to the homeserver:
+ * - No more than one typing request per 10s
+ * - If not called after 10s, the SDK will notify the homeserver that the user is not typing anymore
+ */
+ fun userIsTyping()
+
+ /**
+ * To call when user stops typing in the room
+ * Notify immediately the homeserver that the user is not typing anymore in the room, for
+ * instance when user has emptied the composer, or when the user quits the timeline screen.
+ */
+ fun userStopsTyping()
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt
index 76ca9291ec..3fb086ac45 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt
@@ -40,8 +40,8 @@ interface SignOutService {
/**
* Sign out, and release the session, clear all the session data, including crypto data
- * @param sigOutFromHomeserver true if the sign out request has to be done
+ * @param signOutFromHomeserver true if the sign out request has to be done
*/
- fun signOut(sigOutFromHomeserver: Boolean,
+ fun signOut(signOutFromHomeserver: Boolean,
callback: MatrixCallback): Cancelable
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt
index c813a6813f..918f5f2f55 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt
@@ -17,6 +17,7 @@
package im.vector.matrix.android.internal
import im.vector.matrix.android.api.auth.data.SessionParams
+import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.MatrixComponent
@@ -29,10 +30,11 @@ import javax.inject.Inject
internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent,
private val sessionParamsStore: SessionParamsStore) {
+ // SessionId -> SessionComponent
private val sessionComponents = HashMap()
- fun getSessionComponent(userId: String): SessionComponent? {
- val sessionParams = sessionParamsStore.get(userId) ?: return null
+ fun getSessionComponent(sessionId: String): SessionComponent? {
+ val sessionParams = sessionParamsStore.get(sessionId) ?: return null
return getOrCreateSessionComponent(sessionParams)
}
@@ -40,17 +42,17 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
return getOrCreateSessionComponent(sessionParams).session()
}
- fun releaseSession(userId: String) {
- if (sessionComponents.containsKey(userId).not()) {
- throw RuntimeException("You don't have a session for the user $userId")
+ fun releaseSession(sessionId: String) {
+ if (sessionComponents.containsKey(sessionId).not()) {
+ throw RuntimeException("You don't have a session for id $sessionId")
}
- sessionComponents.remove(userId)?.also {
+ sessionComponents.remove(sessionId)?.also {
it.session().close()
}
}
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
- return sessionComponents.getOrPut(sessionParams.credentials.userId) {
+ return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) {
DaggerSessionComponent
.factory()
.create(matrixComponent, sessionParams)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt
index 93349f4bbc..d5dd7e2959 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt
@@ -45,14 +45,15 @@ import okhttp3.OkHttpClient
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
-internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
- private val okHttpClient: Lazy,
- private val retrofitFactory: RetrofitFactory,
- private val coroutineDispatchers: MatrixCoroutineDispatchers,
- private val sessionParamsStore: SessionParamsStore,
- private val sessionManager: SessionManager,
- private val sessionCreator: SessionCreator,
- private val pendingSessionStore: PendingSessionStore
+internal class DefaultAuthenticationService @Inject constructor(
+ @Unauthenticated
+ private val okHttpClient: Lazy,
+ private val retrofitFactory: RetrofitFactory,
+ private val coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val sessionParamsStore: SessionParamsStore,
+ private val sessionManager: SessionManager,
+ private val sessionCreator: SessionCreator,
+ private val pendingSessionStore: PendingSessionStore
) : AuthenticationService {
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
@@ -112,7 +113,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
// First check the homeserver version
runCatching {
- executeRequest {
+ executeRequest(null) {
apiCall = authAPI.versions()
}
}
@@ -141,7 +142,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
val authAPI = buildAuthAPI(homeServerConnectionConfig)
// Ok, try to get the config.json file of a RiotWeb client
- val riotConfig = executeRequest {
+ val riotConfig = executeRequest(null) {
apiCall = authAPI.getRiotConfig()
}
@@ -153,7 +154,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
- val versions = executeRequest {
+ val versions = executeRequest(null) {
apiCall = newAuthAPI.versions()
}
@@ -167,7 +168,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
return if (versions.isSupportedBySdk()) {
// Get the login flow
- val loginFlowResponse = executeRequest {
+ val loginFlowResponse = executeRequest(null) {
apiCall = authAPI.getLoginFlows()
}
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt
index 57c22b0053..f99b95c2b3 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt
@@ -21,7 +21,7 @@ import im.vector.matrix.android.api.auth.data.SessionParams
internal interface SessionParamsStore {
- fun get(userId: String): SessionParams?
+ fun get(sessionId: String): SessionParams?
fun getLast(): SessionParams?
@@ -29,11 +29,11 @@ internal interface SessionParamsStore {
suspend fun save(sessionParams: SessionParams)
- suspend fun setTokenInvalid(userId: String)
+ suspend fun setTokenInvalid(sessionId: String)
suspend fun updateCredentials(newCredentials: Credentials)
- suspend fun delete(userId: String)
+ suspend fun delete(sessionId: String)
suspend fun deleteAll()
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt
index 7d7f8cc22c..e5e77cb14a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt
@@ -17,7 +17,7 @@
package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.api.auth.data.Credentials
-import im.vector.matrix.android.internal.auth.createSessionId
+import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.internal.di.MoshiProvider
import io.realm.DynamicRealm
import io.realm.RealmMigration
@@ -71,14 +71,13 @@ internal object AuthRealmMigration : RealmMigration {
?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java)
?.setRequired(SessionParamsEntityFields.SESSION_ID, true)
?.transform {
- val userId = it.getString(SessionParamsEntityFields.USER_ID)
val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON)
val credentials = MoshiProvider.providesMoshi()
.adapter(Credentials::class.java)
.fromJson(credentialsJson)
- it.set(SessionParamsEntityFields.SESSION_ID, createSessionId(userId, credentials?.deviceId))
+ it.set(SessionParamsEntityFields.SESSION_ID, credentials!!.sessionId())
}
?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt
index a4774c632a..9491d5737c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt
@@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.SessionParams
+import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase
@@ -42,11 +43,11 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
}
}
- override fun get(userId: String): SessionParams? {
+ override fun get(sessionId: String): SessionParams? {
return Realm.getInstance(realmConfiguration).use { realm ->
realm
.where(SessionParamsEntity::class.java)
- .equalTo(SessionParamsEntityFields.USER_ID, userId)
+ .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId)
.findAll()
.map { mapper.map(it) }
.firstOrNull()
@@ -76,17 +77,17 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
}
}
- override suspend fun setTokenInvalid(userId: String) {
+ override suspend fun setTokenInvalid(sessionId: String) {
awaitTransaction(realmConfiguration) { realm ->
val currentSessionParams = realm
.where(SessionParamsEntity::class.java)
- .equalTo(SessionParamsEntityFields.USER_ID, userId)
+ .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId)
.findAll()
.firstOrNull()
if (currentSessionParams == null) {
// Should not happen
- "Session param not found for user $userId"
+ "Session param not found for id $sessionId"
.let { Timber.w(it) }
.also { error(it) }
} else {
@@ -99,14 +100,14 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
awaitTransaction(realmConfiguration) { realm ->
val currentSessionParams = realm
.where(SessionParamsEntity::class.java)
- .equalTo(SessionParamsEntityFields.USER_ID, newCredentials.userId)
+ .equalTo(SessionParamsEntityFields.SESSION_ID, newCredentials.sessionId())
.findAll()
.map { mapper.map(it) }
.firstOrNull()
if (currentSessionParams == null) {
// Should not happen
- "Session param not found for user ${newCredentials.userId}"
+ "Session param not found for id ${newCredentials.sessionId()}"
.let { Timber.w(it) }
.also { error(it) }
} else {
@@ -123,10 +124,10 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
}
}
- override suspend fun delete(userId: String) {
+ override suspend fun delete(sessionId: String) {
awaitTransaction(realmConfiguration) {
it.where(SessionParamsEntity::class.java)
- .equalTo(SessionParamsEntityFields.USER_ID, userId)
+ .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId)
.findAll()
.deleteAllFromRealm()
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt
index d4ba1eb818..ebd50a6924 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams
-import im.vector.matrix.android.internal.auth.createSessionId
+import im.vector.matrix.android.api.auth.data.sessionId
import javax.inject.Inject
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
@@ -50,7 +50,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
return null
}
return SessionParamsEntity(
- createSessionId(sessionParams.credentials.userId, sessionParams.credentials.deviceId),
+ sessionParams.credentials.sessionId(),
sessionParams.credentials.userId,
credentialsJson,
homeServerConnectionConfigJson,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt
index b847773682..4d98ddcf08 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt
@@ -72,7 +72,7 @@ internal class DefaultLoginWizard(
} else {
PasswordLoginParams.userIdentifier(login, password, deviceName)
}
- val credentials = executeRequest {
+ val credentials = executeRequest(null) {
apiCall = authAPI.login(loginParams)
}
@@ -95,7 +95,7 @@ internal class DefaultLoginWizard(
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
.also { pendingSessionStore.savePendingSessionData(it) }
- val result = executeRequest {
+ val result = executeRequest(null) {
apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
}
@@ -120,7 +120,7 @@ internal class DefaultLoginWizard(
resetPasswordData.newPassword
)
- executeRequest {
+ executeRequest(null) {
apiCall = authAPI.resetPasswordMailConfirmed(param)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt
index 0246075153..c455ccf48c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt
@@ -29,11 +29,12 @@ internal interface RegisterAddThreePidTask : Task {
)
}
-internal class DefaultRegisterTask(private val authAPI: AuthAPI)
- : RegisterTask {
+internal class DefaultRegisterTask(
+ private val authAPI: AuthAPI
+) : RegisterTask {
override suspend fun execute(params: RegisterTask.Params): Credentials {
try {
- return executeRequest {
+ return executeRequest(null) {
apiCall = authAPI.register(params.registrationParams)
}
} catch (throwable: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt
index da75b839a6..30f9aaa705 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt
@@ -27,11 +27,12 @@ internal interface ValidateCodeTask : Task onRoomEncryptionEvent(roomId, event)
+ event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
}
@@ -155,7 +155,7 @@ internal class DefaultCryptoService @Inject constructor(
fun onLiveEvent(roomId: String, event: Event) {
when {
- event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
+ event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
}
@@ -482,7 +482,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
override fun isRoomEncrypted(roomId: String): Boolean {
val encryptionEvent = monarchy.fetchCopied {
- EventEntity.where(it, roomId = roomId, type = EventType.ENCRYPTION).findFirst()
+ EventEntity.where(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst()
}
return encryptionEvent != null
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt
index 3eafa73fab..2d0c77c768 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt
@@ -22,9 +22,10 @@ import im.vector.matrix.android.internal.di.UserId
import timber.log.Timber
import javax.inject.Inject
-internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore,
- @UserId private val userId: String,
- private val keysBackup: KeysBackup) {
+internal class SetDeviceVerificationAction @Inject constructor(
+ private val cryptoStore: IMXCryptoStore,
+ @UserId private val userId: String,
+ private val keysBackup: KeysBackup) {
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
val device = cryptoStore.getUserDevice(deviceId, userId)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt
index 91b3d6b056..99267ee89c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt
@@ -1221,7 +1221,7 @@ internal class KeysBackup @Inject constructor(
// Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver
checkAndStartKeysBackup()
}
- else ->
+ else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
}
@@ -1339,6 +1339,31 @@ internal class KeysBackup @Inject constructor(
return sessionBackupData
}
+ /* ==========================================================================================
+ * For test only
+ * ========================================================================================== */
+
+ // Direct access for test only
+ @VisibleForTesting
+ val store
+ get() = cryptoStore
+
+ @VisibleForTesting
+ fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
+ callback: MatrixCallback) {
+ val createKeysBackupVersionBody = CreateKeysBackupVersionBody()
+ createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm
+ @Suppress("UNCHECKED_CAST")
+ createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java)
+ .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict?
+
+ createKeysBackupVersionTask
+ .configureWith(createKeysBackupVersionBody) {
+ this.callback = callback
+ }
+ .executeBy(taskExecutor)
+ }
+
companion object {
// Maximum delay in ms in {@link maybeBackupKeys}
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt
index 78ae0f7ea6..9b8183bd02 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt
@@ -21,15 +21,18 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeys
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface CreateKeysBackupVersionTask : Task
-internal class DefaultCreateKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
- : CreateKeysBackupVersionTask {
+internal class DefaultCreateKeysBackupVersionTask @Inject constructor(
+ private val roomKeysApi: RoomKeysApi,
+ private val eventBus: EventBus
+) : CreateKeysBackupVersionTask {
override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = roomKeysApi.createKeysBackupVersion(params)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt
index 2b1f3df353..9712bb099b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt
@@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface DeleteBackupTask : Task {
@@ -27,11 +28,13 @@ internal interface DeleteBackupTask : Task {
)
}
-internal class DefaultDeleteBackupTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
- : DeleteBackupTask {
+internal class DefaultDeleteBackupTask @Inject constructor(
+ private val roomKeysApi: RoomKeysApi,
+ private val eventBus: EventBus
+) : DeleteBackupTask {
override suspend fun execute(params: DeleteBackupTask.Params) {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = roomKeysApi.deleteBackup(params.version)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
index ccb3645ef3..72173ec7f4 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
@@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface DeleteRoomSessionDataTask : Task {
@@ -29,11 +30,13 @@ internal interface DeleteRoomSessionDataTask : Task {
@@ -28,11 +29,13 @@ internal interface DeleteRoomSessionsDataTask : Task {
@@ -27,11 +28,13 @@ internal interface DeleteSessionsDataTask : Task
-internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
- : GetKeysBackupLastVersionTask {
+internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(
+ private val roomKeysApi: RoomKeysApi,
+ private val eventBus: EventBus
+) : GetKeysBackupLastVersionTask {
override suspend fun execute(params: Unit): KeysVersionResult {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = roomKeysApi.getKeysBackupLastVersion()
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt
index ea0b9b9f3a..70cc7472a9 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt
@@ -20,15 +20,18 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetKeysBackupVersionTask : Task
-internal class DefaultGetKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
- : GetKeysBackupVersionTask {
+internal class DefaultGetKeysBackupVersionTask @Inject constructor(
+ private val roomKeysApi: RoomKeysApi,
+ private val eventBus: EventBus
+) : GetKeysBackupVersionTask {
override suspend fun execute(params: String): KeysVersionResult {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = roomKeysApi.getKeysBackupVersion(params)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
index a36850ba08..327836ed5f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
@@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetRoomSessionDataTask : Task {
@@ -30,11 +31,13 @@ internal interface GetRoomSessionDataTask : Task {
@@ -29,11 +30,13 @@ internal interface GetRoomSessionsDataTask : Task {
@@ -28,11 +29,13 @@ internal interface GetSessionsDataTask : Task {
@@ -32,11 +33,13 @@ internal interface StoreRoomSessionDataTask : Task {
@@ -31,11 +32,13 @@ internal interface StoreRoomSessionsDataTask : Task {
@@ -30,11 +31,13 @@ internal interface StoreSessionsDataTask : Task {
@@ -29,11 +30,13 @@ internal interface UpdateKeysBackupVersionTask : Task {
val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)
- val keysClaimResponse = executeRequest {
+ val keysClaimResponse = executeRequest(eventBus) {
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
}
val map = MXUsersDevicesMap()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt
index 0e52c118d9..fbbaa0e0f7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt
@@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface DeleteDeviceTask : Task {
@@ -31,12 +32,14 @@ internal interface DeleteDeviceTask : Task {
)
}
-internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi: CryptoApi)
- : DeleteDeviceTask {
+internal class DefaultDeleteDeviceTask @Inject constructor(
+ private val cryptoApi: CryptoApi,
+ private val eventBus: EventBus
+) : DeleteDeviceTask {
override suspend fun execute(params: DeleteDeviceTask.Params) {
try {
- executeRequest {
+ executeRequest(eventBus) {
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
}
} catch (throwable: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt
index eb23f02275..19e0f6efb5 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt
@@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface DeleteDeviceWithUserPasswordTask : Task {
@@ -33,12 +34,14 @@ internal interface DeleteDeviceWithUserPasswordTask : Task {
@@ -31,8 +32,10 @@ internal interface DownloadKeysForUsersTask : Task() }.orEmpty()
@@ -45,7 +48,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(private val crypt
body.token = params.token
}
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = cryptoApi.downloadKeysForUsers(body)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt
index f97e86a57d..9d9513b773 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt
@@ -20,17 +20,20 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetDeviceInfoTask : Task {
data class Params(val deviceId: String)
}
-internal class DefaultGetDeviceInfoTask @Inject constructor(private val cryptoApi: CryptoApi)
- : GetDeviceInfoTask {
+internal class DefaultGetDeviceInfoTask @Inject constructor(
+ private val cryptoApi: CryptoApi,
+ private val eventBus: EventBus
+) : GetDeviceInfoTask {
override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = cryptoApi.getDeviceInfo(params.deviceId)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt
index d6e82adb4e..7a805f6a08 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt
@@ -20,15 +20,18 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetDevicesTask : Task
-internal class DefaultGetDevicesTask @Inject constructor(private val cryptoApi: CryptoApi)
- : GetDevicesTask {
+internal class DefaultGetDevicesTask @Inject constructor(
+ private val cryptoApi: CryptoApi,
+ private val eventBus: EventBus
+) : GetDevicesTask {
override suspend fun execute(params: Unit): DevicesListResponse {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = cryptoApi.getDevices()
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt
index 42c36bd1e7..84e2c293b9 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt
@@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.KeyChangesResponse
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetKeyChangesTask : Task {
@@ -31,11 +32,13 @@ internal interface GetKeyChangesTask : Task {
)
}
-internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi: CryptoApi)
- : SendToDeviceTask {
+internal class DefaultSendToDeviceTask @Inject constructor(
+ private val cryptoApi: CryptoApi,
+ private val eventBus: EventBus
+) : SendToDeviceTask {
override suspend fun execute(params: SendToDeviceTask.Params) {
val sendToDeviceBody = SendToDeviceBody()
sendToDeviceBody.messages = params.contentMap.map
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = cryptoApi.sendToDevice(
params.eventType,
params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt
index 47f3050b88..74757c5cb3 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt
@@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface SetDeviceNameTask : Task {
@@ -31,14 +32,16 @@ internal interface SetDeviceNameTask : Task {
)
}
-internal class DefaultSetDeviceNameTask @Inject constructor(private val cryptoApi: CryptoApi)
- : SetDeviceNameTask {
+internal class DefaultSetDeviceNameTask @Inject constructor(
+ private val cryptoApi: CryptoApi,
+ private val eventBus: EventBus
+) : SetDeviceNameTask {
override suspend fun execute(params: SetDeviceNameTask.Params) {
val body = UpdateDeviceInfoBody(
displayName = params.deviceName
)
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt
index db05f473b1..d8bfe73eda 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt
@@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.convertToUTF8
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface UploadKeysTask : Task {
@@ -36,8 +37,10 @@ internal interface UploadKeysTask : Task = RealmList(),
// this is required for querying
var flatAliases: String = "",
- var isEncrypted: Boolean = false
+ var isEncrypted: Boolean = false,
+ var typingUserIds: RealmList = RealmList()
) : RealmObject() {
private var membershipStr: String = Membership.NONE.name
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt
index e0257bfc83..c802d4b63a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt
@@ -17,13 +17,13 @@
package im.vector.matrix.android.internal.network
import im.vector.matrix.android.internal.auth.SessionParamsStore
-import im.vector.matrix.android.internal.di.UserId
+import im.vector.matrix.android.internal.di.SessionId
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject
internal class AccessTokenInterceptor @Inject constructor(
- @UserId private val userId: String,
+ @SessionId private val sessionId: String,
private val sessionParamsStore: SessionParamsStore) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
@@ -40,5 +40,5 @@ internal class AccessTokenInterceptor @Inject constructor(
}
private val accessToken
- get() = sessionParamsStore.get(userId)?.credentials?.accessToken
+ get() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt
index 7f8e6643c3..074a97662b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt
@@ -18,12 +18,14 @@ package im.vector.matrix.android.internal.network
import im.vector.matrix.android.api.failure.Failure
import kotlinx.coroutines.CancellationException
+import org.greenrobot.eventbus.EventBus
import retrofit2.Call
import java.io.IOException
-internal suspend inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute()
+internal suspend inline fun executeRequest(eventBus: EventBus?,
+ block: Request.() -> Unit) = Request(eventBus).apply(block).execute()
-internal class Request {
+internal class Request(private val eventBus: EventBus?) {
lateinit var apiCall: Call
@@ -34,7 +36,7 @@ internal class Request {
response.body()
?: throw IllegalStateException("The request returned a null body")
} else {
- throw response.toFailure()
+ throw response.toFailure(eventBus)
}
} catch (exception: Throwable) {
throw when (exception) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt
index 2018868059..f33ec2f88a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt
@@ -74,18 +74,18 @@ internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response {
/**
* Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a MatrixError
*/
-internal fun Response.toFailure(): Failure {
- return toFailure(errorBody(), code())
+internal fun Response.toFailure(eventBus: EventBus?): Failure {
+ return toFailure(errorBody(), code(), eventBus)
}
/**
* Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError
*/
-internal fun okhttp3.Response.toFailure(): Failure {
- return toFailure(body, code)
+internal fun okhttp3.Response.toFailure(eventBus: EventBus?): Failure {
+ return toFailure(body, code, eventBus)
}
-private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure {
+private fun toFailure(errorBody: ResponseBody?, httpCode: Int, eventBus: EventBus?): Failure {
if (errorBody == null) {
return Failure.Unknown(RuntimeException("errorBody should not be null"))
}
@@ -100,11 +100,11 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure {
if (matrixError != null) {
if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) {
// Also send this error to the bus, for a global management
- EventBus.getDefault().post(GlobalError.ConsentNotGivenError(matrixError.consentUri))
+ eventBus?.post(GlobalError.ConsentNotGivenError(matrixError.consentUri))
} else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */
&& matrixError.code == MatrixError.M_UNKNOWN_TOKEN) {
// Also send this error to the bus, for a global management
- EventBus.getDefault().post(GlobalError.InvalidToken(matrixError.isSoftLogout))
+ eventBus?.post(GlobalError.InvalidToken(matrixError.isSoftLogout))
}
return Failure.ServerError(matrixError, httpCode)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt
index 483c7d7acf..e22e47bc1c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt
@@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.session
import android.content.Context
-import android.os.Looper
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import dagger.Lazy
@@ -46,6 +45,7 @@ import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.database.LiveEntityObserver
+import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.session.sync.SyncTaskSequencer
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread
@@ -61,69 +61,72 @@ import javax.inject.Inject
import javax.inject.Provider
@SessionScope
-internal class DefaultSession @Inject constructor(override val sessionParams: SessionParams,
- private val context: Context,
- private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>,
- private val sessionListeners: SessionListeners,
- private val roomService: Lazy,
- private val roomDirectoryService: Lazy,
- private val groupService: Lazy,
- private val userService: Lazy,
- private val filterService: Lazy,
- private val cacheService: Lazy,
- private val signOutService: Lazy,
- private val pushRuleService: Lazy,
- private val pushersService: Lazy,
- private val cryptoService: Lazy,
- private val fileService: Lazy,
- private val secureStorageService: Lazy,
- private val profileService: Lazy,
- private val syncThreadProvider: Provider,
- private val contentUrlResolver: ContentUrlResolver,
- private val syncTokenStore: SyncTokenStore,
- private val syncTaskSequencer: SyncTaskSequencer,
- private val sessionParamsStore: SessionParamsStore,
- private val contentUploadProgressTracker: ContentUploadStateTracker,
- private val initialSyncProgressService: Lazy,
- private val homeServerCapabilitiesService: Lazy)
+internal class DefaultSession @Inject constructor(
+ override val sessionParams: SessionParams,
+ private val context: Context,
+ private val eventBus: EventBus,
+ @SessionId
+ override val sessionId: String,
+ private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>,
+ private val sessionListeners: SessionListeners,
+ private val roomService: Lazy,
+ private val roomDirectoryService: Lazy,
+ private val groupService: Lazy,
+ private val userService: Lazy,
+ private val filterService: Lazy,
+ private val cacheService: Lazy,
+ private val signOutService: Lazy,
+ private val pushRuleService: Lazy,
+ private val pushersService: Lazy,
+ private val cryptoService: Lazy,
+ private val fileService: Lazy,
+ private val secureStorageService: Lazy,
+ private val profileService: Lazy,
+ private val syncThreadProvider: Provider,
+ private val contentUrlResolver: ContentUrlResolver,
+ private val syncTokenStore: SyncTokenStore,
+ private val syncTaskSequencer: SyncTaskSequencer,
+ private val sessionParamsStore: SessionParamsStore,
+ private val contentUploadProgressTracker: ContentUploadStateTracker,
+ private val initialSyncProgressService: Lazy,
+ private val homeServerCapabilitiesService: Lazy)
: Session,
- RoomService by roomService.get(),
- RoomDirectoryService by roomDirectoryService.get(),
- GroupService by groupService.get(),
- UserService by userService.get(),
- CryptoService by cryptoService.get(),
- SignOutService by signOutService.get(),
- FilterService by filterService.get(),
- PushRuleService by pushRuleService.get(),
- PushersService by pushersService.get(),
- FileService by fileService.get(),
- InitialSyncProgressService by initialSyncProgressService.get(),
- SecureStorageService by secureStorageService.get(),
- HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
- ProfileService by profileService.get() {
+ RoomService by roomService.get(),
+ RoomDirectoryService by roomDirectoryService.get(),
+ GroupService by groupService.get(),
+ UserService by userService.get(),
+ CryptoService by cryptoService.get(),
+ SignOutService by signOutService.get(),
+ FilterService by filterService.get(),
+ PushRuleService by pushRuleService.get(),
+ PushersService by pushersService.get(),
+ FileService by fileService.get(),
+ InitialSyncProgressService by initialSyncProgressService.get(),
+ SecureStorageService by secureStorageService.get(),
+ HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
+ ProfileService by profileService.get() {
private var isOpen = false
private var syncThread: SyncThread? = null
override val isOpenable: Boolean
- get() = sessionParamsStore.get(myUserId)?.isTokenValid ?: false
+ get() = sessionParamsStore.get(sessionId)?.isTokenValid ?: false
@MainThread
override fun open() {
- assertMainThread()
assert(!isOpen)
isOpen = true
liveEntityObservers.forEach { it.start() }
- EventBus.getDefault().register(this)
+ eventBus.register(this)
}
override fun requireBackgroundSync() {
- SyncWorker.requireBackgroundSync(context, myUserId)
+ SyncWorker.requireBackgroundSync(context, sessionId)
}
override fun startAutomaticBackgroundSync(repeatDelay: Long) {
- SyncWorker.automaticallyBackgroundSync(context, myUserId, 0, repeatDelay)
+ SyncWorker.automaticallyBackgroundSync(context, sessionId, 0, repeatDelay)
}
override fun stopAnyBackgroundSync() {
@@ -155,7 +158,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
liveEntityObservers.forEach { it.dispose() }
cryptoService.get().close()
isOpen = false
- EventBus.getDefault().unregister(this)
+ eventBus.unregister(this)
syncTaskSequencer.close()
}
@@ -183,10 +186,10 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
@Subscribe(threadMode = ThreadMode.MAIN)
fun onGlobalError(globalError: GlobalError) {
if (globalError is GlobalError.InvalidToken
- && globalError.softLogout) {
+ && globalError.softLogout) {
// Mark the token has invalid
GlobalScope.launch(Dispatchers.IO) {
- sessionParamsStore.setTokenInvalid(myUserId)
+ sessionParamsStore.setTokenInvalid(sessionId)
}
}
@@ -204,12 +207,4 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
override fun removeListener(listener: Session.Listener) {
sessionListeners.removeListener(listener)
}
-
- // Private methods *****************************************************************************
-
- private fun assertMainThread() {
- if (Looper.getMainLooper().thread !== Thread.currentThread()) {
- throw IllegalStateException("This method can only be called on the main thread!")
- }
- }
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
index 437a559ea1..2c72d93ebb 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
@@ -26,11 +26,11 @@ import dagger.multibindings.IntoSet
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams
+import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.api.session.InitialSyncProgressService
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
-import im.vector.matrix.android.internal.auth.createSessionId
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
import im.vector.matrix.android.internal.di.*
@@ -47,6 +47,7 @@ import im.vector.matrix.android.internal.session.securestorage.DefaultSecureStor
import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration
import okhttp3.OkHttpClient
+import org.greenrobot.eventbus.EventBus
import retrofit2.Retrofit
import java.io.File
@@ -87,7 +88,7 @@ internal abstract class SessionModule {
@SessionId
@Provides
fun providesSessionId(credentials: Credentials): String {
- return createSessionId(credentials.userId, credentials.deviceId)
+ return credentials.sessionId()
}
@JvmStatic
@@ -154,6 +155,13 @@ internal abstract class SessionModule {
return retrofitFactory
.create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString())
}
+
+ @JvmStatic
+ @Provides
+ @SessionScope
+ fun providesEventBus(): EventBus {
+ return EventBus.builder().build()
+ }
}
@Binds
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
index 2f4e991e62..4071c9224f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
@@ -29,12 +29,14 @@ import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import org.greenrobot.eventbus.EventBus
import java.io.File
import java.io.IOException
import javax.inject.Inject
internal class FileUploader @Inject constructor(@Authenticated
private val okHttpClient: OkHttpClient,
+ private val eventBus: EventBus,
sessionParams: SessionParams,
moshi: Moshi) {
@@ -73,7 +75,7 @@ internal class FileUploader @Inject constructor(@Authenticated
return okHttpClient.newCall(request).awaitResponse().use { response ->
if (!response.isSuccessful) {
- throw response.toFailure()
+ throw response.toFailure(eventBus)
} else {
response.body?.source()?.let {
responseAdapter.fromJson(it)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
index a05edb7b0f..1725ef99aa 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
@@ -42,7 +42,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
@JsonClass(generateAdapter = true)
internal data class Params(
- override val userId: String,
+ override val sessionId: String,
val roomId: String,
val event: Event,
val attachment: ContentAttachmentData,
@@ -64,7 +64,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
return Result.success(inputData)
}
- val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
+ val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
val eventId = params.event.eventId ?: return Result.success()
@@ -169,7 +169,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
Timber.v("handleSuccess $attachmentUrl, work is stopped $isStopped")
contentUploadStateTracker.setSuccess(params.event.eventId!!)
val event = updateEvent(params.event, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
- val sendParams = SendEventWorker.Params(params.userId, params.roomId, event)
+ val sendParams = SendEventWorker.Params(params.sessionId, params.roomId, event)
return Result.success(WorkerParamsFactory.toData(sendParams))
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt
index 08985bf17d..47c5e4a08a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt
@@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
/**
@@ -32,9 +33,11 @@ internal interface SaveFilterTask : Task {
)
}
-internal class DefaultSaveFilterTask @Inject constructor(@UserId private val userId: String,
- private val filterAPI: FilterApi,
- private val filterRepository: FilterRepository
+internal class DefaultSaveFilterTask @Inject constructor(
+ @UserId private val userId: String,
+ private val filterAPI: FilterApi,
+ private val filterRepository: FilterRepository,
+ private val eventBus: EventBus
) : SaveFilterTask {
override suspend fun execute(params: SaveFilterTask.Params) {
@@ -56,7 +59,7 @@ internal class DefaultSaveFilterTask @Inject constructor(@UserId private val use
}
val updated = filterRepository.storeFilter(filterBody, roomFilter)
if (updated) {
- val filterResponse = executeRequest {
+ val filterResponse = executeRequest(eventBus) {
// TODO auto retry
apiCall = filterAPI.uploadFilter(userId, filterBody)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt
index 40dc0a44c0..069c9b8d21 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt
@@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.session.group.model.GroupRooms
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
import im.vector.matrix.android.internal.session.group.model.GroupUsers
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetGroupDataTask : Task {
@@ -34,18 +35,19 @@ internal interface GetGroupDataTask : Task {
internal class DefaultGetGroupDataTask @Inject constructor(
private val groupAPI: GroupAPI,
- private val monarchy: Monarchy
+ private val monarchy: Monarchy,
+ private val eventBus: EventBus
) : GetGroupDataTask {
override suspend fun execute(params: GetGroupDataTask.Params) {
val groupId = params.groupId
- val groupSummary = executeRequest {
+ val groupSummary = executeRequest(eventBus) {
apiCall = groupAPI.getSummary(groupId)
}
- val groupRooms = executeRequest {
+ val groupRooms = executeRequest(eventBus) {
apiCall = groupAPI.getRooms(groupId)
}
- val groupUsers = executeRequest {
+ val groupUsers = executeRequest(eventBus) {
apiCall = groupAPI.getUsers(groupId)
}
insertInDb(groupSummary, groupRooms, groupUsers, groupId)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
index 76a7d5a48d..93705774e6 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
@@ -29,7 +29,7 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
@JsonClass(generateAdapter = true)
internal data class Params(
- override val userId: String,
+ override val sessionId: String,
val groupIds: List,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@@ -40,7 +40,7 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
val params = WorkerParamsFactory.fromData(inputData)
?: return Result.failure()
- val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
+ val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
val results = params.groupIds.map { groupId ->
runCatching { fetchGroupData(groupId) }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt
index 553a0387c5..a60bc78b6c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt
@@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
import im.vector.matrix.android.internal.database.query.where
-import im.vector.matrix.android.internal.di.UserId
+import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
@@ -37,9 +37,10 @@ import javax.inject.Inject
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
-internal class GroupSummaryUpdater @Inject constructor(private val context: Context,
- @UserId private val userId: String,
- private val monarchy: Monarchy)
+internal class GroupSummaryUpdater @Inject constructor(
+ private val context: Context,
+ @SessionId private val sessionId: String,
+ private val monarchy: Monarchy)
: RealmLiveEntityObserver(monarchy.realmConfiguration) {
override val query = Monarchy.Query { GroupEntity.where(it) }
@@ -51,9 +52,9 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
.mapNotNull { results[it] }
fetchGroupsData(modifiedGroupEntity
- .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
- .map { it.groupId }
- .toList())
+ .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
+ .map { it.groupId }
+ .toList())
modifiedGroupEntity
.filter { it.membership == Membership.LEAVE }
@@ -67,7 +68,7 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
}
private fun fetchGroupsData(groupIds: List) {
- val getGroupDataWorkerParams = GetGroupDataWorker.Params(userId, groupIds)
+ val getGroupDataWorkerParams = GetGroupDataWorker.Params(sessionId, groupIds)
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
index 45571286b9..3837d893f9 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt
@@ -23,14 +23,16 @@ import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
-import java.util.Date
+import org.greenrobot.eventbus.EventBus
+import java.util.*
import javax.inject.Inject
internal interface GetHomeServerCapabilitiesTask : Task
internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
private val capabilitiesAPI: CapabilitiesAPI,
- private val monarchy: Monarchy
+ private val monarchy: Monarchy,
+ private val eventBus: EventBus
) : GetHomeServerCapabilitiesTask {
override suspend fun execute(params: Unit) {
@@ -45,7 +47,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
return
}
- val uploadCapabilities = executeRequest {
+ val uploadCapabilities = executeRequest(eventBus) {
apiCall = capabilitiesAPI.getUploadCapabilities()
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/GetProfileInfoTask.kt
index 69db2d7b99..2309628a8a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/GetProfileInfoTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/GetProfileInfoTask.kt
@@ -20,6 +20,7 @@ package im.vector.matrix.android.internal.session.profile
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal abstract class GetProfileInfoTask : Task {
@@ -28,10 +29,11 @@ internal abstract class GetProfileInfoTask : Task(inputData)
?: return Result.failure()
- val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
+ val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
val pusher = params.pusher
@@ -76,7 +80,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
}
private suspend fun setPusher(pusher: JsonPusher) {
- executeRequest {
+ executeRequest(eventBus) {
apiCall = pushersAPI.setPusher(pusher)
}
monarchy.awaitTransaction { realm ->
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt
index 99992ef4dc..b310ba7cd1 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt
@@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface AddPushRuleTask : Task {
@@ -28,11 +29,13 @@ internal interface AddPushRuleTask : Task {
)
}
-internal class DefaultAddPushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi)
- : AddPushRuleTask {
+internal class DefaultAddPushRuleTask @Inject constructor(
+ private val pushRulesApi: PushRulesApi,
+ private val eventBus: EventBus
+) : AddPushRuleTask {
override suspend fun execute(params: AddPushRuleTask.Params) {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
index fcce69c2fc..cdbf6aeee4 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
@@ -26,22 +26,23 @@ import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.query.where
-import im.vector.matrix.android.internal.di.UserId
+import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
-import java.util.UUID
+import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
-internal class DefaultPusherService @Inject constructor(private val context: Context,
- private val monarchy: Monarchy,
- @UserId private val userId: String,
- private val getPusherTask: GetPushersTask,
- private val removePusherTask: RemovePusherTask,
- private val taskExecutor: TaskExecutor
+internal class DefaultPusherService @Inject constructor(
+ private val context: Context,
+ private val monarchy: Monarchy,
+ @SessionId private val sessionId: String,
+ private val getPusherTask: GetPushersTask,
+ private val removePusherTask: RemovePusherTask,
+ private val taskExecutor: TaskExecutor
) : PushersService {
override fun refreshPushers() {
@@ -65,7 +66,7 @@ internal class DefaultPusherService @Inject constructor(private val context: Con
data = JsonPusherData(url, if (withEventIdOnly) PushersService.EVENT_ID_ONLY else null),
append = append)
- val params = AddHttpPusherWorker.Params(pusher, userId)
+ val params = AddHttpPusherWorker.Params(sessionId, pusher)
val request = matrixOneTimeWorkRequestBuilder()
.setConstraints(WorkManagerUtil.workConstraints)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt
index d135c36543..4f3b39d1a7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt
@@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.pushers
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetPushRulesTask : Task {
@@ -27,11 +28,14 @@ internal interface GetPushRulesTask : Task {
/**
* We keep this task, but it should not be used anymore, the push rules comes from the sync response
*/
-internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi,
- private val savePushRulesTask: SavePushRulesTask) : GetPushRulesTask {
+internal class DefaultGetPushRulesTask @Inject constructor(
+ private val pushRulesApi: PushRulesApi,
+ private val savePushRulesTask: SavePushRulesTask,
+ private val eventBus: EventBus
+) : GetPushRulesTask {
override suspend fun execute(params: GetPushRulesTask.Params) {
- val response = executeRequest {
+ val response = executeRequest(eventBus) {
apiCall = pushRulesApi.getAllRules()
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt
index 045db56786..64e982914a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt
@@ -22,15 +22,19 @@ import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetPushersTask : Task
-internal class DefaultGetPushersTask @Inject constructor(private val pushersAPI: PushersAPI,
- private val monarchy: Monarchy) : GetPushersTask {
+internal class DefaultGetPushersTask @Inject constructor(
+ private val pushersAPI: PushersAPI,
+ private val monarchy: Monarchy,
+ private val eventBus: EventBus
+) : GetPushersTask {
override suspend fun execute(params: Unit) {
- val response = executeRequest {
+ val response = executeRequest(eventBus) {
apiCall = pushersAPI.getPushers()
}
monarchy.awaitTransaction { realm ->
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt
index c4938fa0cc..d0edda9cfb 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt
@@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface RemovePushRuleTask : Task {
@@ -28,11 +29,13 @@ internal interface RemovePushRuleTask : Task {
)
}
-internal class DefaultRemovePushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi)
- : RemovePushRuleTask {
+internal class DefaultRemovePushRuleTask @Inject constructor(
+ private val pushRulesApi: PushRulesApi,
+ private val eventBus: EventBus
+) : RemovePushRuleTask {
override suspend fun execute(params: RemovePushRuleTask.Params) {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt
index 297375454a..48e3f40a21 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt
@@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface RemovePusherTask : Task {
@@ -34,7 +35,8 @@ internal interface RemovePusherTask : Task {
internal class DefaultRemovePusherTask @Inject constructor(
private val pushersAPI: PushersAPI,
- private val monarchy: Monarchy
+ private val monarchy: Monarchy,
+ private val eventBus: EventBus
) : RemovePusherTask {
override suspend fun execute(params: RemovePusherTask.Params) {
@@ -59,7 +61,7 @@ internal class DefaultRemovePusherTask @Inject constructor(
data = JsonPusherData(existing.data.url, existing.data.format),
append = false
)
- executeRequest {
+ executeRequest(eventBus) {
apiCall = pushersAPI.setPusher(deleteBody)
}
monarchy.awaitTransaction {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
index 91ed65d833..c5d593c263 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
@@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface UpdatePushRuleEnableStatusTask : Task {
@@ -27,11 +28,13 @@ internal interface UpdatePushRuleEnableStatusTask : Task) {
+ if (isEncrypted()) {
+ callback.onFailure(IllegalStateException("Encryption is already enabled for this room"))
+ } else {
+ stateService.enableEncryption(algorithm, callback)
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt
index 4a14005fe9..c6bcdf396e 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt
@@ -36,9 +36,10 @@ import javax.inject.Inject
* The summaries can then be extracted and added (as a decoration) to a TimelineEvent for final display.
*/
-internal class EventRelationsAggregationUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
- @UserId private val userId: String,
- private val task: EventRelationsAggregationTask) :
+internal class EventRelationsAggregationUpdater @Inject constructor(
+ @SessionDatabase realmConfiguration: RealmConfiguration,
+ @UserId private val userId: String,
+ private val task: EventRelationsAggregationTask) :
RealmLiveEntityObserver(realmConfiguration) {
override val query = Monarchy.Query {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt
index 6896788de9..622ffbe2f0 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt
@@ -18,13 +18,13 @@ package im.vector.matrix.android.internal.session.room
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event
-import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.matrix.android.internal.network.NetworkConstants
+import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody
import im.vector.matrix.android.internal.session.room.relation.RelationsResponse
@@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.session.room.reporting.ReportContentBod
import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
+import im.vector.matrix.android.internal.session.room.typing.TypingBody
import retrofit2.Call
import retrofit2.http.*
@@ -268,4 +269,12 @@ internal interface RoomAPI {
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call
+
+ /**
+ * Inform that the user is starting to type or has stopped typing
+ */
+ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}")
+ fun sendTypingState(@Path("roomId") roomId: String,
+ @Path("userId") userId: String,
+ @Body body: TypingBody): Call
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt
index a21a3b4a8d..b24bb73d56 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt
@@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReporting
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
+import im.vector.matrix.android.internal.session.room.typing.DefaultTypingService
import javax.inject.Inject
internal interface RoomFactory {
@@ -46,6 +47,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
private val stateServiceFactory: DefaultStateService.Factory,
private val reportingServiceFactory: DefaultReportingService.Factory,
private val readServiceFactory: DefaultReadService.Factory,
+ private val typingServiceFactory: DefaultTypingService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory) :
@@ -62,6 +64,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
stateServiceFactory.create(roomId),
reportingServiceFactory.create(roomId),
readServiceFactory.create(roomId),
+ typingServiceFactory.create(roomId),
cryptoService,
relationServiceFactory.create(roomId),
membershipServiceFactory.create(roomId),
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
index cc786a7493..5551930bd1 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
@@ -52,6 +52,8 @@ import im.vector.matrix.android.internal.session.room.reporting.ReportContentTas
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
import im.vector.matrix.android.internal.session.room.state.SendStateTask
import im.vector.matrix.android.internal.session.room.timeline.*
+import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask
+import im.vector.matrix.android.internal.session.room.typing.SendTypingTask
import retrofit2.Retrofit
@Module
@@ -68,74 +70,77 @@ internal abstract class RoomModule {
}
@Binds
- abstract fun bindRoomFactory(roomFactory: DefaultRoomFactory): RoomFactory
+ abstract fun bindRoomFactory(factory: DefaultRoomFactory): RoomFactory
@Binds
- abstract fun bindRoomService(roomService: DefaultRoomService): RoomService
+ abstract fun bindRoomService(service: DefaultRoomService): RoomService
@Binds
- abstract fun bindRoomDirectoryService(roomDirectoryService: DefaultRoomDirectoryService): RoomDirectoryService
+ abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService
@Binds
- abstract fun bindEventRelationsAggregationTask(eventRelationsAggregationTask: DefaultEventRelationsAggregationTask): EventRelationsAggregationTask
+ abstract fun bindFileService(service: DefaultFileService): FileService
@Binds
- abstract fun bindCreateRoomTask(createRoomTask: DefaultCreateRoomTask): CreateRoomTask
+ abstract fun bindEventRelationsAggregationTask(task: DefaultEventRelationsAggregationTask): EventRelationsAggregationTask
@Binds
- abstract fun bindGetPublicRoomTask(getPublicRoomTask: DefaultGetPublicRoomTask): GetPublicRoomTask
+ abstract fun bindCreateRoomTask(task: DefaultCreateRoomTask): CreateRoomTask
@Binds
- abstract fun bindGetThirdPartyProtocolsTask(getThirdPartyProtocolsTask: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
+ abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask
@Binds
- abstract fun bindInviteTask(inviteTask: DefaultInviteTask): InviteTask
+ abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
@Binds
- abstract fun bindJoinRoomTask(joinRoomTask: DefaultJoinRoomTask): JoinRoomTask
+ abstract fun bindInviteTask(task: DefaultInviteTask): InviteTask
@Binds
- abstract fun bindLeaveRoomTask(leaveRoomTask: DefaultLeaveRoomTask): LeaveRoomTask
+ abstract fun bindJoinRoomTask(task: DefaultJoinRoomTask): JoinRoomTask
@Binds
- abstract fun bindLoadRoomMembersTask(loadRoomMembersTask: DefaultLoadRoomMembersTask): LoadRoomMembersTask
+ abstract fun bindLeaveRoomTask(task: DefaultLeaveRoomTask): LeaveRoomTask
@Binds
- abstract fun bindPruneEventTask(pruneEventTask: DefaultPruneEventTask): PruneEventTask
+ abstract fun bindLoadRoomMembersTask(task: DefaultLoadRoomMembersTask): LoadRoomMembersTask
@Binds
- abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask
+ abstract fun bindPruneEventTask(task: DefaultPruneEventTask): PruneEventTask
@Binds
- abstract fun bindMarkAllRoomsReadTask(markAllRoomsReadTask: DefaultMarkAllRoomsReadTask): MarkAllRoomsReadTask
+ abstract fun bindSetReadMarkersTask(task: DefaultSetReadMarkersTask): SetReadMarkersTask
@Binds
- abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
+ abstract fun bindMarkAllRoomsReadTask(task: DefaultMarkAllRoomsReadTask): MarkAllRoomsReadTask
@Binds
- abstract fun bindUpdateQuickReactionTask(updateQuickReactionTask: DefaultUpdateQuickReactionTask): UpdateQuickReactionTask
+ abstract fun bindFindReactionEventForUndoTask(task: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
@Binds
- abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask
+ abstract fun bindUpdateQuickReactionTask(task: DefaultUpdateQuickReactionTask): UpdateQuickReactionTask
@Binds
- abstract fun bindReportContentTask(reportContentTask: DefaultReportContentTask): ReportContentTask
+ abstract fun bindSendStateTask(task: DefaultSendStateTask): SendStateTask
@Binds
- abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
+ abstract fun bindReportContentTask(task: DefaultReportContentTask): ReportContentTask
@Binds
- abstract fun bindClearUnlinkedEventsTask(clearUnlinkedEventsTask: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask
+ abstract fun bindGetContextOfEventTask(task: DefaultGetContextOfEventTask): GetContextOfEventTask
@Binds
- abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask
+ abstract fun bindClearUnlinkedEventsTask(task: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask
@Binds
- abstract fun bindFileService(fileService: DefaultFileService): FileService
+ abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask
@Binds
- abstract fun bindFetchEditHistoryTask(fetchEditHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask
+ abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask
@Binds
- abstract fun bindGetRoomIdByAliasTask(getRoomIdByAliasTask: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
+ abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
+
+ @Binds
+ abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
index 009ca682ed..bbb5feba15 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
@@ -29,22 +29,20 @@ import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.*
-import im.vector.matrix.android.internal.database.query.isEventRead
-import im.vector.matrix.android.internal.database.query.latestEvent
-import im.vector.matrix.android.internal.database.query.prev
-import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
+import im.vector.matrix.android.internal.session.sync.RoomSyncHandler
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
import io.realm.Realm
import javax.inject.Inject
-internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId: String,
- private val roomDisplayNameResolver: RoomDisplayNameResolver,
- private val roomAvatarResolver: RoomAvatarResolver,
- private val monarchy: Monarchy) {
+internal class RoomSummaryUpdater @Inject constructor(
+ @UserId private val userId: String,
+ private val roomDisplayNameResolver: RoomDisplayNameResolver,
+ private val roomAvatarResolver: RoomAvatarResolver,
+ private val monarchy: Monarchy) {
// TODO: maybe allow user of SDK to give that list
private val PREVIEWABLE_TYPES = listOf(
@@ -57,7 +55,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
EventType.CALL_HANGUP,
EventType.CALL_ANSWER,
EventType.ENCRYPTED,
- EventType.ENCRYPTION,
+ EventType.STATE_ROOM_ENCRYPTION,
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STICKER,
EventType.STATE_ROOM_CREATE
@@ -68,7 +66,8 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
membership: Membership? = null,
roomSummary: RoomSyncSummary? = null,
unreadNotifications: RoomSyncUnreadNotifications? = null,
- updateMembers: Boolean = false) {
+ updateMembers: Boolean = false,
+ ephemeralResult: RoomSyncHandler.EphemeralResult? = null) {
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
if (roomSummary != null) {
if (roomSummary.heroes.isNotEmpty()) {
@@ -93,11 +92,11 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev()
val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
- val encryptionEvent = EventEntity.where(realm, roomId, EventType.ENCRYPTION).prev()
+ val encryptionEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ENCRYPTION).prev()
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
- // avoid this call if we are sure there are unread events
- || !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
+ // avoid this call if we are sure there are unread events
+ || !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
@@ -107,11 +106,13 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
?.canonicalAlias
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases
- ?: emptyList()
+ ?: emptyList()
roomSummaryEntity.aliases.clear()
roomSummaryEntity.aliases.addAll(roomAliases)
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
roomSummaryEntity.isEncrypted = encryptionEvent != null
+ roomSummaryEntity.typingUserIds.clear()
+ roomSummaryEntity.typingUserIds.addAll(ephemeralResult?.typingUserIds.orEmpty())
if (updateMembers) {
val otherRoomMembers = RoomMemberHelper(realm, roomId)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt
index 1a726c3fe5..6aee8c170b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt
@@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task
import io.realm.Realm
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetRoomIdByAliasTask : Task> {
@@ -33,8 +34,11 @@ internal interface GetRoomIdByAliasTask : Task {
var roomId = Realm.getInstance(monarchy.realmConfiguration).use {
@@ -45,7 +49,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(private val monar
} else if (!params.searchOnServer) {
Optional.from(null)
} else {
- roomId = executeRequest {
+ roomId = executeRequest(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
}.roomId
Optional.from(roomId)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt
index 970a1fed7e..6567b7ad97 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt
@@ -35,21 +35,25 @@ import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.RealmConfiguration
import kotlinx.coroutines.TimeoutCancellationException
+import org.greenrobot.eventbus.EventBus
import java.util.concurrent.TimeUnit
import javax.inject.Inject
internal interface CreateRoomTask : Task
-internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: RoomAPI,
- private val monarchy: Monarchy,
- private val directChatsHelper: DirectChatsHelper,
- private val updateUserAccountDataTask: UpdateUserAccountDataTask,
- private val readMarkersTask: SetReadMarkersTask,
- @SessionDatabase
- private val realmConfiguration: RealmConfiguration) : CreateRoomTask {
+internal class DefaultCreateRoomTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val monarchy: Monarchy,
+ private val directChatsHelper: DirectChatsHelper,
+ private val updateUserAccountDataTask: UpdateUserAccountDataTask,
+ private val readMarkersTask: SetReadMarkersTask,
+ @SessionDatabase
+ private val realmConfiguration: RealmConfiguration,
+ private val eventBus: EventBus
+) : CreateRoomTask {
override suspend fun execute(params: CreateRoomParams): String {
- val createRoomResponse = executeRequest {
+ val createRoomResponse = executeRequest(eventBus) {
apiCall = roomAPI.createRoom(params)
}
val roomId = createRoomResponse.roomId!!
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt
index a24765e0bb..2b7d2e7dd9 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt
@@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface GetPublicRoomTask : Task {
@@ -30,10 +31,13 @@ internal interface GetPublicRoomTask : Task>
-internal class DefaultGetThirdPartyProtocolsTask @Inject constructor(private val roomAPI: RoomAPI) : GetThirdPartyProtocolsTask {
+internal class DefaultGetThirdPartyProtocolsTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val eventBus: EventBus
+) : GetThirdPartyProtocolsTask {
override suspend fun execute(params: Unit): Map {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = roomAPI.thirdPartyProtocols()
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
index 01646e4c17..0195497798 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
@@ -29,6 +29,7 @@ import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
+import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.query.process
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
@@ -39,13 +40,16 @@ import im.vector.matrix.android.internal.util.fetchCopied
import io.realm.Realm
import io.realm.RealmQuery
-internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String,
- private val monarchy: Monarchy,
- private val taskExecutor: TaskExecutor,
- private val loadRoomMembersTask: LoadRoomMembersTask,
- private val inviteTask: InviteTask,
- private val joinTask: JoinRoomTask,
- private val leaveRoomTask: LeaveRoomTask
+internal class DefaultMembershipService @AssistedInject constructor(
+ @Assisted private val roomId: String,
+ private val monarchy: Monarchy,
+ private val taskExecutor: TaskExecutor,
+ private val loadRoomMembersTask: LoadRoomMembersTask,
+ private val inviteTask: InviteTask,
+ private val joinTask: JoinRoomTask,
+ private val leaveRoomTask: LeaveRoomTask,
+ @UserId
+ private val userId: String
) : MembershipService {
@AssistedInject.Factory
@@ -91,11 +95,17 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
)
}
+
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery {
return RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
.process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId)
.process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
.process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
+ .apply {
+ if (queryParams.excludeSelf) {
+ notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
+ }
+ }
}
override fun getNumberOfJoinedMembers(): Int {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt
index dd91875f98..3610511dbf 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt
@@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
import io.realm.kotlin.createObject
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface LoadRoomMembersTask : Task {
@@ -40,12 +41,14 @@ internal interface LoadRoomMembersTask : Task
)
}
-internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAPI: RoomAPI,
- private val monarchy: Monarchy,
- private val syncTokenStore: SyncTokenStore,
- private val roomSummaryUpdater: RoomSummaryUpdater,
- private val roomMemberEventHandler: RoomMemberEventHandler,
- private val timelineEventSenderVisitor: TimelineEventSenderVisitor
+internal class DefaultLoadRoomMembersTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val monarchy: Monarchy,
+ private val syncTokenStore: SyncTokenStore,
+ private val roomSummaryUpdater: RoomSummaryUpdater,
+ private val roomMemberEventHandler: RoomMemberEventHandler,
+ private val timelineEventSenderVisitor: TimelineEventSenderVisitor,
+ private val eventBus: EventBus
) : LoadRoomMembersTask {
override suspend fun execute(params: LoadRoomMembersTask.Params) {
@@ -53,7 +56,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
return
}
val lastToken = syncTokenStore.getLastToken()
- val response = executeRequest {
+ val response = executeRequest(eventBus) {
apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
}
insertInDb(response, params.roomId)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt
index 6bc453a0f3..93b3889455 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt
@@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.membership.joining
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface InviteTask : Task {
@@ -29,10 +30,13 @@ internal interface InviteTask : Task {
)
}
-internal class DefaultInviteTask @Inject constructor(private val roomAPI: RoomAPI) : InviteTask {
+internal class DefaultInviteTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val eventBus: EventBus
+) : InviteTask {
override suspend fun execute(params: InviteTask.Params) {
- return executeRequest {
+ return executeRequest(eventBus) {
val body = InviteBody(params.userId, params.reason)
apiCall = roomAPI.invite(params.roomId, body)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt
index fbede72520..d4341951eb 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt
@@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.task.Task
import io.realm.RealmConfiguration
import kotlinx.coroutines.TimeoutCancellationException
+import org.greenrobot.eventbus.EventBus
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -38,13 +39,16 @@ internal interface JoinRoomTask : Task {
)
}
-internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI,
- private val readMarkersTask: SetReadMarkersTask,
- @SessionDatabase
- private val realmConfiguration: RealmConfiguration) : JoinRoomTask {
+internal class DefaultJoinRoomTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val readMarkersTask: SetReadMarkersTask,
+ @SessionDatabase
+ private val realmConfiguration: RealmConfiguration,
+ private val eventBus: EventBus
+) : JoinRoomTask {
override suspend fun execute(params: JoinRoomTask.Params) {
- executeRequest {
+ executeRequest(eventBus) {
apiCall = roomAPI.join(params.roomId, params.viaServers, mapOf("reason" to params.reason))
}
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt
index 01198c47de..08eb71fc89 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt
@@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.membership.leaving
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface LeaveRoomTask : Task {
@@ -28,10 +29,13 @@ internal interface LeaveRoomTask : Task {
)
}
-internal class DefaultLeaveRoomTask @Inject constructor(private val roomAPI: RoomAPI) : LeaveRoomTask {
+internal class DefaultLeaveRoomTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val eventBus: EventBus
+) : LeaveRoomTask {
override suspend fun execute(params: LeaveRoomTask.Params) {
- return executeRequest {
+ return executeRequest(eventBus) {
apiCall = roomAPI.leave(params.roomId, mapOf("reason" to params.reason))
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt
index b8b9fe82ef..a9a0f60083 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt
@@ -36,12 +36,13 @@ import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
-internal class DefaultReadService @AssistedInject constructor(@Assisted private val roomId: String,
- private val monarchy: Monarchy,
- private val taskExecutor: TaskExecutor,
- private val setReadMarkersTask: SetReadMarkersTask,
- private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
- @UserId private val userId: String
+internal class DefaultReadService @AssistedInject constructor(
+ @Assisted private val roomId: String,
+ private val monarchy: Monarchy,
+ private val taskExecutor: TaskExecutor,
+ private val setReadMarkersTask: SetReadMarkersTask,
+ private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
+ @UserId private val userId: String
) : ReadService {
@AssistedInject.Factory
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt
index b9dca748cb..6a0d04193d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt
@@ -20,7 +20,8 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
-import im.vector.matrix.android.internal.database.query.*
+import im.vector.matrix.android.internal.database.query.isEventRead
+import im.vector.matrix.android.internal.database.query.isReadMarkerMoreRecent
import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
@@ -31,8 +32,11 @@ import im.vector.matrix.android.internal.session.sync.RoomFullyReadHandler
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
+import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
+import kotlin.collections.HashMap
+import kotlin.collections.set
internal interface SetReadMarkersTask : Task {
@@ -47,12 +51,14 @@ internal interface SetReadMarkersTask : Task {
private const val READ_MARKER = "m.fully_read"
private const val READ_RECEIPT = "m.read"
-internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI: RoomAPI,
- private val monarchy: Monarchy,
- private val roomFullyReadHandler: RoomFullyReadHandler,
- private val readReceiptHandler: ReadReceiptHandler,
- @UserId private val userId: String)
- : SetReadMarkersTask {
+internal class DefaultSetReadMarkersTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val monarchy: Monarchy,
+ private val roomFullyReadHandler: RoomFullyReadHandler,
+ private val readReceiptHandler: ReadReceiptHandler,
+ @UserId private val userId: String,
+ private val eventBus: EventBus
+) : SetReadMarkersTask {
override suspend fun execute(params: SetReadMarkersTask.Params) {
val markers = HashMap