mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Merge branch 'develop' into cross_signing
This commit is contained in:
commit
fb9abefe59
312 changed files with 9755 additions and 2589 deletions
|
@ -20,6 +20,7 @@
|
|||
<w>signin</w>
|
||||
<w>signout</w>
|
||||
<w>signup</w>
|
||||
<w>threepid</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
37
CHANGES.md
37
CHANGES.md
|
@ -1,20 +1,17 @@
|
|||
Changes in RiotX 0.12.0 (2019-XX-XX)
|
||||
Changes in RiotX 0.13.0 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
|
||||
Improvements 🙌:
|
||||
- The initial sync is now handled by a foreground service
|
||||
- Render aliases and canonical alias change in the timeline
|
||||
- Fix autocompletion issues and add support for rooms and groups
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix avatar image disappearing (#777)
|
||||
- Fix read marker banner when permalink
|
||||
-
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
@ -22,6 +19,32 @@ Translations 🗣:
|
|||
Build 🧱:
|
||||
-
|
||||
|
||||
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)
|
||||
===================================================
|
||||
|
||||
|
@ -281,7 +304,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 ✨:
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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.")
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -19,7 +19,10 @@ 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
|
||||
|
@ -29,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
|
||||
|
@ -146,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 ->
|
||||
|
@ -177,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
|
||||
}
|
||||
|
@ -191,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,7 @@ 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -25,7 +25,6 @@ object EventType {
|
|||
const val MESSAGE = "m.room.message"
|
||||
const val STICKER = "m.sticker"
|
||||
const val ENCRYPTED = "m.room.encrypted"
|
||||
const val ENCRYPTION = "m.room.encryption"
|
||||
const val FEEDBACK = "m.room.message.feedback"
|
||||
const val TYPING = "m.typing"
|
||||
const val REDACTION = "m.room.redaction"
|
||||
|
@ -54,6 +53,7 @@ object EventType {
|
|||
const val STATE_ROOM_HISTORY_VISIBILITY = "m.room.history_visibility"
|
||||
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
|
||||
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
|
||||
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
||||
|
||||
// Call Events
|
||||
|
||||
|
|
|
@ -38,9 +38,15 @@ interface GroupService {
|
|||
*/
|
||||
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>>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -56,5 +56,8 @@ interface Room :
|
|||
*/
|
||||
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
|
||||
|
||||
/**
|
||||
* A current snapshot of [RoomSummary] associated with the room
|
||||
*/
|
||||
fun roomSummary(): RoomSummary?
|
||||
}
|
||||
|
|
|
@ -60,16 +60,28 @@ interface RoomService {
|
|||
fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
|
||||
|
||||
/**
|
||||
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
||||
* @return the [LiveData] of [RoomSummary]
|
||||
* Get a snapshot list of room summaries.
|
||||
* @return the immutable list of [RoomSummary]
|
||||
*/
|
||||
fun liveRoomSummaries(): LiveData<List<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.
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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>)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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() }
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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>>
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -146,3 +147,4 @@ fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, ava
|
|||
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
@ -149,7 +151,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
fun onStateEvent(roomId: String, event: Event) {
|
||||
when {
|
||||
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||
}
|
||||
|
@ -157,7 +159,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
fun onLiveEvent(roomId: String, event: Event) {
|
||||
when {
|
||||
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||
}
|
||||
|
@ -203,6 +205,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)
|
||||
}
|
||||
|
@ -476,7 +486,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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -16,43 +16,36 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database
|
||||
|
||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||
import io.realm.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConfiguration,
|
||||
private val realmQueryBuilder: (Realm) -> RealmQuery<E>) {
|
||||
internal suspend fun <T> awaitNotEmptyResult(realmConfiguration: RealmConfiguration,
|
||||
timeoutMillis: Long,
|
||||
builder: (Realm) -> RealmQuery<T>) {
|
||||
withTimeout(timeoutMillis) {
|
||||
// Confine Realm interaction to a single thread with Looper.
|
||||
withContext(Dispatchers.Main) {
|
||||
val latch = CompletableDeferred<Unit>()
|
||||
|
||||
private companion object {
|
||||
val QUERY_LATCH_HANDLER = createBackgroundHandler("REALM_QUERY_LATCH")
|
||||
}
|
||||
Realm.getInstance(realmConfiguration).use { realm ->
|
||||
val result = builder(realm).findAllAsync()
|
||||
|
||||
@Throws(InterruptedException::class)
|
||||
fun await(timeout: Long, timeUnit: TimeUnit) {
|
||||
val realmRef = AtomicReference<Realm>()
|
||||
val latch = CountDownLatch(1)
|
||||
QUERY_LATCH_HANDLER.post {
|
||||
val realm = Realm.getInstance(realmConfiguration)
|
||||
realmRef.set(realm)
|
||||
val result = realmQueryBuilder(realm).findAllAsync()
|
||||
result.addChangeListener(object : RealmChangeListener<RealmResults<E>> {
|
||||
override fun onChange(t: RealmResults<E>) {
|
||||
if (t.isNotEmpty()) {
|
||||
result.removeChangeListener(this)
|
||||
latch.countDown()
|
||||
val listener = object : RealmChangeListener<RealmResults<T>> {
|
||||
override fun onChange(it: RealmResults<T>) {
|
||||
if (it.isNotEmpty()) {
|
||||
result.removeChangeListener(this)
|
||||
latch.complete(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
try {
|
||||
latch.await(timeout, timeUnit)
|
||||
} catch (exception: InterruptedException) {
|
||||
throw exception
|
||||
} finally {
|
||||
QUERY_LATCH_HANDLER.post {
|
||||
realmRef.getAndSet(null).close()
|
||||
|
||||
result.addChangeListener(listener)
|
||||
try {
|
||||
latch.await()
|
||||
} catch (e: CancellationException) {
|
||||
result.removeChangeListener(listener)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.database
|
|||
|
||||
import android.content.Context
|
||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.UserCacheDirectory
|
||||
import im.vector.matrix.android.internal.di.UserMd5
|
||||
import im.vector.matrix.android.internal.session.SessionModule
|
||||
|
@ -37,13 +38,14 @@ private const val REALM_NAME = "disk_store.realm"
|
|||
*/
|
||||
internal class SessionRealmConfigurationFactory @Inject constructor(private val realmKeysUtils: RealmKeysUtils,
|
||||
@UserCacheDirectory val directory: File,
|
||||
@SessionId val sessionId: String,
|
||||
@UserMd5 val userMd5: String,
|
||||
context: Context) {
|
||||
|
||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
||||
|
||||
fun create(): RealmConfiguration {
|
||||
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", false)
|
||||
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
||||
if (shouldClearRealm) {
|
||||
Timber.v("************************************************************")
|
||||
Timber.v("The realm file session was corrupted and couldn't be loaded.")
|
||||
|
@ -53,14 +55,15 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val
|
|||
}
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", true)
|
||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
|
||||
.apply()
|
||||
|
||||
val realmConfiguration = RealmConfiguration.Builder()
|
||||
.compactOnLaunch()
|
||||
.directory(directory)
|
||||
.name(REALM_NAME)
|
||||
.apply {
|
||||
realmKeysUtils.configureEncryption(this, "${SessionModule.DB_ALIAS_PREFIX}$userMd5")
|
||||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||
}
|
||||
.modules(SessionRealmModule())
|
||||
.deleteRealmIfMigrationNeeded()
|
||||
|
@ -71,7 +74,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val
|
|||
Timber.v("Successfully create realm instance")
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", false)
|
||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
||||
.apply()
|
||||
}
|
||||
return realmConfiguration
|
||||
|
|
|
@ -28,15 +28,7 @@ import im.vector.matrix.android.internal.database.query.where
|
|||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import io.realm.Sort
|
||||
|
||||
// By default if a chunk is empty we consider it unlinked
|
||||
internal fun ChunkEntity.isUnlinked(): Boolean {
|
||||
assertIsManaged()
|
||||
return timelineEvents.where()
|
||||
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false)
|
||||
.findAll()
|
||||
.isEmpty()
|
||||
}
|
||||
import io.realm.kotlin.createObject
|
||||
|
||||
internal fun ChunkEntity.deleteOnCascade() {
|
||||
assertIsManaged()
|
||||
|
@ -46,11 +38,10 @@ internal fun ChunkEntity.deleteOnCascade() {
|
|||
|
||||
internal fun ChunkEntity.merge(roomId: String,
|
||||
chunkToMerge: ChunkEntity,
|
||||
direction: PaginationDirection) {
|
||||
direction: PaginationDirection): List<TimelineEventEntity> {
|
||||
assertIsManaged()
|
||||
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked()
|
||||
val isCurrentChunkUnlinked = this.isUnlinked()
|
||||
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
|
||||
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked
|
||||
val isCurrentChunkUnlinked = isUnlinked
|
||||
|
||||
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
||||
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
||||
|
@ -65,49 +56,21 @@ internal fun ChunkEntity.merge(roomId: String,
|
|||
this.isLastBackward = chunkToMerge.isLastBackward
|
||||
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
}
|
||||
val events = eventsToMerge.mapNotNull { it.root?.asDomain() }
|
||||
val eventIds = ArrayList<String>()
|
||||
events.forEach { event ->
|
||||
add(roomId, event, direction, isUnlinked = isUnlinked)
|
||||
if (event.eventId != null) {
|
||||
eventIds.add(event.eventId)
|
||||
}
|
||||
}
|
||||
updateSenderDataFor(eventIds)
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.addAll(roomId: String,
|
||||
events: List<Event>,
|
||||
direction: PaginationDirection,
|
||||
stateIndexOffset: Int = 0,
|
||||
// Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk)
|
||||
isUnlinked: Boolean = false) {
|
||||
assertIsManaged()
|
||||
val eventIds = ArrayList<String>()
|
||||
events.forEach { event ->
|
||||
add(roomId, event, direction, stateIndexOffset, isUnlinked)
|
||||
if (event.eventId != null) {
|
||||
eventIds.add(event.eventId)
|
||||
}
|
||||
}
|
||||
updateSenderDataFor(eventIds)
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.updateSenderDataFor(eventIds: List<String>) {
|
||||
for (eventId in eventIds) {
|
||||
val timelineEventEntity = timelineEvents.find(eventId) ?: continue
|
||||
timelineEventEntity.updateSenderData()
|
||||
}
|
||||
return eventsToMerge
|
||||
.mapNotNull {
|
||||
val event = it.root?.asDomain() ?: return@mapNotNull null
|
||||
add(roomId, event, direction)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.add(roomId: String,
|
||||
event: Event,
|
||||
direction: PaginationDirection,
|
||||
stateIndexOffset: Int = 0,
|
||||
isUnlinked: Boolean = false) {
|
||||
stateIndexOffset: Int = 0
|
||||
): TimelineEventEntity? {
|
||||
assertIsManaged()
|
||||
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
||||
if (direction == PaginationDirection.FORWARDS) {
|
||||
|
@ -129,12 +92,15 @@ internal fun ChunkEntity.add(roomId: String,
|
|||
}
|
||||
}
|
||||
|
||||
val isChunkUnlinked = isUnlinked
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val eventId = event.eventId ?: ""
|
||||
val senderId = event.senderId ?: ""
|
||||
|
||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
||||
?: ReadReceiptsSummaryEntity(eventId, roomId)
|
||||
?: realm.createObject<ReadReceiptsSummaryEntity>(eventId).apply {
|
||||
this.roomId = roomId
|
||||
}
|
||||
|
||||
// Update RR for the sender of a new message with a dummy one
|
||||
|
||||
|
@ -151,13 +117,15 @@ internal fun ChunkEntity.add(roomId: String,
|
|||
}
|
||||
}
|
||||
|
||||
val eventEntity = TimelineEventEntity(localId).also {
|
||||
it.root = event.toEntity(roomId).apply {
|
||||
this.stateIndex = currentStateIndex
|
||||
this.isUnlinked = isUnlinked
|
||||
this.displayIndex = currentDisplayIndex
|
||||
this.sendState = SendState.SYNCED
|
||||
}
|
||||
val rootEvent = event.toEntity(roomId).apply {
|
||||
this.stateIndex = currentStateIndex
|
||||
this.displayIndex = currentDisplayIndex
|
||||
this.sendState = SendState.SYNCED
|
||||
this.isUnlinked = isChunkUnlinked
|
||||
}
|
||||
val eventEntity = realm.createObject<TimelineEventEntity>().also {
|
||||
it.localId = localId
|
||||
it.root = realm.copyToRealm(rootEvent)
|
||||
it.eventId = eventId
|
||||
it.roomId = roomId
|
||||
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||
|
@ -165,6 +133,7 @@ internal fun ChunkEntity.add(roomId: String,
|
|||
}
|
||||
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
||||
timelineEvents.add(position, eventEntity)
|
||||
return eventEntity
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
||||
|
|
|
@ -60,7 +60,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
|
|||
this.sendState = SendState.UNSENT
|
||||
}
|
||||
val roomMembers = RoomMembers(realm, roomId)
|
||||
val myUser = roomMembers.get(senderId)
|
||||
val myUser = roomMembers.getLastRoomMember(senderId)
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val timelineEventEntity = TimelineEventEntity(localId).also {
|
||||
it.root = eventEntity
|
||||
|
@ -69,7 +69,6 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
|
|||
it.senderName = myUser?.displayName
|
||||
it.senderAvatar = myUser?.avatarUrl
|
||||
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
|
||||
it.senderMembershipEvent = roomMembers.queryRoomMemberEvent(senderId).findFirst()
|
||||
}
|
||||
sendingTimelineEvents.add(0, timelineEventEntity)
|
||||
}
|
||||
|
|
|
@ -16,74 +16,9 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database.helper
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.internal.database.query.next
|
||||
import im.vector.matrix.android.internal.database.query.prev
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmQuery
|
||||
|
||||
internal fun TimelineEventEntity.updateSenderData() {
|
||||
assertIsManaged()
|
||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return
|
||||
val stateIndex = root?.stateIndex ?: return
|
||||
val senderId = root?.sender ?: return
|
||||
val chunkEntity = chunk?.firstOrNull() ?: return
|
||||
val isUnlinked = chunkEntity.isUnlinked()
|
||||
var senderMembershipEvent: EventEntity?
|
||||
var senderRoomMemberContent: String?
|
||||
var senderRoomMemberPrevContent: String?
|
||||
when {
|
||||
stateIndex <= 0 -> {
|
||||
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
|
||||
senderRoomMemberContent = senderMembershipEvent?.prevContent
|
||||
senderRoomMemberPrevContent = senderMembershipEvent?.content
|
||||
}
|
||||
else -> {
|
||||
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
|
||||
senderRoomMemberContent = senderMembershipEvent?.content
|
||||
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||
}
|
||||
}
|
||||
|
||||
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
|
||||
if (senderMembershipEvent == null) {
|
||||
senderMembershipEvent = roomEntity.untimelinedStateEvents
|
||||
.where()
|
||||
.equalTo(EventEntityFields.STATE_KEY, senderId)
|
||||
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||
.prev(since = stateIndex)
|
||||
senderRoomMemberContent = senderMembershipEvent?.content
|
||||
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||
}
|
||||
|
||||
ContentMapper.map(senderRoomMemberContent).toModel<RoomMember>()?.also {
|
||||
this.senderAvatar = it.avatarUrl
|
||||
this.senderName = it.displayName
|
||||
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
}
|
||||
|
||||
// We try to fallback on prev content if we got a room member state events with null fields
|
||||
if (root?.type == EventType.STATE_ROOM_MEMBER) {
|
||||
ContentMapper.map(senderRoomMemberPrevContent).toModel<RoomMember>()?.also {
|
||||
if (this.senderAvatar == null && it.avatarUrl != null) {
|
||||
this.senderAvatar = it.avatarUrl
|
||||
}
|
||||
if (this.senderName == null && it.displayName != null) {
|
||||
this.senderName = it.displayName
|
||||
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.senderMembershipEvent = senderMembershipEvent
|
||||
}
|
||||
|
||||
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
||||
val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
|
||||
|
@ -93,10 +28,3 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
|||
currentIdNum.toLong() + 1
|
||||
}
|
||||
}
|
||||
|
||||
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
|
||||
return where()
|
||||
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
|
||||
}
|
||||
|
|
|
@ -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.database.helper
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.internal.database.query.next
|
||||
import im.vector.matrix.android.internal.database.query.prev
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmQuery
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This is an internal cache to avoid querying all the time the room member events
|
||||
*/
|
||||
@SessionScope
|
||||
internal class TimelineEventSenderVisitor @Inject constructor() {
|
||||
|
||||
internal data class Key(
|
||||
val roomId: String,
|
||||
val stateIndex: Int,
|
||||
val senderId: String
|
||||
)
|
||||
|
||||
internal class Value(
|
||||
var senderAvatar: String? = null,
|
||||
var senderName: String? = null,
|
||||
var isUniqueDisplayName: Boolean = false,
|
||||
var senderMembershipEventId: String? = null
|
||||
)
|
||||
|
||||
private val values = HashMap<Key, Value>()
|
||||
|
||||
fun clear() {
|
||||
values.clear()
|
||||
}
|
||||
|
||||
fun clear(roomId: String, senderId: String) {
|
||||
val keysToRemove = values.keys.filter { it.senderId == senderId && it.roomId == roomId }
|
||||
keysToRemove.forEach {
|
||||
values.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun visit(timelineEventEntities: List<TimelineEventEntity>) = timelineEventEntities.forEach { visit(it) }
|
||||
|
||||
fun visit(timelineEventEntity: TimelineEventEntity) {
|
||||
if (!timelineEventEntity.isValid) {
|
||||
return
|
||||
}
|
||||
val key = Key(
|
||||
roomId = timelineEventEntity.roomId,
|
||||
stateIndex = timelineEventEntity.root?.stateIndex ?: 0,
|
||||
senderId = timelineEventEntity.root?.sender ?: ""
|
||||
)
|
||||
val result = values.getOrPut(key) {
|
||||
timelineEventEntity.computeValue()
|
||||
}
|
||||
timelineEventEntity.apply {
|
||||
this.isUniqueDisplayName = result.isUniqueDisplayName
|
||||
this.senderAvatar = result.senderAvatar
|
||||
this.senderName = result.senderName
|
||||
this.senderMembershipEventId = result.senderMembershipEventId
|
||||
}
|
||||
}
|
||||
|
||||
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
|
||||
return where()
|
||||
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
|
||||
}
|
||||
|
||||
private fun TimelineEventEntity.computeValue(): Value {
|
||||
assertIsManaged()
|
||||
val result = Value()
|
||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return result
|
||||
val stateIndex = root?.stateIndex ?: return result
|
||||
val senderId = root?.sender ?: return result
|
||||
val chunkEntity = chunk?.firstOrNull() ?: return result
|
||||
val isUnlinked = chunkEntity.isUnlinked
|
||||
var senderMembershipEvent: EventEntity?
|
||||
var senderRoomMemberContent: String?
|
||||
var senderRoomMemberPrevContent: String?
|
||||
|
||||
if (stateIndex <= 0) {
|
||||
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
|
||||
senderRoomMemberContent = senderMembershipEvent?.prevContent
|
||||
senderRoomMemberPrevContent = senderMembershipEvent?.content
|
||||
} else {
|
||||
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
|
||||
senderRoomMemberContent = senderMembershipEvent?.content
|
||||
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||
}
|
||||
|
||||
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
|
||||
if (senderMembershipEvent == null) {
|
||||
senderMembershipEvent = roomEntity.untimelinedStateEvents
|
||||
.where()
|
||||
.equalTo(EventEntityFields.STATE_KEY, senderId)
|
||||
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||
.prev(since = stateIndex)
|
||||
senderRoomMemberContent = senderMembershipEvent?.content
|
||||
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||
}
|
||||
|
||||
ContentMapper.map(senderRoomMemberContent).toModel<RoomMemberContent>()?.also {
|
||||
result.senderAvatar = it.avatarUrl
|
||||
result.senderName = it.displayName
|
||||
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
}
|
||||
// We try to fallback on prev content if we got a room member state events with null fields
|
||||
if (root?.type == EventType.STATE_ROOM_MEMBER) {
|
||||
ContentMapper.map(senderRoomMemberPrevContent).toModel<RoomMemberContent>()?.also {
|
||||
if (result.senderAvatar == null && it.avatarUrl != null) {
|
||||
result.senderAvatar = it.avatarUrl
|
||||
}
|
||||
if (result.senderName == null && it.displayName != null) {
|
||||
result.senderName = it.displayName
|
||||
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
result.senderMembershipEventId = senderMembershipEvent?.eventId
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.database.mapper
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||
|
||||
internal object RoomMemberMapper {
|
||||
|
||||
fun map(roomMemberEntity: RoomMemberEntity): RoomMember {
|
||||
return RoomMember(
|
||||
userId = roomMemberEntity.userId,
|
||||
avatarUrl = roomMemberEntity.avatarUrl,
|
||||
displayName = roomMemberEntity.displayName,
|
||||
membership = roomMemberEntity.membership
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RoomMemberEntity.asDomain(): RoomMember {
|
||||
return RoomMemberMapper.map(this)
|
||||
}
|
|
@ -70,7 +70,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||
readMarkerId = roomSummaryEntity.readMarkerId,
|
||||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
||||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
||||
aliases = roomSummaryEntity.aliases.toList()
|
||||
aliases = roomSummaryEntity.aliases.toList(),
|
||||
isEncrypted = roomSummaryEntity.isEncrypted
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ internal open class ChunkEntity(@Index var prevToken: String? = null,
|
|||
var backwardsDisplayIndex: Int? = null,
|
||||
var forwardsDisplayIndex: Int? = null,
|
||||
var backwardsStateIndex: Int? = null,
|
||||
var forwardsStateIndex: Int? = null
|
||||
var forwardsStateIndex: Int? = null,
|
||||
var isUnlinked: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
fun identifier() = "${prevToken}_$nextToken"
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.database.model
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.Index
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "",
|
||||
@Index var userId: String = "",
|
||||
@Index var roomId: String = "",
|
||||
var displayName: String = "",
|
||||
var avatarUrl: String = "",
|
||||
var reason: String? = null,
|
||||
var isDirect: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
var membership: Membership
|
||||
get() {
|
||||
return Membership.valueOf(membershipStr)
|
||||
}
|
||||
set(value) {
|
||||
membershipStr = value.name
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
|
@ -42,7 +42,9 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
|||
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||
var canonicalAlias: String? = null,
|
||||
var aliases: RealmList<String> = RealmList(),
|
||||
var flatAliases: String = ""
|
||||
// this is required for querying
|
||||
var flatAliases: String = "",
|
||||
var isEncrypted: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
|
|
|
@ -50,6 +50,7 @@ import io.realm.annotations.RealmModule
|
|||
ReadMarkerEntity::class,
|
||||
UserDraftsEntity::class,
|
||||
DraftEntity::class,
|
||||
HomeServerCapabilitiesEntity::class
|
||||
HomeServerCapabilitiesEntity::class,
|
||||
RoomMemberEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -29,7 +29,7 @@ internal open class TimelineEventEntity(var localId: Long = 0,
|
|||
var senderName: String? = null,
|
||||
var isUniqueDisplayName: Boolean = false,
|
||||
var senderAvatar: String? = null,
|
||||
var senderMembershipEvent: EventEntity? = null,
|
||||
var senderMembershipEventId: String? = null,
|
||||
var readReceipts: ReadReceiptsSummaryEntity? = null
|
||||
) : RealmObject() {
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue