Merge branch 'release/0.12.0'

This commit is contained in:
Benoit Marty 2020-01-09 15:31:30 +01:00
commit 24a7ce7d98
375 changed files with 11184 additions and 3135 deletions

View file

@ -20,6 +20,7 @@
<w>signin</w>
<w>signout</w>
<w>signup</w>
<w>threepid</w>
</words>
</dictionary>
</component>

View file

@ -1,3 +1,29 @@
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
- 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
- Fix avatar image disappearing (#777)
- Fix read marker banner when permalink
- Fix joining upgraded rooms (#697)
- Fix matrix.org room directory not being browsable (#807)
- Hide non working settings (#751)
Changes in RiotX 0.11.0 (2019-12-19)
===================================================
@ -256,7 +282,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 ✨:

View file

@ -17,13 +17,16 @@
package im.vector.matrix.rx
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import io.reactivex.Observable
import io.reactivex.Single
@ -31,18 +34,22 @@ class RxRoom(private val room: Room) {
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
return room.getRoomSummaryLive().asObservable()
.startWith(room.roomSummary().toOptional())
}
fun liveRoomMemberIds(): Observable<List<String>> {
return room.getRoomMemberIdsLive().asObservable()
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMember>> {
return room.getRoomMembersLive(queryParams).asObservable()
.startWith(room.getRoomMembers(queryParams))
}
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
return room.getEventSummaryLive(eventId).asObservable()
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
.startWith(room.getEventAnnotationsSummary(eventId).toOptional())
}
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
return room.getTimeLineEventLive(eventId).asObservable()
.startWith(room.getTimeLineEvent(eventId).toOptional())
}
fun liveReadMarker(): Observable<Optional<String>> {

View file

@ -18,8 +18,10 @@ package im.vector.matrix.rx
import androidx.paging.PagedList
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.sync.SyncState
@ -30,40 +32,43 @@ import io.reactivex.Single
class RxSession(private val session: Session) {
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
return session.liveRoomSummaries().asObservable()
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
return session.getRoomSummariesLive(queryParams).asObservable()
.startWith(session.getRoomSummaries(queryParams))
}
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
return session.liveGroupSummaries().asObservable()
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
return session.getGroupSummariesLive(queryParams).asObservable()
.startWith(session.getGroupSummaries(queryParams))
}
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
return session.liveBreadcrumbs().asObservable()
return session.getBreadcrumbsLive().asObservable()
.startWith(session.getBreadcrumbs())
}
fun liveSyncState(): Observable<SyncState> {
return session.syncState().asObservable()
return session.getSyncStateLive().asObservable()
}
fun livePushers(): Observable<List<Pusher>> {
return session.livePushers().asObservable()
return session.getPushersLive().asObservable()
}
fun liveUser(userId: String): Observable<Optional<User>> {
return session.liveUser(userId).asObservable().distinctUntilChanged()
return session.getUserLive(userId).asObservable().distinctUntilChanged()
}
fun liveUsers(): Observable<List<User>> {
return session.liveUsers().asObservable()
return session.getUsersLive().asObservable()
}
fun liveIgnoredUsers(): Observable<List<User>> {
return session.liveIgnoredUsers().asObservable()
return session.getIgnoredUsersLive().asObservable()
}
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
return session.livePagedUsers(filter).asObservable()
return session.getPagedUsersLive(filter).asObservable()
}
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {

View file

@ -10,7 +10,7 @@ buildscript {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:5.12.0"
classpath "io.realm:realm-gradle-plugin:6.0.2"
}
}
@ -102,7 +102,6 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.recyclerview:recyclerview:1.1.0-beta05"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
@ -119,14 +118,14 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version"
// Image
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation 'androidx.exifinterface:exifinterface:1.1.0'
// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
// Work
implementation "androidx.work:work-runtime-ktx:2.3.0-alpha01"
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
// FP
implementation "io.arrow-kt:arrow-core:$arrow_version"

View file

@ -19,4 +19,4 @@ package im.vector.matrix.android
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers.Main
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main)
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main)

View file

@ -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()
}
}

View file

@ -1,60 +0,0 @@
/*
* 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.auth
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.OkReplayRuleChainNoActivity
import im.vector.matrix.android.api.auth.AuthenticationService
import okreplay.*
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
internal class AuthenticationServiceTest : InstrumentedTest {
lateinit var authenticationService: AuthenticationService
lateinit var okReplayInterceptor: OkReplayInterceptor
private val okReplayConfig = OkReplayConfig.Builder()
.tapeRoot(AndroidTapeRoot(
context(), javaClass))
.defaultMode(TapeMode.READ_WRITE) // or TapeMode.READ_ONLY
.sslEnabled(true)
.interceptor(okReplayInterceptor)
.build()
@get:Rule
val testRule = OkReplayRuleChainNoActivity(okReplayConfig).get()
@Test
@UiThreadTest
@OkReplay(tape = "auth", mode = TapeMode.READ_WRITE)
fun auth() {
}
companion object {
@ClassRule
@JvmField
val grantExternalStoragePermissionRule: GrantPermissionRule =
GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}

View file

@ -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> { 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<TimelineEvent> {
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
val latch = CountDownLatch(nbOfMessages)
val onEventSentListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
// 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<LoginFlowResult> {
matrix.authenticationService
.getLoginFlow(hs, it)
}
doSync<RegistrationResult> {
matrix.authenticationService
.getRegistrationWizard()
.createAccount(userName, password, null, it)
}
// Preform dummy step
val registrationResult = doSync<RegistrationResult> {
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<LoginFlowResult> {
matrix.authenticationService
.getLoginFlow(hs, it)
}
val session = doSync<Session> {
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 <reified T> doSync(block: (MatrixCallback<T>) -> Unit): T {
val lock = CountDownLatch(1)
var result: T? = null
val callback = object : TestMatrixCallback<T>(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<Session>.close() = forEach { it.close() }
fun signout(session: Session) {
val lock = CountDownLatch(1)
session.signOut(true, object : TestMatrixCallback<Unit>(lock) {})
await(lock)
}
}

View file

@ -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()
}
}

View file

@ -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<String> = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!")
val messagesFromBob: List<String> = 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<String>(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<Unit>(lock2) {})
mTestHelper.await(lock2)
return CryptoTestData(aliceSession, roomId!!)
}
/**
* @return alice and bob sessions
*/
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
val statuses = HashMap<String, String>()
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<Unit>(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<String, String>()
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<Unit>(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<Unit>(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<TimelineEvent>) {
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<String, Any>()
// 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<String, Map<String, String>>().apply {
this["something"] = HashMap<String, String>().apply {
this["ed25519:something"] = "hijklmnop"
}
}
)
}
fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo {
return MegolmBackupCreationInfo().apply {
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
authData = createFakeMegolmBackupAuthData()
}
}
}

View file

@ -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:
* <code>
* val mockInterceptor = MockOkHttpInterceptor()
* mockInterceptor.addRule(MockOkHttpInterceptor.SimpleRule(".well-known/matrix/client", 200, "{}"))
*
* RestHttpClientFactoryProvider.defaultProvider = RestClientHttpClientFactory(mockInterceptor)
* AutoDiscovery().findClientConfig("matrix.org", <callback>)
* </code>
*/
class MockOkHttpInterceptor : Interceptor {
private var rules: ArrayList<Rule> = 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()
}
}
}

View file

@ -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)

View file

@ -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<Any>?, list2: List<Any>?) {
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<String, Any>?, dict2: Map<String, Any>?) {
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.")
}

View file

@ -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()
}
}

View file

@ -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 <T>
*/
open class TestMatrixCallback<T>(private val countDownLatch: CountDownLatch,
private val onlySuccessful: Boolean = true) : MatrixCallback<T> {
@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()
}
}

View file

@ -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))
}
}

View file

@ -16,20 +16,31 @@
package im.vector.matrix.android.internal.crypto
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import io.realm.Realm
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmManager
import org.matrix.olm.OlmSession
private const val DUMMY_DEVICE_KEY = "DeviceKey"
class CryptoStoreTest {
@RunWith(AndroidJUnit4::class)
class CryptoStoreTest : InstrumentedTest {
private val cryptoStoreHelper = CryptoStoreHelper()
@Before
fun setup() {
Realm.init(context())
}
@Test
fun test_metadata_realm_ok() {
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()

View file

@ -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)
}
}

View file

@ -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<Int>(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"
}
}

View file

@ -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<KeysBackupState>()
private var lastTransitionError: String? = null
init {
keysBackup.addListener(this)
}
// TODO Make expectedStates mandatory to enforce test
fun stopAndCheckStates(expectedStates: List<KeysBackupState>?) {
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()
}
}
}

View file

@ -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<KeyVerificationCancel>()!!
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<KeyVerificationCancel>()!!
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<KeyVerificationCancel>()!!
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<String> = SASVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
hashes: List<String> = SASVerificationTransaction.KNOWN_HASHES,
mac: List<String> = SASVerificationTransaction.KNOWN_MACS,
codes: List<String> = 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<Any>()
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<Void>(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<SASVerificationTransaction>()
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()
}
}

View file

@ -19,8 +19,12 @@ package im.vector.matrix.android.session.room.timeline
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.helper.add
import im.vector.matrix.android.internal.database.helper.lastStateIndex
import im.vector.matrix.android.internal.database.helper.merge
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
@ -28,7 +32,6 @@ import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeR
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.createObject
import org.amshove.kluent.shouldBeFalse
import org.amshove.kluent.shouldBeTrue
import org.amshove.kluent.shouldEqual
import org.junit.Before
@ -43,7 +46,11 @@ internal class ChunkEntityTest : InstrumentedTest {
@Before
fun setup() {
Realm.init(context())
val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build()
val testConfig = RealmConfiguration.Builder()
.inMemory()
.name("test-realm")
.modules(SessionRealmModule())
.build()
monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build()
}
@ -141,30 +148,6 @@ internal class ChunkEntityTest : InstrumentedTest {
}
}
@Test
fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.isUnlinked().shouldBeFalse()
}
}
@Test
fun merge_shouldEventsBeUnlinked_whenMergingUnlinkedWithUnlinked() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.isUnlinked().shouldBeTrue()
}
}
@Test
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
monarchy.runTransactionSync { realm ->
@ -172,8 +155,8 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject()
val prevToken = "prev_token"
chunk1.prevToken = prevToken
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
chunk1.prevToken shouldEqual prevToken
}
@ -186,10 +169,19 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject()
val nextToken = "next_token"
chunk1.nextToken = nextToken
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.nextToken shouldEqual nextToken
}
}
private fun ChunkEntity.addAll(roomId: String,
events: List<Event>,
direction: PaginationDirection,
stateIndexOffset: Int = 0) {
events.forEach { event ->
add(roomId, event, direction, stateIndexOffset)
}
}
}

View file

@ -16,7 +16,6 @@
package im.vector.matrix.android.session.room.timeline
import com.zhuinden.monarchy.Monarchy
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.api.session.events.model.EventType
@ -25,12 +24,6 @@ import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import io.realm.kotlin.createObject
import kotlin.random.Random
object RoomDataHelper {
@ -73,19 +66,4 @@ object RoomDataHelper {
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
}
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
monarchy.runTransactionSync { realm ->
val roomEntity = realm.createObject<RoomEntity>(roomId)
roomEntity.membership = Membership.JOIN
val eventList = createFakeListOfEvents(10)
val chunkEntity = realm.createObject<ChunkEntity>().apply {
nextToken = null
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
isLastForward = true
}
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS)
roomEntity.addOrUpdate(chunkEntity)
}
}
}

View file

@ -66,7 +66,7 @@ internal class TimelineTest : InstrumentedTest {
// val latch = CountDownLatch(2)
// var timelineEvents: List<TimelineEvent> = emptyList()
// timeline.listener = object : Timeline.Listener {
// override fun onUpdated(snapshot: List<TimelineEvent>) {
// override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
// if (snapshot.isNotEmpty()) {
// if (initialLoad == 0) {
// initialLoad = snapshot.size

View file

@ -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()
}

View file

@ -28,6 +28,12 @@ fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
?.chunked(4)
?.joinToString(separator = " ")
fun MutableList<DeviceInfo>.sortByLastSeen() {
sortWith(DatedObjectComparators.descComparator)
/* ==========================================================================================
* DeviceInfo
* ========================================================================================== */
fun List<DeviceInfo>.sortByLastSeen(): List<DeviceInfo> {
val list = toMutableList()
list.sortWith(DatedObjectComparators.descComparator)
return list
}

View file

@ -14,13 +14,15 @@
* limitations under the License.
*/
package im.vector.riotx.core.error
package im.vector.matrix.android.api.failure
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import javax.net.ssl.HttpsURLConnection
fun Throwable.is401(): Boolean {
return (this is Failure.ServerError && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
&& error.code == MatrixError.M_UNAUTHORIZED)
}
fun Throwable.is401() =
this is Failure.ServerError
&& httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
&& error.code == MatrixError.M_UNAUTHORIZED
fun Throwable.isTokenError() =
this is Failure.ServerError
&& (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN)

View file

@ -17,7 +17,6 @@
package im.vector.matrix.android.api.permalinks
import android.text.Spannable
import im.vector.matrix.android.api.MatrixPatterns
/**
* MatrixLinkify take a piece of text and turns all of the
@ -30,7 +29,13 @@ object MatrixLinkify {
*
* @param spannable the text in which the matrix items has to be clickable.
*/
@Suppress("UNUSED_PARAMETER")
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
/**
* I disable it because it mess up with pills, and even with pills, it does not work correctly:
* The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to
*/
/*
// sanity checks
if (spannable.isEmpty()) {
return false
@ -50,5 +55,7 @@ object MatrixLinkify {
}
}
return hasMatch
*/
return false
}
}

View file

@ -56,23 +56,23 @@ object PermalinkParser {
val identifier = params.getOrNull(0)
val extraParameter = params.getOrNull(1)
if (identifier.isNullOrEmpty()) {
return PermalinkData.FallbackLink(uri)
}
return when {
identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
MatrixPatterns.isRoomId(identifier) -> {
val eventId = extraParameter.takeIf {
!it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
}
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = false, eventId = eventId)
PermalinkData.RoomLink(
roomIdOrAlias = identifier,
isRoomAlias = false,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
)
}
MatrixPatterns.isRoomAlias(identifier) -> {
val eventId = extraParameter.takeIf {
!it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
}
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = true, eventId = eventId)
PermalinkData.RoomLink(
roomIdOrAlias = identifier,
isRoomAlias = true,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
)
}
else -> PermalinkData.FallbackLink(uri)
}

View file

@ -0,0 +1,35 @@
/*
* 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.query
/**
* Basic query language. All these cases are mutually exclusive.
*/
sealed class QueryStringValue {
object NoCondition : QueryStringValue()
object IsNull : QueryStringValue()
object IsNotNull : QueryStringValue()
object IsEmpty : QueryStringValue()
object IsNotEmpty : QueryStringValue()
data class Equals(val string: String, val case: Case) : QueryStringValue()
data class Contains(val string: String, val case: Case) : QueryStringValue()
enum class Case {
SENSITIVE,
INSENSITIVE
}
}

View file

@ -73,6 +73,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.
*/
@ -107,7 +112,12 @@ interface Session :
* This method allows to listen the sync state.
* @return a [LiveData] of [SyncState].
*/
fun syncState(): LiveData<SyncState>
fun getSyncStateLive(): LiveData<SyncState>
/**
* This methods return true if an initial sync has been processed
*/
fun hasAlreadySynced(): Boolean
/**
* This method allow to close a session. It does stop some services.

View file

@ -24,7 +24,7 @@ import im.vector.matrix.android.api.MatrixCallback
interface CacheService {
/**
* Clear the whole cached data, except credentials. Once done, the session is closed and has to be opened again
* Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user.
*/
fun clearCache(callback: MatrixCallback<Unit>)
}

View file

@ -30,6 +30,7 @@ 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.MXEncryptEventContentResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
@ -89,6 +90,8 @@ interface CryptoService {
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
fun isRoomEncrypted(roomId: String): Boolean

View file

@ -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"
@ -50,10 +49,11 @@ object EventType {
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
const val STATE_ROOM_ALIASES = "m.room.aliases"
const val STATE_ROOM_TOMBSTONE = "m.room.tombstone"
const val STATE_CANONICAL_ALIAS = "m.room.canonical_alias"
const val STATE_HISTORY_VISIBILITY = "m.room.history_visibility"
const val STATE_RELATED_GROUPS = "m.room.related_groups"
const val STATE_PINNED_EVENT = "m.room.pinned_events"
const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias"
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
@ -86,10 +86,12 @@ object EventType {
STATE_ROOM_JOIN_RULES,
STATE_ROOM_GUEST_ACCESS,
STATE_ROOM_POWER_LEVELS,
STATE_ROOM_ALIASES,
STATE_ROOM_TOMBSTONE,
STATE_HISTORY_VISIBILITY,
STATE_RELATED_GROUPS,
STATE_PINNED_EVENT
STATE_ROOM_CANONICAL_ALIAS,
STATE_ROOM_HISTORY_VISIBILITY,
STATE_ROOM_RELATED_GROUPS,
STATE_ROOM_PINNED_EVENT
)
fun isStateEvent(type: String): Boolean {

View file

@ -31,9 +31,22 @@ interface GroupService {
*/
fun getGroup(groupId: String): Group?
/**
* Get a groupSummary from a groupId
* @param groupId the groupId to look for.
* @return the groupSummary with groupId or null
*/
fun getGroupSummary(groupId: String): GroupSummary?
/**
* Get a list of group summaries. This list is a snapshot of the data.
* @return the list of [GroupSummary]
*/
fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List<GroupSummary>
/**
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of [GroupSummary]
*/
fun liveGroupSummaries(): LiveData<List<GroupSummary>>
fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>>
}

View file

@ -0,0 +1,44 @@
/*
* 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.group
import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.room.model.Membership
fun groupSummaryQueryParams(init: (GroupSummaryQueryParams.Builder.() -> Unit) = {}): GroupSummaryQueryParams {
return GroupSummaryQueryParams.Builder().apply(init).build()
}
/**
* This class can be used to filter group summaries
*/
data class GroupSummaryQueryParams(
val displayName: QueryStringValue,
val memberships: List<Membership>
) {
class Builder {
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var memberships: List<Membership> = Membership.all()
fun build() = GroupSummaryQueryParams(
displayName = displayName,
memberships = memberships
)
}
}

View file

@ -58,7 +58,7 @@ interface PushersService {
const val EVENT_ID_ONLY = "event_id_only"
}
fun livePushers(): LiveData<List<Pusher>>
fun getPushersLive(): LiveData<List<Pusher>>
fun pushers() : List<Pusher>
}

View file

@ -56,5 +56,8 @@ interface Room :
*/
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
/**
* A current snapshot of [RoomSummary] associated with the room
*/
fun roomSummary(): RoomSummary?
}

View file

@ -53,16 +53,35 @@ interface RoomService {
fun getRoom(roomId: String): Room?
/**
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of [RoomSummary]
* Get a roomSummary from a roomId or a room alias
* @param roomIdOrAlias the roomId or the alias of a room to look for.
* @return a matching room summary or null
*/
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
/**
* Get a snapshot list of room summaries.
* @return the immutable list of [RoomSummary]
*/
fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary>
/**
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of List[RoomSummary]
*/
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>>
/**
* Get a snapshot list of Breadcrumbs
* @return the immutable list of [RoomSummary]
*/
fun getBreadcrumbs(): List<RoomSummary>
/**
* Get a live list of Breadcrumbs
* @return the [LiveData] of [RoomSummary]
*/
fun liveBreadcrumbs(): LiveData<List<RoomSummary>>
fun getBreadcrumbsLive(): LiveData<List<RoomSummary>>
/**
* Inform the Matrix SDK that a room is displayed.

View file

@ -0,0 +1,48 @@
/*
* 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
import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.room.model.Membership
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
return RoomSummaryQueryParams.Builder().apply(init).build()
}
/**
* This class can be used to filter room summaries to use with:
* [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
*/
data class RoomSummaryQueryParams(
val displayName: QueryStringValue,
val canonicalAlias: QueryStringValue,
val memberships: List<Membership>
) {
class Builder {
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
var memberships: List<Membership> = Membership.all()
fun build() = RoomSummaryQueryParams(
displayName = displayName,
canonicalAlias = canonicalAlias,
memberships = memberships
)
}
}

View file

@ -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<Unit>)
}

View file

@ -41,11 +41,18 @@ interface MembershipService {
fun getRoomMember(userId: String): RoomMember?
/**
* Return all the roomMembers ids of the room
*
* Return all the roomMembers of the room with params
* @param queryParams the params to query for
* @return a roomMember list.
*/
fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember>
/**
* Return all the roomMembers of the room filtered by memberships
* @param queryParams the params to query for
* @return a [LiveData] of roomMember list.
*/
fun getRoomMemberIdsLive(): LiveData<List<String>>
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>>
fun getNumberOfJoinedMembers(): Int

View file

@ -0,0 +1,44 @@
/*
* 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.members
import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.room.model.Membership
fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {}): RoomMemberQueryParams {
return RoomMemberQueryParams.Builder().apply(init).build()
}
/**
* This class can be used to filter room members
*/
data class RoomMemberQueryParams(
val displayName: QueryStringValue,
val memberships: List<Membership>
) {
class Builder {
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var memberships: List<Membership> = Membership.all()
fun build() = RoomMemberQueryParams(
displayName = displayName,
memberships = memberships
)
}
}

View file

@ -43,4 +43,14 @@ enum class Membership(val value: String) {
fun isLeft(): Boolean {
return this == KNOCK || this == LEAVE || this == BAN
}
companion object {
fun activeMemberships(): List<Membership> {
return listOf(INVITE, JOIN)
}
fun all(): List<Membership> {
return values().asList()
}
}
}

View file

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Class representing the EventType.STATE_CANONICAL_ALIAS state event content
* Class representing the EventType.STATE_ROOM_CANONICAL_ALIAS state event content
*/
@JsonClass(generateAdapter = true)
data class RoomCanonicalAliasContent(

View file

@ -16,23 +16,12 @@
package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.UnsignedData
/**
* Class representing the EventType.STATE_ROOM_MEMBER state event content
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
*/
@JsonClass(generateAdapter = true)
data class RoomMember(
@Json(name = "membership") val membership: Membership,
@Json(name = "reason") val reason: String? = null,
@Json(name = "displayname") val displayName: String? = null,
@Json(name = "avatar_url") val avatarUrl: String? = null,
@Json(name = "is_direct") val isDirect: Boolean = false,
@Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null,
@Json(name = "unsigned") val unsignedData: UnsignedData? = null
) {
val safeReason
get() = reason?.takeIf { it.isNotBlank() }
}
val membership: Membership,
val userId: String,
val displayName: String? = null,
val avatarUrl: String? = null
)

View file

@ -0,0 +1,38 @@
/*
* 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
import im.vector.matrix.android.api.session.events.model.UnsignedData
/**
* Class representing the EventType.STATE_ROOM_MEMBER state event content
*/
@JsonClass(generateAdapter = true)
data class RoomMemberContent(
@Json(name = "membership") val membership: Membership,
@Json(name = "reason") val reason: String? = null,
@Json(name = "displayname") val displayName: String? = null,
@Json(name = "avatar_url") val avatarUrl: String? = null,
@Json(name = "is_direct") val isDirect: Boolean = false,
@Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null,
@Json(name = "unsigned") val unsignedData: UnsignedData? = null
) {
val safeReason
get() = reason?.takeIf { it.isNotBlank() }
}

View file

@ -41,7 +41,8 @@ data class RoomSummary(
val membership: Membership = Membership.NONE,
val versioningState: VersioningState = VersioningState.NONE,
val readMarkerId: String? = null,
val userDrafts: List<UserDraft> = emptyList()
val userDrafts: List<UserDraft> = emptyList(),
var isEncrypted: Boolean
) {
val isVersioned: Boolean

View file

@ -125,7 +125,7 @@ class CreateRoomParams {
val contentMap = HashMap<String, String>()
contentMap["algorithm"] = algorithm
val algoEvent = Event(type = EventType.ENCRYPTION,
val algoEvent = Event(type = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "",
content = contentMap.toContent()
)
@ -145,13 +145,13 @@ class CreateRoomParams {
*/
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
// Remove the existing value if any.
initialStates?.removeAll { it.getClearType() == EventType.STATE_HISTORY_VISIBILITY }
initialStates?.removeAll { it.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY }
if (historyVisibility != null) {
val contentMap = HashMap<String, RoomHistoryVisibility>()
contentMap["history_visibility"] = historyVisibility
val historyVisibilityEvent = Event(type = EventType.STATE_HISTORY_VISIBILITY,
val historyVisibilityEvent = Event(type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
stateKey = "",
content = contentMap.toContent())

View file

@ -98,7 +98,7 @@ interface RelationService {
/**
* Reply to an event in the timeline (must be in same room)
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
* The replyText can be a Spannable and contains special spans (UserMentionSpan) that will be translated
* The replyText can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
* by the sdk into pills.
* @param eventReplied the event referenced by the reply
* @param replyText the reply text
@ -108,5 +108,17 @@ interface RelationService {
replyText: CharSequence,
autoMarkdown: Boolean = false): Cancelable?
fun getEventSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
/**
* Get the current EventAnnotationsSummary
* @param eventId the eventId to look for EventAnnotationsSummary
* @return the EventAnnotationsSummary found
*/
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary?
/**
* Get a LiveData of EventAnnotationsSummary for the specified eventId
* @param eventId the eventId to look for EventAnnotationsSummary
* @return the LiveData of EventAnnotationsSummary
*/
fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
}

View file

@ -19,9 +19,9 @@ package im.vector.matrix.android.api.session.room.send
import im.vector.matrix.android.api.util.MatrixItem
/**
* Tag class for spans that should mention a user.
* Tag class for spans that should mention a matrix item.
* These Spans will be transformed into pills when detected in message to send
*/
interface UserMentionSpan {
interface MatrixItemSpan {
val matrixItem: MatrixItem
}

View file

@ -29,7 +29,7 @@ interface SendService {
/**
* Method to send a text message asynchronously.
* The text to send can be a Spannable and contains special spans (UserMentionSpan) that will be translated
* The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
* by the sdk into pills.
* @param text the text message to send
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE

View file

@ -26,5 +26,10 @@ interface StateService {
*/
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
/**
* Enable encryption of the room
*/
fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>)
fun getStateEvent(eventType: String): Event?
}

View file

@ -65,7 +65,7 @@ interface Timeline {
/**
* This is the main method to enrich the timeline with new data.
* It will call the onUpdated method from [Listener] when the data will be processed.
* It will call the onTimelineUpdated method from [Listener] when the data will be processed.
* It also ensures only one pagination by direction is launched at a time, so you can safely call this multiple time in a row.
*/
fun paginate(direction: Direction, count: Int)
@ -106,7 +106,12 @@ interface Timeline {
* Call when the timeline has been updated through pagination or sync.
* @param snapshot the most up to date snapshot
*/
fun onUpdated(snapshot: List<TimelineEvent>)
fun onTimelineUpdated(snapshot: List<TimelineEvent>)
/**
* Called whenever an error we can't recover from occurred
*/
fun onTimelineFailure(throwable: Throwable)
}
/**

View file

@ -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<Unit>): Cancelable
}

View file

@ -50,25 +50,25 @@ interface UserService {
* @param userId the userId to look for.
* @return a LiveData of user with userId
*/
fun liveUser(userId: String): LiveData<Optional<User>>
fun getUserLive(userId: String): LiveData<Optional<User>>
/**
* Observe a live list of users sorted alphabetically
* @return a Livedata of users
*/
fun liveUsers(): LiveData<List<User>>
fun getUsersLive(): LiveData<List<User>>
/**
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
* @param filter the filter. It will look into userId and displayName.
* @return a Livedata of users
*/
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>
fun getPagedUsersLive(filter: String? = null): LiveData<PagedList<User>>
/**
* Get list of ignored users
*/
fun liveIgnoredUsers(): LiveData<List<User>>
fun getIgnoredUsersLive(): LiveData<List<User>>
/**
* Ignore users

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.util
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.user.model.User
@ -62,6 +63,9 @@ sealed class MatrixItem(
init {
if (BuildConfig.DEBUG) checkId()
}
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
override fun getBestName() = id
}
data class GroupItem(override val id: String,
@ -71,9 +75,12 @@ sealed class MatrixItem(
init {
if (BuildConfig.DEBUG) checkId()
}
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
override fun getBestName() = id
}
fun getBestName(): String {
open fun getBestName(): String {
return displayName?.takeIf { it.isNotBlank() } ?: id
}
@ -95,7 +102,7 @@ sealed class MatrixItem(
}
fun firstLetterOfDisplayName(): String {
return getBestName()
return (displayName?.takeIf { it.isNotBlank() } ?: id)
.let { dn ->
var startIndex = 0
val initial = dn[startIndex]
@ -138,4 +145,6 @@ sealed class MatrixItem(
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
fun RoomMember.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)

View file

@ -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<String, SessionComponent>()
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)

View file

@ -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<OkHttpClient>,
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<OkHttpClient>,
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<Versions> {
executeRequest<Versions>(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<RiotConfig> {
val riotConfig = executeRequest<RiotConfig>(null) {
apiCall = authAPI.getRiotConfig()
}
@ -153,7 +154,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
val versions = executeRequest<Versions> {
val versions = executeRequest<Versions>(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<LoginFlowResponse> {
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
apiCall = authAPI.getLoginFlows()
}
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)

View file

@ -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()
}

View file

@ -16,6 +16,9 @@
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.sessionId
import im.vector.matrix.android.internal.di.MoshiProvider
import io.realm.DynamicRealm
import io.realm.RealmMigration
import timber.log.Timber
@ -23,35 +26,59 @@ import timber.log.Timber
internal object AuthRealmMigration : RealmMigration {
// Current schema version
const val SCHEMA_VERSION = 2L
const val SCHEMA_VERSION = 3L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
if (oldVersion <= 0) {
Timber.d("Step 0 -> 1")
Timber.d("Create PendingSessionEntity")
if (oldVersion <= 0) migrateTo1(realm)
if (oldVersion <= 1) migrateTo2(realm)
if (oldVersion <= 2) migrateTo3(realm)
}
realm.schema.create("PendingSessionEntity")
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
.setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
.addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
.setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
.addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
.setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
.addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
.addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
}
private fun migrateTo1(realm: DynamicRealm) {
Timber.d("Step 0 -> 1")
Timber.d("Create PendingSessionEntity")
if (oldVersion <= 1) {
Timber.d("Step 1 -> 2")
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
realm.schema.create("PendingSessionEntity")
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
.setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
.addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
.setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
.addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
.setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
.addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
.addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
}
realm.schema.get("SessionParamsEntity")
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
}
private fun migrateTo2(realm: DynamicRealm) {
Timber.d("Step 1 -> 2")
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
realm.schema.get("SessionParamsEntity")
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
}
private fun migrateTo3(realm: DynamicRealm) {
Timber.d("Step 2 -> 3")
Timber.d("Update SessionParamsEntity primary key, to allow several sessions with the same userId")
realm.schema.get("SessionParamsEntity")
?.removePrimaryKey()
?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java)
?.setRequired(SessionParamsEntityFields.SESSION_ID, true)
?.transform {
val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON)
val credentials = MoshiProvider.providesMoshi()
.adapter(Credentials::class.java)
.fromJson(credentialsJson)
it.set(SessionParamsEntityFields.SESSION_ID, credentials!!.sessionId())
}
?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
}
}

View file

@ -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()
}

View file

@ -20,7 +20,8 @@ import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
internal open class SessionParamsEntity(
@PrimaryKey var userId: String = "",
@PrimaryKey var sessionId: String = "",
var userId: String = "",
var credentialsJson: String = "",
var homeServerConnectionConfigJson: String = "",
// Set to false when the token is invalid and the user has been soft logged out

View file

@ -20,6 +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.api.auth.data.sessionId
import javax.inject.Inject
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
@ -49,6 +50,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
return null
}
return SessionParamsEntity(
sessionParams.credentials.sessionId(),
sessionParams.credentials.userId,
credentialsJson,
homeServerConnectionConfigJson,

View file

@ -72,7 +72,7 @@ internal class DefaultLoginWizard(
} else {
PasswordLoginParams.userIdentifier(login, password, deviceName)
}
val credentials = executeRequest<Credentials> {
val credentials = executeRequest<Credentials>(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<AddThreePidRegistrationResponse> {
val result = executeRequest<AddThreePidRegistrationResponse>(null) {
apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
}
@ -120,7 +120,7 @@ internal class DefaultLoginWizard(
resetPasswordData.newPassword
)
executeRequest<Unit> {
executeRequest<Unit>(null) {
apiCall = authAPI.resetPasswordMailConfirmed(param)
}

View file

@ -29,11 +29,12 @@ internal interface RegisterAddThreePidTask : Task<RegisterAddThreePidTask.Params
)
}
internal class DefaultRegisterAddThreePidTask(private val authAPI: AuthAPI)
: RegisterAddThreePidTask {
internal class DefaultRegisterAddThreePidTask(
private val authAPI: AuthAPI
) : RegisterAddThreePidTask {
override suspend fun execute(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationResponse {
return executeRequest {
return executeRequest(null) {
apiCall = authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params))
}
}

View file

@ -29,12 +29,13 @@ internal interface RegisterTask : Task<RegisterTask.Params, Credentials> {
)
}
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) {

View file

@ -27,11 +27,12 @@ internal interface ValidateCodeTask : Task<ValidateCodeTask.Params, SuccessResul
)
}
internal class DefaultValidateCodeTask(private val authAPI: AuthAPI)
: ValidateCodeTask {
internal class DefaultValidateCodeTask(
private val authAPI: AuthAPI
) : ValidateCodeTask {
override suspend fun execute(params: ValidateCodeTask.Params): SuccessResult {
return executeRequest {
return executeRequest(null) {
apiCall = authAPI.validate3Pid(params.url, params.body)
}
}

View file

@ -47,7 +47,7 @@ internal abstract class CryptoModule {
@Module
companion object {
internal const val DB_ALIAS_PREFIX = "crypto_module_"
internal fun getKeyAlias(userMd5: String) = "crypto_module_$userMd5"
@JvmStatic
@Provides
@ -59,7 +59,7 @@ internal abstract class CryptoModule {
return RealmConfiguration.Builder()
.directory(directory)
.apply {
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
}
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
@ -123,6 +123,9 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
@Binds
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
@Binds
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask

View file

@ -50,6 +50,7 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
@ -127,6 +128,7 @@ internal class DefaultCryptoService @Inject constructor(
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
// Tasks
private val getDevicesTask: GetDevicesTask,
private val getDeviceInfoTask: GetDeviceInfoTask,
private val setDeviceNameTask: SetDeviceNameTask,
private val uploadKeysTask: UploadKeysTask,
private val loadRoomMembersTask: LoadRoomMembersTask,
@ -145,17 +147,17 @@ internal class DefaultCryptoService @Inject constructor(
fun onStateEvent(roomId: String, event: Event) {
when {
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(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)
}
}
fun onLiveEvent(roomId: String, event: Event) {
when {
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(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)
}
}
@ -199,6 +201,14 @@ internal class DefaultCryptoService @Inject constructor(
.executeBy(taskExecutor)
}
override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
getDeviceInfoTask
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
}
@ -472,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
}

View file

@ -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)

View file

@ -25,11 +25,18 @@ internal interface CryptoApi {
/**
* Get the devices list
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-devices
* Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices")
fun getDevices(): Call<DevicesListResponse>
/**
* Get the device info by id
* Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices-deviceid
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}")
fun getDeviceInfo(@Path("deviceId") deviceId: String): Call<DeviceInfo>
/**
* Upload device and/or one-time keys.
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-upload

View file

@ -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<KeysVersion>) {
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

View file

@ -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<CreateKeysBackupVersionBody, KeysVersion>
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)
}
}

View file

@ -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<DeleteBackupTask.Params, Unit> {
@ -27,11 +28,13 @@ internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
)
}
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)
}
}

View file

@ -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<DeleteRoomSessionDataTask.Params, Unit> {
@ -29,11 +30,13 @@ internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Pa
)
}
internal class DefaultDeleteRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteRoomSessionDataTask {
internal class DefaultDeleteRoomSessionDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : DeleteRoomSessionDataTask {
override suspend fun execute(params: DeleteRoomSessionDataTask.Params) {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.deleteRoomSessionData(
params.roomId,
params.sessionId,

View file

@ -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 DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.Params, Unit> {
@ -28,11 +29,13 @@ internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.
)
}
internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteRoomSessionsDataTask {
internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : DeleteRoomSessionsDataTask {
override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.deleteRoomSessionsData(
params.roomId,
params.version)

View file

@ -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 DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params, Unit> {
@ -27,11 +28,13 @@ internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params,
)
}
internal class DefaultDeleteSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteSessionsDataTask {
internal class DefaultDeleteSessionsDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : DeleteSessionsDataTask {
override suspend fun execute(params: DeleteSessionsDataTask.Params) {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.deleteSessionsData(params.version)
}
}

View file

@ -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 GetKeysBackupLastVersionTask : Task<Unit, KeysVersionResult>
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()
}
}

View file

@ -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<String, KeysVersionResult>
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)
}
}

View file

@ -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<GetRoomSessionDataTask.Params, KeyBackupData> {
@ -30,11 +31,13 @@ internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params,
)
}
internal class DefaultGetRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: GetRoomSessionDataTask {
internal class DefaultGetRoomSessionDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : GetRoomSessionDataTask {
override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.getRoomSessionData(
params.roomId,
params.sessionId,

View file

@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
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 GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params, RoomKeysBackupData> {
@ -29,11 +30,13 @@ internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params
)
}
internal class DefaultGetRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: GetRoomSessionsDataTask {
internal class DefaultGetRoomSessionsDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : GetRoomSessionsDataTask {
override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.getRoomSessionsData(
params.roomId,
params.version)

View file

@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
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 GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBackupData> {
@ -28,11 +29,13 @@ internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBa
)
}
internal class DefaultGetSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: GetSessionsDataTask {
internal class DefaultGetSessionsDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : GetSessionsDataTask {
override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.getSessionsData(params.version)
}
}

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeys
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 StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Params, BackupKeysResult> {
@ -32,11 +33,13 @@ internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Para
)
}
internal class DefaultStoreRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: StoreRoomSessionDataTask {
internal class DefaultStoreRoomSessionDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : StoreRoomSessionDataTask {
override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.storeRoomSessionData(
params.roomId,
params.sessionId,

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeys
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
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 StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Params, BackupKeysResult> {
@ -31,11 +32,13 @@ internal interface StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Pa
)
}
internal class DefaultStoreRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: StoreRoomSessionsDataTask {
internal class DefaultStoreRoomSessionsDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : StoreRoomSessionsDataTask {
override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.storeRoomSessionsData(
params.roomId,
params.version,

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeys
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
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 StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, BackupKeysResult> {
@ -30,11 +31,13 @@ internal interface StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, Ba
)
}
internal class DefaultStoreSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: StoreSessionsDataTask {
internal class DefaultStoreSessionsDataTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : StoreSessionsDataTask {
override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.storeSessionsData(
params.version,
params.keysBackupData)

View file

@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
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 UpdateKeysBackupVersionTask : Task<UpdateKeysBackupVersionTask.Params, Unit> {
@ -29,11 +30,13 @@ internal interface UpdateKeysBackupVersionTask : Task<UpdateKeysBackupVersionTas
)
}
internal class DefaultUpdateKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: UpdateKeysBackupVersionTask {
internal class DefaultUpdateKeysBackupVersionTask @Inject constructor(
private val roomKeysApi: RoomKeysApi,
private val eventBus: EventBus
) : UpdateKeysBackupVersionTask {
override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) {
return executeRequest {
return executeRequest(eventBus) {
apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody)
}
}

View file

@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysClaimBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysClaimResponse
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
@ -33,13 +34,15 @@ internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysFor
)
}
internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private val cryptoApi: CryptoApi)
: ClaimOneTimeKeysForUsersDeviceTask {
internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(
private val cryptoApi: CryptoApi,
private val eventBus: EventBus
) : ClaimOneTimeKeysForUsersDeviceTask {
override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap<MXKey> {
val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)
val keysClaimResponse = executeRequest<KeysClaimResponse> {
val keysClaimResponse = executeRequest<KeysClaimResponse>(eventBus) {
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
}
val map = MXUsersDevicesMap<MXKey>()

View file

@ -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<DeleteDeviceTask.Params, Unit> {
@ -31,12 +32,14 @@ internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
)
}
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<Unit> {
executeRequest<Unit>(eventBus) {
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
}
} catch (throwable: Throwable) {

View file

@ -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<DeleteDeviceWithUserPasswordTask.Params, Unit> {
@ -33,12 +34,14 @@ internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserP
)
}
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(private val cryptoApi: CryptoApi,
@UserId private val userId: String)
: DeleteDeviceWithUserPasswordTask {
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(
private val cryptoApi: CryptoApi,
@UserId private val userId: String,
private val eventBus: EventBus
) : DeleteDeviceWithUserPasswordTask {
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
return executeRequest {
return executeRequest(eventBus) {
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()
.apply {
deleteDeviceAuth = DeleteDeviceAuth()

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse
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 DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
@ -31,8 +32,10 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
val token: String?)
}
internal class DefaultDownloadKeysForUsers @Inject constructor(private val cryptoApi: CryptoApi)
: DownloadKeysForUsersTask {
internal class DefaultDownloadKeysForUsers @Inject constructor(
private val cryptoApi: CryptoApi,
private val eventBus: EventBus
) : DownloadKeysForUsersTask {
override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.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)
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.tasks
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<GetDeviceInfoTask.Params, DeviceInfo> {
data class Params(val deviceId: String)
}
internal class DefaultGetDeviceInfoTask @Inject constructor(
private val cryptoApi: CryptoApi,
private val eventBus: EventBus
) : GetDeviceInfoTask {
override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo {
return executeRequest(eventBus) {
apiCall = cryptoApi.getDeviceInfo(params.deviceId)
}
}
}

View file

@ -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<Unit, DevicesListResponse>
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()
}
}

View file

@ -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<GetKeyChangesTask.Params, KeyChangesResponse> {
@ -31,11 +32,13 @@ internal interface GetKeyChangesTask : Task<GetKeyChangesTask.Params, KeyChanges
)
}
internal class DefaultGetKeyChangesTask @Inject constructor(private val cryptoApi: CryptoApi)
: GetKeyChangesTask {
internal class DefaultGetKeyChangesTask @Inject constructor(
private val cryptoApi: CryptoApi,
private val eventBus: EventBus
) : GetKeyChangesTask {
override suspend fun execute(params: GetKeyChangesTask.Params): KeyChangesResponse {
return executeRequest {
return executeRequest(eventBus) {
apiCall = cryptoApi.getKeyChanges(params.from, params.to)
}
}

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
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
import kotlin.random.Random
@ -35,14 +36,16 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
)
}
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(),

View file

@ -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<SetDeviceNameTask.Params, Unit> {
@ -31,14 +32,16 @@ internal interface SetDeviceNameTask : Task<SetDeviceNameTask.Params, Unit> {
)
}
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)
}
}

View file

@ -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<UploadKeysTask.Params, KeysUploadResponse> {
@ -36,8 +37,10 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
val deviceId: String)
}
internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi: CryptoApi)
: UploadKeysTask {
internal class DefaultUploadKeysTask @Inject constructor(
private val cryptoApi: CryptoApi,
private val eventBus: EventBus
) : UploadKeysTask {
override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
val encodedDeviceId = convertToUTF8(params.deviceId)
@ -52,7 +55,7 @@ internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi:
body.oneTimeKeys = params.oneTimeKeys
}
return executeRequest {
return executeRequest(eventBus) {
apiCall = if (encodedDeviceId.isBlank()) {
cryptoApi.uploadKeys(body)
} else {

View file

@ -19,12 +19,16 @@ package im.vector.matrix.android.internal.database
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.util.createBackgroundHandler
import io.realm.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
internal interface LiveEntityObserver {
fun start()
fun dispose()
fun cancelProcess()
fun isStarted(): Boolean
}
@ -35,6 +39,7 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
}
protected val observerScope = CoroutineScope(SupervisorJob())
protected abstract val query: Monarchy.Query<T>
private val isStarted = AtomicBoolean(false)
private val backgroundRealm = AtomicReference<Realm>()
@ -59,10 +64,15 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
backgroundRealm.getAndSet(null).also {
it.close()
}
observerScope.coroutineContext.cancelChildren()
}
}
}
override fun cancelProcess() {
observerScope.coroutineContext.cancelChildren()
}
override fun isStarted(): Boolean {
return isStarted.get()
}

Some files were not shown because too many files have changed in this diff Show more