mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +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>signin</w>
|
||||||
<w>signout</w>
|
<w>signout</w>
|
||||||
<w>signup</w>
|
<w>signup</w>
|
||||||
|
<w>threepid</w>
|
||||||
</words>
|
</words>
|
||||||
</dictionary>
|
</dictionary>
|
||||||
</component>
|
</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 ✨:
|
Features ✨:
|
||||||
-
|
-
|
||||||
|
|
||||||
Improvements 🙌:
|
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:
|
Other changes:
|
||||||
-
|
-
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Fix avatar image disappearing (#777)
|
-
|
||||||
- Fix read marker banner when permalink
|
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
@ -22,6 +19,32 @@ Translations 🗣:
|
||||||
Build 🧱:
|
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)
|
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 ✨:
|
Features ✨:
|
||||||
|
|
|
@ -17,13 +17,16 @@
|
||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
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.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
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.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
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.send.UserDraft
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
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.Optional
|
||||||
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
@ -31,18 +34,22 @@ class RxRoom(private val room: Room) {
|
||||||
|
|
||||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||||
return room.getRoomSummaryLive().asObservable()
|
return room.getRoomSummaryLive().asObservable()
|
||||||
|
.startWith(room.roomSummary().toOptional())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMember>> {
|
||||||
return room.getRoomMemberIdsLive().asObservable()
|
return room.getRoomMembersLive(queryParams).asObservable()
|
||||||
|
.startWith(room.getRoomMembers(queryParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
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>> {
|
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
||||||
return room.getTimeLineEventLive(eventId).asObservable()
|
return room.getTimeLineEventLive(eventId).asObservable()
|
||||||
|
.startWith(room.getTimeLineEvent(eventId).toOptional())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveReadMarker(): Observable<Optional<String>> {
|
fun liveReadMarker(): Observable<Optional<String>> {
|
||||||
|
|
|
@ -18,8 +18,10 @@ package im.vector.matrix.rx
|
||||||
|
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
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.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
|
@ -30,40 +32,43 @@ import io.reactivex.Single
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
return session.liveRoomSummaries().asObservable()
|
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||||
|
.startWith(session.getRoomSummaries(queryParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||||
return session.liveGroupSummaries().asObservable()
|
return session.getGroupSummariesLive(queryParams).asObservable()
|
||||||
|
.startWith(session.getGroupSummaries(queryParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||||
return session.liveBreadcrumbs().asObservable()
|
return session.getBreadcrumbsLive().asObservable()
|
||||||
|
.startWith(session.getBreadcrumbs())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
return session.syncState().asObservable()
|
return session.getSyncStateLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePushers(): Observable<List<Pusher>> {
|
fun livePushers(): Observable<List<Pusher>> {
|
||||||
return session.livePushers().asObservable()
|
return session.getPushersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||||
return session.liveUser(userId).asObservable().distinctUntilChanged()
|
return session.getUserLive(userId).asObservable().distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveUsers(): Observable<List<User>> {
|
fun liveUsers(): Observable<List<User>> {
|
||||||
return session.liveUsers().asObservable()
|
return session.getUsersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveIgnoredUsers(): Observable<List<User>> {
|
fun liveIgnoredUsers(): Observable<List<User>> {
|
||||||
return session.liveIgnoredUsers().asObservable()
|
return session.getIgnoredUsersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
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 {
|
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||||
|
|
|
@ -10,7 +10,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
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 "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.1.0-beta05"
|
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||||
|
@ -119,14 +118,14 @@ dependencies {
|
||||||
implementation "ru.noties.markwon:core:$markwon_version"
|
implementation "ru.noties.markwon:core:$markwon_version"
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
implementation 'androidx.exifinterface:exifinterface:1.1.0'
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:2.3.0-alpha01"
|
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
||||||
|
|
||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
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 androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
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.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
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.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import org.amshove.kluent.shouldBeFalse
|
|
||||||
import org.amshove.kluent.shouldBeTrue
|
import org.amshove.kluent.shouldBeTrue
|
||||||
import org.amshove.kluent.shouldEqual
|
import org.amshove.kluent.shouldEqual
|
||||||
import org.junit.Before
|
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
|
@Test
|
||||||
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
|
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
|
@ -177,8 +155,8 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
val chunk2: ChunkEntity = realm.createObject()
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
val prevToken = "prev_token"
|
val prevToken = "prev_token"
|
||||||
chunk1.prevToken = prevToken
|
chunk1.prevToken = prevToken
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
||||||
chunk1.prevToken shouldEqual prevToken
|
chunk1.prevToken shouldEqual prevToken
|
||||||
}
|
}
|
||||||
|
@ -191,10 +169,19 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
val chunk2: ChunkEntity = realm.createObject()
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
val nextToken = "next_token"
|
val nextToken = "next_token"
|
||||||
chunk1.nextToken = nextToken
|
chunk1.nextToken = nextToken
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||||
chunk1.nextToken shouldEqual nextToken
|
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
|
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.Content
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
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.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.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
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.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
|
import kotlin.random.Random
|
||||||
|
|
||||||
object RoomDataHelper {
|
object RoomDataHelper {
|
||||||
|
@ -73,19 +66,4 @@ object RoomDataHelper {
|
||||||
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||||
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
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.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.util.md5
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This data class hold credentials user data.
|
* 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
|
// Optional data that may contain info to override home server and/or identity server
|
||||||
@Json(name = "well_known") val wellKnown: WellKnown? = null
|
@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)
|
?.chunked(4)
|
||||||
?.joinToString(separator = " ")
|
?.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
|
val myUserId: String
|
||||||
get() = sessionParams.credentials.userId
|
get() = sessionParams.credentials.userId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sessionId
|
||||||
|
*/
|
||||||
|
val sessionId: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method allow to open a session. It does start some service on the background.
|
* 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.
|
* This method allows to listen the sync state.
|
||||||
* @return a [LiveData] of [SyncState].
|
* @return a [LiveData] of [SyncState].
|
||||||
*/
|
*/
|
||||||
fun syncState(): LiveData<SyncState>
|
fun getSyncStateLive(): LiveData<SyncState>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This methods return true if an initial sync has been processed
|
* 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.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
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.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.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
|
||||||
|
@ -89,6 +90,8 @@ interface CryptoService {
|
||||||
|
|
||||||
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||||
|
|
||||||
|
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||||
|
|
||||||
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
||||||
|
|
||||||
fun isRoomEncrypted(roomId: String): Boolean
|
fun isRoomEncrypted(roomId: String): Boolean
|
||||||
|
|
|
@ -25,7 +25,6 @@ object EventType {
|
||||||
const val MESSAGE = "m.room.message"
|
const val MESSAGE = "m.room.message"
|
||||||
const val STICKER = "m.sticker"
|
const val STICKER = "m.sticker"
|
||||||
const val ENCRYPTED = "m.room.encrypted"
|
const val ENCRYPTED = "m.room.encrypted"
|
||||||
const val ENCRYPTION = "m.room.encryption"
|
|
||||||
const val FEEDBACK = "m.room.message.feedback"
|
const val FEEDBACK = "m.room.message.feedback"
|
||||||
const val TYPING = "m.typing"
|
const val TYPING = "m.typing"
|
||||||
const val REDACTION = "m.room.redaction"
|
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_HISTORY_VISIBILITY = "m.room.history_visibility"
|
||||||
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
|
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
|
||||||
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
|
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
|
||||||
|
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
||||||
|
|
||||||
// Call Events
|
// Call Events
|
||||||
|
|
||||||
|
|
|
@ -38,9 +38,15 @@ interface GroupService {
|
||||||
*/
|
*/
|
||||||
fun getGroupSummary(groupId: String): GroupSummary?
|
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.
|
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
|
||||||
* @return the [LiveData] of [GroupSummary]
|
* @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"
|
const val EVENT_ID_ONLY = "event_id_only"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePushers(): LiveData<List<Pusher>>
|
fun getPushersLive(): LiveData<List<Pusher>>
|
||||||
|
|
||||||
fun pushers() : List<Pusher>
|
fun pushers() : List<Pusher>
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,5 +56,8 @@ interface Room :
|
||||||
*/
|
*/
|
||||||
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
|
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A current snapshot of [RoomSummary] associated with the room
|
||||||
|
*/
|
||||||
fun roomSummary(): RoomSummary?
|
fun roomSummary(): RoomSummary?
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,16 +60,28 @@ interface RoomService {
|
||||||
fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
|
fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
* Get a snapshot list of room summaries.
|
||||||
* @return the [LiveData] of [RoomSummary]
|
* @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
|
* Get a live list of Breadcrumbs
|
||||||
* @return the [LiveData] of [RoomSummary]
|
* @return the [LiveData] of [RoomSummary]
|
||||||
*/
|
*/
|
||||||
fun liveBreadcrumbs(): LiveData<List<RoomSummary>>
|
fun getBreadcrumbsLive(): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inform the Matrix SDK that a room is displayed.
|
* 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
|
package im.vector.matrix.android.api.session.room.crypto
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
|
||||||
interface RoomCryptoService {
|
interface RoomCryptoService {
|
||||||
|
|
||||||
fun isEncrypted(): Boolean
|
fun isEncrypted(): Boolean
|
||||||
|
@ -23,4 +25,6 @@ interface RoomCryptoService {
|
||||||
fun encryptionAlgorithm(): String?
|
fun encryptionAlgorithm(): String?
|
||||||
|
|
||||||
fun shouldEncryptForInvitedMembers(): Boolean
|
fun shouldEncryptForInvitedMembers(): Boolean
|
||||||
|
|
||||||
|
fun enableEncryptionWithAlgorithm(algorithm: String, callback: MatrixCallback<Unit>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,11 +41,18 @@ interface MembershipService {
|
||||||
fun getRoomMember(userId: String): RoomMember?
|
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.
|
* @return a [LiveData] of roomMember list.
|
||||||
*/
|
*/
|
||||||
fun getRoomMemberIdsLive(): LiveData<List<String>>
|
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>>
|
||||||
|
|
||||||
fun getNumberOfJoinedMembers(): Int
|
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 {
|
fun isLeft(): Boolean {
|
||||||
return this == KNOCK || this == LEAVE || this == BAN
|
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
|
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(
|
data class RoomMember(
|
||||||
@Json(name = "membership") val membership: Membership,
|
val membership: Membership,
|
||||||
@Json(name = "reason") val reason: String? = null,
|
val userId: String,
|
||||||
@Json(name = "displayname") val displayName: String? = null,
|
val displayName: String? = null,
|
||||||
@Json(name = "avatar_url") val avatarUrl: String? = null,
|
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() }
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 membership: Membership = Membership.NONE,
|
||||||
val versioningState: VersioningState = VersioningState.NONE,
|
val versioningState: VersioningState = VersioningState.NONE,
|
||||||
val readMarkerId: String? = null,
|
val readMarkerId: String? = null,
|
||||||
val userDrafts: List<UserDraft> = emptyList()
|
val userDrafts: List<UserDraft> = emptyList(),
|
||||||
|
var isEncrypted: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val isVersioned: Boolean
|
val isVersioned: Boolean
|
||||||
|
|
|
@ -125,7 +125,7 @@ class CreateRoomParams {
|
||||||
val contentMap = HashMap<String, String>()
|
val contentMap = HashMap<String, String>()
|
||||||
contentMap["algorithm"] = algorithm
|
contentMap["algorithm"] = algorithm
|
||||||
|
|
||||||
val algoEvent = Event(type = EventType.ENCRYPTION,
|
val algoEvent = Event(type = EventType.STATE_ROOM_ENCRYPTION,
|
||||||
stateKey = "",
|
stateKey = "",
|
||||||
content = contentMap.toContent()
|
content = contentMap.toContent()
|
||||||
)
|
)
|
||||||
|
|
|
@ -108,5 +108,17 @@ interface RelationService {
|
||||||
replyText: CharSequence,
|
replyText: CharSequence,
|
||||||
autoMarkdown: Boolean = false): Cancelable?
|
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>)
|
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable encryption of the room
|
||||||
|
*/
|
||||||
|
fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun getStateEvent(eventType: String): Event?
|
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
|
* 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
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,25 +50,25 @@ interface UserService {
|
||||||
* @param userId the userId to look for.
|
* @param userId the userId to look for.
|
||||||
* @return a LiveData of user with userId
|
* @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
|
* Observe a live list of users sorted alphabetically
|
||||||
* @return a Livedata of users
|
* @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.
|
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
||||||
* @param filter the filter. It will look into userId and displayName.
|
* @param filter the filter. It will look into userId and displayName.
|
||||||
* @return a Livedata of users
|
* @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
|
* Get list of ignored users
|
||||||
*/
|
*/
|
||||||
fun liveIgnoredUsers(): LiveData<List<User>>
|
fun getIgnoredUsersLive(): LiveData<List<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ignore users
|
* Ignore users
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.util
|
||||||
|
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
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.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
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.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||||
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||||
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, 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
|
package im.vector.matrix.android.internal
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
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.api.session.Session
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.di.MatrixComponent
|
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,
|
internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent,
|
||||||
private val sessionParamsStore: SessionParamsStore) {
|
private val sessionParamsStore: SessionParamsStore) {
|
||||||
|
|
||||||
|
// SessionId -> SessionComponent
|
||||||
private val sessionComponents = HashMap<String, SessionComponent>()
|
private val sessionComponents = HashMap<String, SessionComponent>()
|
||||||
|
|
||||||
fun getSessionComponent(userId: String): SessionComponent? {
|
fun getSessionComponent(sessionId: String): SessionComponent? {
|
||||||
val sessionParams = sessionParamsStore.get(userId) ?: return null
|
val sessionParams = sessionParamsStore.get(sessionId) ?: return null
|
||||||
return getOrCreateSessionComponent(sessionParams)
|
return getOrCreateSessionComponent(sessionParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,17 +42,17 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
|
||||||
return getOrCreateSessionComponent(sessionParams).session()
|
return getOrCreateSessionComponent(sessionParams).session()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun releaseSession(userId: String) {
|
fun releaseSession(sessionId: String) {
|
||||||
if (sessionComponents.containsKey(userId).not()) {
|
if (sessionComponents.containsKey(sessionId).not()) {
|
||||||
throw RuntimeException("You don't have a session for the user $userId")
|
throw RuntimeException("You don't have a session for id $sessionId")
|
||||||
}
|
}
|
||||||
sessionComponents.remove(userId)?.also {
|
sessionComponents.remove(sessionId)?.also {
|
||||||
it.session().close()
|
it.session().close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
||||||
return sessionComponents.getOrPut(sessionParams.credentials.userId) {
|
return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) {
|
||||||
DaggerSessionComponent
|
DaggerSessionComponent
|
||||||
.factory()
|
.factory()
|
||||||
.create(matrixComponent, sessionParams)
|
.create(matrixComponent, sessionParams)
|
||||||
|
|
|
@ -45,14 +45,15 @@ import okhttp3.OkHttpClient
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
internal class DefaultAuthenticationService @Inject constructor(
|
||||||
private val okHttpClient: Lazy<OkHttpClient>,
|
@Unauthenticated
|
||||||
private val retrofitFactory: RetrofitFactory,
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val retrofitFactory: RetrofitFactory,
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val sessionManager: SessionManager,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val sessionCreator: SessionCreator,
|
private val sessionManager: SessionManager,
|
||||||
private val pendingSessionStore: PendingSessionStore
|
private val sessionCreator: SessionCreator,
|
||||||
|
private val pendingSessionStore: PendingSessionStore
|
||||||
) : AuthenticationService {
|
) : AuthenticationService {
|
||||||
|
|
||||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||||
|
@ -112,7 +113,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
||||||
|
|
||||||
// First check the homeserver version
|
// First check the homeserver version
|
||||||
runCatching {
|
runCatching {
|
||||||
executeRequest<Versions> {
|
executeRequest<Versions>(null) {
|
||||||
apiCall = authAPI.versions()
|
apiCall = authAPI.versions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +142,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
||||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
// Ok, try to get the config.json file of a RiotWeb client
|
// Ok, try to get the config.json file of a RiotWeb client
|
||||||
val riotConfig = executeRequest<RiotConfig> {
|
val riotConfig = executeRequest<RiotConfig>(null) {
|
||||||
apiCall = authAPI.getRiotConfig()
|
apiCall = authAPI.getRiotConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +154,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
||||||
|
|
||||||
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||||
|
|
||||||
val versions = executeRequest<Versions> {
|
val versions = executeRequest<Versions>(null) {
|
||||||
apiCall = newAuthAPI.versions()
|
apiCall = newAuthAPI.versions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +168,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
||||||
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
||||||
return if (versions.isSupportedBySdk()) {
|
return if (versions.isSupportedBySdk()) {
|
||||||
// Get the login flow
|
// Get the login flow
|
||||||
val loginFlowResponse = executeRequest<LoginFlowResponse> {
|
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
|
||||||
apiCall = authAPI.getLoginFlows()
|
apiCall = authAPI.getLoginFlows()
|
||||||
}
|
}
|
||||||
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
|
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
|
||||||
|
|
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
|
||||||
internal interface SessionParamsStore {
|
internal interface SessionParamsStore {
|
||||||
|
|
||||||
fun get(userId: String): SessionParams?
|
fun get(sessionId: String): SessionParams?
|
||||||
|
|
||||||
fun getLast(): SessionParams?
|
fun getLast(): SessionParams?
|
||||||
|
|
||||||
|
@ -29,11 +29,11 @@ internal interface SessionParamsStore {
|
||||||
|
|
||||||
suspend fun save(sessionParams: SessionParams)
|
suspend fun save(sessionParams: SessionParams)
|
||||||
|
|
||||||
suspend fun setTokenInvalid(userId: String)
|
suspend fun setTokenInvalid(sessionId: String)
|
||||||
|
|
||||||
suspend fun updateCredentials(newCredentials: Credentials)
|
suspend fun updateCredentials(newCredentials: Credentials)
|
||||||
|
|
||||||
suspend fun delete(userId: String)
|
suspend fun delete(sessionId: String)
|
||||||
|
|
||||||
suspend fun deleteAll()
|
suspend fun deleteAll()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
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.DynamicRealm
|
||||||
import io.realm.RealmMigration
|
import io.realm.RealmMigration
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -23,35 +26,59 @@ import timber.log.Timber
|
||||||
internal object AuthRealmMigration : RealmMigration {
|
internal object AuthRealmMigration : RealmMigration {
|
||||||
|
|
||||||
// Current schema version
|
// Current schema version
|
||||||
const val SCHEMA_VERSION = 2L
|
const val SCHEMA_VERSION = 3L
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
||||||
|
|
||||||
if (oldVersion <= 0) {
|
if (oldVersion <= 0) migrateTo1(realm)
|
||||||
Timber.d("Step 0 -> 1")
|
if (oldVersion <= 1) migrateTo2(realm)
|
||||||
Timber.d("Create PendingSessionEntity")
|
if (oldVersion <= 2) migrateTo3(realm)
|
||||||
|
}
|
||||||
|
|
||||||
realm.schema.create("PendingSessionEntity")
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
|
Timber.d("Step 0 -> 1")
|
||||||
.setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
|
Timber.d("Create PendingSessionEntity")
|
||||||
.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion <= 1) {
|
realm.schema.create("PendingSessionEntity")
|
||||||
Timber.d("Step 1 -> 2")
|
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
|
||||||
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
|
.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")
|
private fun migrateTo2(realm: DynamicRealm) {
|
||||||
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
|
Timber.d("Step 1 -> 2")
|
||||||
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
|
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.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
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.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.database.awaitTransaction
|
import im.vector.matrix.android.internal.database.awaitTransaction
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
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 ->
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
realm
|
realm
|
||||||
.where(SessionParamsEntity::class.java)
|
.where(SessionParamsEntity::class.java)
|
||||||
.equalTo(SessionParamsEntityFields.USER_ID, userId)
|
.equalTo(SessionParamsEntityFields.SESSION_ID, sessionId)
|
||||||
.findAll()
|
.findAll()
|
||||||
.map { mapper.map(it) }
|
.map { mapper.map(it) }
|
||||||
.firstOrNull()
|
.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 ->
|
awaitTransaction(realmConfiguration) { realm ->
|
||||||
val currentSessionParams = realm
|
val currentSessionParams = realm
|
||||||
.where(SessionParamsEntity::class.java)
|
.where(SessionParamsEntity::class.java)
|
||||||
.equalTo(SessionParamsEntityFields.USER_ID, userId)
|
.equalTo(SessionParamsEntityFields.SESSION_ID, sessionId)
|
||||||
.findAll()
|
.findAll()
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
|
||||||
if (currentSessionParams == null) {
|
if (currentSessionParams == null) {
|
||||||
// Should not happen
|
// Should not happen
|
||||||
"Session param not found for user $userId"
|
"Session param not found for id $sessionId"
|
||||||
.let { Timber.w(it) }
|
.let { Timber.w(it) }
|
||||||
.also { error(it) }
|
.also { error(it) }
|
||||||
} else {
|
} else {
|
||||||
|
@ -99,14 +100,14 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
||||||
awaitTransaction(realmConfiguration) { realm ->
|
awaitTransaction(realmConfiguration) { realm ->
|
||||||
val currentSessionParams = realm
|
val currentSessionParams = realm
|
||||||
.where(SessionParamsEntity::class.java)
|
.where(SessionParamsEntity::class.java)
|
||||||
.equalTo(SessionParamsEntityFields.USER_ID, newCredentials.userId)
|
.equalTo(SessionParamsEntityFields.SESSION_ID, newCredentials.sessionId())
|
||||||
.findAll()
|
.findAll()
|
||||||
.map { mapper.map(it) }
|
.map { mapper.map(it) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
|
||||||
if (currentSessionParams == null) {
|
if (currentSessionParams == null) {
|
||||||
// Should not happen
|
// Should not happen
|
||||||
"Session param not found for user ${newCredentials.userId}"
|
"Session param not found for id ${newCredentials.sessionId()}"
|
||||||
.let { Timber.w(it) }
|
.let { Timber.w(it) }
|
||||||
.also { error(it) }
|
.also { error(it) }
|
||||||
} else {
|
} 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) {
|
awaitTransaction(realmConfiguration) {
|
||||||
it.where(SessionParamsEntity::class.java)
|
it.where(SessionParamsEntity::class.java)
|
||||||
.equalTo(SessionParamsEntityFields.USER_ID, userId)
|
.equalTo(SessionParamsEntityFields.SESSION_ID, sessionId)
|
||||||
.findAll()
|
.findAll()
|
||||||
.deleteAllFromRealm()
|
.deleteAllFromRealm()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
internal open class SessionParamsEntity(
|
internal open class SessionParamsEntity(
|
||||||
@PrimaryKey var userId: String = "",
|
@PrimaryKey var sessionId: String = "",
|
||||||
|
var userId: String = "",
|
||||||
var credentialsJson: String = "",
|
var credentialsJson: String = "",
|
||||||
var homeServerConnectionConfigJson: String = "",
|
var homeServerConnectionConfigJson: String = "",
|
||||||
// Set to false when the token is invalid and the user has been soft logged out
|
// 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.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
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.SessionParams
|
||||||
|
import im.vector.matrix.android.api.auth.data.sessionId
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||||
|
@ -49,6 +50,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return SessionParamsEntity(
|
return SessionParamsEntity(
|
||||||
|
sessionParams.credentials.sessionId(),
|
||||||
sessionParams.credentials.userId,
|
sessionParams.credentials.userId,
|
||||||
credentialsJson,
|
credentialsJson,
|
||||||
homeServerConnectionConfigJson,
|
homeServerConnectionConfigJson,
|
||||||
|
|
|
@ -72,7 +72,7 @@ internal class DefaultLoginWizard(
|
||||||
} else {
|
} else {
|
||||||
PasswordLoginParams.userIdentifier(login, password, deviceName)
|
PasswordLoginParams.userIdentifier(login, password, deviceName)
|
||||||
}
|
}
|
||||||
val credentials = executeRequest<Credentials> {
|
val credentials = executeRequest<Credentials>(null) {
|
||||||
apiCall = authAPI.login(loginParams)
|
apiCall = authAPI.login(loginParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ internal class DefaultLoginWizard(
|
||||||
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
|
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
|
||||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||||
|
|
||||||
val result = executeRequest<AddThreePidRegistrationResponse> {
|
val result = executeRequest<AddThreePidRegistrationResponse>(null) {
|
||||||
apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
|
apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ internal class DefaultLoginWizard(
|
||||||
resetPasswordData.newPassword
|
resetPasswordData.newPassword
|
||||||
)
|
)
|
||||||
|
|
||||||
executeRequest<Unit> {
|
executeRequest<Unit>(null) {
|
||||||
apiCall = authAPI.resetPasswordMailConfirmed(param)
|
apiCall = authAPI.resetPasswordMailConfirmed(param)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,12 @@ internal interface RegisterAddThreePidTask : Task<RegisterAddThreePidTask.Params
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultRegisterAddThreePidTask(private val authAPI: AuthAPI)
|
internal class DefaultRegisterAddThreePidTask(
|
||||||
: RegisterAddThreePidTask {
|
private val authAPI: AuthAPI
|
||||||
|
) : RegisterAddThreePidTask {
|
||||||
|
|
||||||
override suspend fun execute(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationResponse {
|
override suspend fun execute(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationResponse {
|
||||||
return executeRequest {
|
return executeRequest(null) {
|
||||||
apiCall = authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params))
|
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)
|
internal class DefaultRegisterTask(
|
||||||
: RegisterTask {
|
private val authAPI: AuthAPI
|
||||||
|
) : RegisterTask {
|
||||||
|
|
||||||
override suspend fun execute(params: RegisterTask.Params): Credentials {
|
override suspend fun execute(params: RegisterTask.Params): Credentials {
|
||||||
try {
|
try {
|
||||||
return executeRequest {
|
return executeRequest(null) {
|
||||||
apiCall = authAPI.register(params.registrationParams)
|
apiCall = authAPI.register(params.registrationParams)
|
||||||
}
|
}
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
|
|
|
@ -27,11 +27,12 @@ internal interface ValidateCodeTask : Task<ValidateCodeTask.Params, SuccessResul
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultValidateCodeTask(private val authAPI: AuthAPI)
|
internal class DefaultValidateCodeTask(
|
||||||
: ValidateCodeTask {
|
private val authAPI: AuthAPI
|
||||||
|
) : ValidateCodeTask {
|
||||||
|
|
||||||
override suspend fun execute(params: ValidateCodeTask.Params): SuccessResult {
|
override suspend fun execute(params: ValidateCodeTask.Params): SuccessResult {
|
||||||
return executeRequest {
|
return executeRequest(null) {
|
||||||
apiCall = authAPI.validate3Pid(params.url, params.body)
|
apiCall = authAPI.validate3Pid(params.url, params.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ internal abstract class CryptoModule {
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
companion object {
|
companion object {
|
||||||
internal const val DB_ALIAS_PREFIX = "crypto_module_"
|
internal fun getKeyAlias(userMd5: String) = "crypto_module_$userMd5"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -59,7 +59,7 @@ internal abstract class CryptoModule {
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
|
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
|
||||||
}
|
}
|
||||||
.name("crypto_store.realm")
|
.name("crypto_store.realm")
|
||||||
.modules(RealmCryptoStoreModule())
|
.modules(RealmCryptoStoreModule())
|
||||||
|
@ -123,6 +123,9 @@ internal abstract class CryptoModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
|
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask
|
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.MXEncryptEventContentResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
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.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.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
@ -127,6 +128,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
||||||
// Tasks
|
// Tasks
|
||||||
private val getDevicesTask: GetDevicesTask,
|
private val getDevicesTask: GetDevicesTask,
|
||||||
|
private val getDeviceInfoTask: GetDeviceInfoTask,
|
||||||
private val setDeviceNameTask: SetDeviceNameTask,
|
private val setDeviceNameTask: SetDeviceNameTask,
|
||||||
private val uploadKeysTask: UploadKeysTask,
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
|
@ -149,7 +151,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
|
|
||||||
fun onStateEvent(roomId: String, event: Event) {
|
fun onStateEvent(roomId: String, event: Event) {
|
||||||
when {
|
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_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(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) {
|
fun onLiveEvent(roomId: String, event: Event) {
|
||||||
when {
|
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_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
}
|
}
|
||||||
|
@ -203,6 +205,14 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.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 {
|
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||||
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||||
}
|
}
|
||||||
|
@ -476,7 +486,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
*/
|
*/
|
||||||
override fun isRoomEncrypted(roomId: String): Boolean {
|
override fun isRoomEncrypted(roomId: String): Boolean {
|
||||||
val encryptionEvent = monarchy.fetchCopied {
|
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
|
return encryptionEvent != null
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,10 @@ import im.vector.matrix.android.internal.di.UserId
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore,
|
internal class SetDeviceVerificationAction @Inject constructor(
|
||||||
@UserId private val userId: String,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val keysBackup: KeysBackup) {
|
@UserId private val userId: String,
|
||||||
|
private val keysBackup: KeysBackup) {
|
||||||
|
|
||||||
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
|
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
|
||||||
val device = cryptoStore.getUserDevice(deviceId, userId)
|
val device = cryptoStore.getUserDevice(deviceId, userId)
|
||||||
|
|
|
@ -25,11 +25,18 @@ internal interface CryptoApi {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the devices list
|
* 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")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices")
|
||||||
fun getDevices(): Call<DevicesListResponse>
|
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.
|
* 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
|
* 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
|
// Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver
|
||||||
checkAndStartKeysBackup()
|
checkAndStartKeysBackup()
|
||||||
}
|
}
|
||||||
else ->
|
else ->
|
||||||
// Come back to the ready state so that we will retry on the next received key
|
// Come back to the ready state so that we will retry on the next received key
|
||||||
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
|
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
|
||||||
}
|
}
|
||||||
|
@ -1339,6 +1339,31 @@ internal class KeysBackup @Inject constructor(
|
||||||
return sessionBackupData
|
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 {
|
companion object {
|
||||||
// Maximum delay in ms in {@link maybeBackupKeys}
|
// Maximum delay in ms in {@link maybeBackupKeys}
|
||||||
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L
|
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.crypto.keysbackup.model.rest.KeysVersion
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface CreateKeysBackupVersionTask : Task<CreateKeysBackupVersionBody, KeysVersion>
|
internal interface CreateKeysBackupVersionTask : Task<CreateKeysBackupVersionBody, KeysVersion>
|
||||||
|
|
||||||
internal class DefaultCreateKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
internal class DefaultCreateKeysBackupVersionTask @Inject constructor(
|
||||||
: CreateKeysBackupVersionTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : CreateKeysBackupVersionTask {
|
||||||
|
|
||||||
override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion {
|
override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.createKeysBackupVersion(params)
|
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.crypto.keysbackup.api.RoomKeysApi
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
|
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)
|
internal class DefaultDeleteBackupTask @Inject constructor(
|
||||||
: DeleteBackupTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : DeleteBackupTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeleteBackupTask.Params) {
|
override suspend fun execute(params: DeleteBackupTask.Params) {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.deleteBackup(params.version)
|
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.crypto.keysbackup.api.RoomKeysApi
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Params, Unit> {
|
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)
|
internal class DefaultDeleteRoomSessionDataTask @Inject constructor(
|
||||||
: DeleteRoomSessionDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : DeleteRoomSessionDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeleteRoomSessionDataTask.Params) {
|
override suspend fun execute(params: DeleteRoomSessionDataTask.Params) {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.deleteRoomSessionData(
|
apiCall = roomKeysApi.deleteRoomSessionData(
|
||||||
params.roomId,
|
params.roomId,
|
||||||
params.sessionId,
|
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.crypto.keysbackup.api.RoomKeysApi
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.Params, Unit> {
|
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)
|
internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(
|
||||||
: DeleteRoomSessionsDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : DeleteRoomSessionsDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) {
|
override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.deleteRoomSessionsData(
|
apiCall = roomKeysApi.deleteRoomSessionsData(
|
||||||
params.roomId,
|
params.roomId,
|
||||||
params.version)
|
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.crypto.keysbackup.api.RoomKeysApi
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params, Unit> {
|
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)
|
internal class DefaultDeleteSessionsDataTask @Inject constructor(
|
||||||
: DeleteSessionsDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : DeleteSessionsDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeleteSessionsDataTask.Params) {
|
override suspend fun execute(params: DeleteSessionsDataTask.Params) {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.deleteSessionsData(params.version)
|
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.crypto.keysbackup.model.rest.KeysVersionResult
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetKeysBackupLastVersionTask : Task<Unit, KeysVersionResult>
|
internal interface GetKeysBackupLastVersionTask : Task<Unit, KeysVersionResult>
|
||||||
|
|
||||||
internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(
|
||||||
: GetKeysBackupLastVersionTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : GetKeysBackupLastVersionTask {
|
||||||
|
|
||||||
override suspend fun execute(params: Unit): KeysVersionResult {
|
override suspend fun execute(params: Unit): KeysVersionResult {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.getKeysBackupLastVersion()
|
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.crypto.keysbackup.model.rest.KeysVersionResult
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetKeysBackupVersionTask : Task<String, KeysVersionResult>
|
internal interface GetKeysBackupVersionTask : Task<String, KeysVersionResult>
|
||||||
|
|
||||||
internal class DefaultGetKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
|
internal class DefaultGetKeysBackupVersionTask @Inject constructor(
|
||||||
: GetKeysBackupVersionTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : GetKeysBackupVersionTask {
|
||||||
|
|
||||||
override suspend fun execute(params: String): KeysVersionResult {
|
override suspend fun execute(params: String): KeysVersionResult {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.getKeysBackupVersion(params)
|
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.crypto.keysbackup.model.rest.KeyBackupData
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params, KeyBackupData> {
|
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)
|
internal class DefaultGetRoomSessionDataTask @Inject constructor(
|
||||||
: GetRoomSessionDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : GetRoomSessionDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData {
|
override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.getRoomSessionData(
|
apiCall = roomKeysApi.getRoomSessionData(
|
||||||
params.roomId,
|
params.roomId,
|
||||||
params.sessionId,
|
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.crypto.keysbackup.model.rest.RoomKeysBackupData
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params, RoomKeysBackupData> {
|
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)
|
internal class DefaultGetRoomSessionsDataTask @Inject constructor(
|
||||||
: GetRoomSessionsDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : GetRoomSessionsDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData {
|
override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.getRoomSessionsData(
|
apiCall = roomKeysApi.getRoomSessionsData(
|
||||||
params.roomId,
|
params.roomId,
|
||||||
params.version)
|
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.crypto.keysbackup.model.rest.KeysBackupData
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBackupData> {
|
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)
|
internal class DefaultGetSessionsDataTask @Inject constructor(
|
||||||
: GetSessionsDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : GetSessionsDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData {
|
override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.getSessionsData(params.version)
|
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.crypto.keysbackup.model.rest.KeyBackupData
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Params, BackupKeysResult> {
|
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)
|
internal class DefaultStoreRoomSessionDataTask @Inject constructor(
|
||||||
: StoreRoomSessionDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : StoreRoomSessionDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult {
|
override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.storeRoomSessionData(
|
apiCall = roomKeysApi.storeRoomSessionData(
|
||||||
params.roomId,
|
params.roomId,
|
||||||
params.sessionId,
|
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.crypto.keysbackup.model.rest.RoomKeysBackupData
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Params, BackupKeysResult> {
|
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)
|
internal class DefaultStoreRoomSessionsDataTask @Inject constructor(
|
||||||
: StoreRoomSessionsDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : StoreRoomSessionsDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult {
|
override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.storeRoomSessionsData(
|
apiCall = roomKeysApi.storeRoomSessionsData(
|
||||||
params.roomId,
|
params.roomId,
|
||||||
params.version,
|
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.crypto.keysbackup.model.rest.KeysBackupData
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, BackupKeysResult> {
|
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)
|
internal class DefaultStoreSessionsDataTask @Inject constructor(
|
||||||
: StoreSessionsDataTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : StoreSessionsDataTask {
|
||||||
|
|
||||||
override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult {
|
override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.storeSessionsData(
|
apiCall = roomKeysApi.storeSessionsData(
|
||||||
params.version,
|
params.version,
|
||||||
params.keysBackupData)
|
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.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface UpdateKeysBackupVersionTask : Task<UpdateKeysBackupVersionTask.Params, Unit> {
|
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)
|
internal class DefaultUpdateKeysBackupVersionTask @Inject constructor(
|
||||||
: UpdateKeysBackupVersionTask {
|
private val roomKeysApi: RoomKeysApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : UpdateKeysBackupVersionTask {
|
||||||
|
|
||||||
override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) {
|
override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody)
|
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.crypto.model.rest.KeysClaimResponse
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -33,13 +34,15 @@ internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysFor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private val cryptoApi: CryptoApi)
|
internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(
|
||||||
: ClaimOneTimeKeysForUsersDeviceTask {
|
private val cryptoApi: CryptoApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : ClaimOneTimeKeysForUsersDeviceTask {
|
||||||
|
|
||||||
override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap<MXKey> {
|
override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap<MXKey> {
|
||||||
val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)
|
val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)
|
||||||
|
|
||||||
val keysClaimResponse = executeRequest<KeysClaimResponse> {
|
val keysClaimResponse = executeRequest<KeysClaimResponse>(eventBus) {
|
||||||
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
|
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
|
||||||
}
|
}
|
||||||
val map = MXUsersDevicesMap<MXKey>()
|
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.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
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)
|
internal class DefaultDeleteDeviceTask @Inject constructor(
|
||||||
: DeleteDeviceTask {
|
private val cryptoApi: CryptoApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : DeleteDeviceTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeleteDeviceTask.Params) {
|
override suspend fun execute(params: DeleteDeviceTask.Params) {
|
||||||
try {
|
try {
|
||||||
executeRequest<Unit> {
|
executeRequest<Unit>(eventBus) {
|
||||||
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
|
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
|
||||||
}
|
}
|
||||||
} catch (throwable: Throwable) {
|
} 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.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserPasswordTask.Params, Unit> {
|
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,
|
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(
|
||||||
@UserId private val userId: String)
|
private val cryptoApi: CryptoApi,
|
||||||
: DeleteDeviceWithUserPasswordTask {
|
@UserId private val userId: String,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : DeleteDeviceWithUserPasswordTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
|
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()
|
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()
|
||||||
.apply {
|
.apply {
|
||||||
deleteDeviceAuth = DeleteDeviceAuth()
|
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.crypto.model.rest.KeysQueryResponse
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
|
internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
|
||||||
|
@ -31,8 +32,10 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
|
||||||
val token: String?)
|
val token: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultDownloadKeysForUsers @Inject constructor(private val cryptoApi: CryptoApi)
|
internal class DefaultDownloadKeysForUsers @Inject constructor(
|
||||||
: DownloadKeysForUsersTask {
|
private val cryptoApi: CryptoApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : DownloadKeysForUsersTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
|
override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
|
||||||
val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.orEmpty()
|
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
|
body.token = params.token
|
||||||
}
|
}
|
||||||
|
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = cryptoApi.downloadKeysForUsers(body)
|
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.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetDevicesTask : Task<Unit, DevicesListResponse>
|
internal interface GetDevicesTask : Task<Unit, DevicesListResponse>
|
||||||
|
|
||||||
internal class DefaultGetDevicesTask @Inject constructor(private val cryptoApi: CryptoApi)
|
internal class DefaultGetDevicesTask @Inject constructor(
|
||||||
: GetDevicesTask {
|
private val cryptoApi: CryptoApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : GetDevicesTask {
|
||||||
|
|
||||||
override suspend fun execute(params: Unit): DevicesListResponse {
|
override suspend fun execute(params: Unit): DevicesListResponse {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = cryptoApi.getDevices()
|
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.crypto.model.rest.KeyChangesResponse
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetKeyChangesTask : Task<GetKeyChangesTask.Params, KeyChangesResponse> {
|
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)
|
internal class DefaultGetKeyChangesTask @Inject constructor(
|
||||||
: GetKeyChangesTask {
|
private val cryptoApi: CryptoApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : GetKeyChangesTask {
|
||||||
|
|
||||||
override suspend fun execute(params: GetKeyChangesTask.Params): KeyChangesResponse {
|
override suspend fun execute(params: GetKeyChangesTask.Params): KeyChangesResponse {
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = cryptoApi.getKeyChanges(params.from, params.to)
|
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.crypto.model.rest.SendToDeviceBody
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@ -35,14 +36,16 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi: CryptoApi)
|
internal class DefaultSendToDeviceTask @Inject constructor(
|
||||||
: SendToDeviceTask {
|
private val cryptoApi: CryptoApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : SendToDeviceTask {
|
||||||
|
|
||||||
override suspend fun execute(params: SendToDeviceTask.Params) {
|
override suspend fun execute(params: SendToDeviceTask.Params) {
|
||||||
val sendToDeviceBody = SendToDeviceBody()
|
val sendToDeviceBody = SendToDeviceBody()
|
||||||
sendToDeviceBody.messages = params.contentMap.map
|
sendToDeviceBody.messages = params.contentMap.map
|
||||||
|
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = cryptoApi.sendToDevice(
|
apiCall = cryptoApi.sendToDevice(
|
||||||
params.eventType,
|
params.eventType,
|
||||||
params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
|
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.crypto.model.rest.UpdateDeviceInfoBody
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface SetDeviceNameTask : Task<SetDeviceNameTask.Params, Unit> {
|
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)
|
internal class DefaultSetDeviceNameTask @Inject constructor(
|
||||||
: SetDeviceNameTask {
|
private val cryptoApi: CryptoApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : SetDeviceNameTask {
|
||||||
|
|
||||||
override suspend fun execute(params: SetDeviceNameTask.Params) {
|
override suspend fun execute(params: SetDeviceNameTask.Params) {
|
||||||
val body = UpdateDeviceInfoBody(
|
val body = UpdateDeviceInfoBody(
|
||||||
displayName = params.deviceName
|
displayName = params.deviceName
|
||||||
)
|
)
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body)
|
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.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.convertToUTF8
|
import im.vector.matrix.android.internal.util.convertToUTF8
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadResponse> {
|
internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadResponse> {
|
||||||
|
@ -36,8 +37,10 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
|
||||||
val deviceId: String)
|
val deviceId: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi: CryptoApi)
|
internal class DefaultUploadKeysTask @Inject constructor(
|
||||||
: UploadKeysTask {
|
private val cryptoApi: CryptoApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : UploadKeysTask {
|
||||||
|
|
||||||
override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
|
override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
|
||||||
val encodedDeviceId = convertToUTF8(params.deviceId)
|
val encodedDeviceId = convertToUTF8(params.deviceId)
|
||||||
|
@ -52,7 +55,7 @@ internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi:
|
||||||
body.oneTimeKeys = params.oneTimeKeys
|
body.oneTimeKeys = params.oneTimeKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
return executeRequest {
|
return executeRequest(eventBus) {
|
||||||
apiCall = if (encodedDeviceId.isBlank()) {
|
apiCall = if (encodedDeviceId.isBlank()) {
|
||||||
cryptoApi.uploadKeys(body)
|
cryptoApi.uploadKeys(body)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -16,43 +16,36 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database
|
package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
|
||||||
import io.realm.*
|
import io.realm.*
|
||||||
import java.util.concurrent.CountDownLatch
|
import kotlinx.coroutines.*
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
|
|
||||||
class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConfiguration,
|
internal suspend fun <T> awaitNotEmptyResult(realmConfiguration: RealmConfiguration,
|
||||||
private val realmQueryBuilder: (Realm) -> RealmQuery<E>) {
|
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 {
|
Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val QUERY_LATCH_HANDLER = createBackgroundHandler("REALM_QUERY_LATCH")
|
val result = builder(realm).findAllAsync()
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(InterruptedException::class)
|
val listener = object : RealmChangeListener<RealmResults<T>> {
|
||||||
fun await(timeout: Long, timeUnit: TimeUnit) {
|
override fun onChange(it: RealmResults<T>) {
|
||||||
val realmRef = AtomicReference<Realm>()
|
if (it.isNotEmpty()) {
|
||||||
val latch = CountDownLatch(1)
|
result.removeChangeListener(this)
|
||||||
QUERY_LATCH_HANDLER.post {
|
latch.complete(Unit)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
result.addChangeListener(listener)
|
||||||
try {
|
try {
|
||||||
latch.await(timeout, timeUnit)
|
latch.await()
|
||||||
} catch (exception: InterruptedException) {
|
} catch (e: CancellationException) {
|
||||||
throw exception
|
result.removeChangeListener(listener)
|
||||||
} finally {
|
throw e
|
||||||
QUERY_LATCH_HANDLER.post {
|
}
|
||||||
realmRef.getAndSet(null).close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
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.UserCacheDirectory
|
||||||
import im.vector.matrix.android.internal.di.UserMd5
|
import im.vector.matrix.android.internal.di.UserMd5
|
||||||
import im.vector.matrix.android.internal.session.SessionModule
|
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,
|
internal class SessionRealmConfigurationFactory @Inject constructor(private val realmKeysUtils: RealmKeysUtils,
|
||||||
@UserCacheDirectory val directory: File,
|
@UserCacheDirectory val directory: File,
|
||||||
|
@SessionId val sessionId: String,
|
||||||
@UserMd5 val userMd5: String,
|
@UserMd5 val userMd5: String,
|
||||||
context: Context) {
|
context: Context) {
|
||||||
|
|
||||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
fun create(): RealmConfiguration {
|
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) {
|
if (shouldClearRealm) {
|
||||||
Timber.v("************************************************************")
|
Timber.v("************************************************************")
|
||||||
Timber.v("The realm file session was corrupted and couldn't be loaded.")
|
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
|
sharedPreferences
|
||||||
.edit()
|
.edit()
|
||||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", true)
|
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
val realmConfiguration = RealmConfiguration.Builder()
|
val realmConfiguration = RealmConfiguration.Builder()
|
||||||
|
.compactOnLaunch()
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
.name(REALM_NAME)
|
.name(REALM_NAME)
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, "${SessionModule.DB_ALIAS_PREFIX}$userMd5")
|
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||||
}
|
}
|
||||||
.modules(SessionRealmModule())
|
.modules(SessionRealmModule())
|
||||||
.deleteRealmIfMigrationNeeded()
|
.deleteRealmIfMigrationNeeded()
|
||||||
|
@ -71,7 +74,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val
|
||||||
Timber.v("Successfully create realm instance")
|
Timber.v("Successfully create realm instance")
|
||||||
sharedPreferences
|
sharedPreferences
|
||||||
.edit()
|
.edit()
|
||||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", false)
|
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
return realmConfiguration
|
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.extensions.assertIsManaged
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun ChunkEntity.deleteOnCascade() {
|
internal fun ChunkEntity.deleteOnCascade() {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
|
@ -46,11 +38,10 @@ internal fun ChunkEntity.deleteOnCascade() {
|
||||||
|
|
||||||
internal fun ChunkEntity.merge(roomId: String,
|
internal fun ChunkEntity.merge(roomId: String,
|
||||||
chunkToMerge: ChunkEntity,
|
chunkToMerge: ChunkEntity,
|
||||||
direction: PaginationDirection) {
|
direction: PaginationDirection): List<TimelineEventEntity> {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked()
|
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked
|
||||||
val isCurrentChunkUnlinked = this.isUnlinked()
|
val isCurrentChunkUnlinked = isUnlinked
|
||||||
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
|
|
||||||
|
|
||||||
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
||||||
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
||||||
|
@ -65,49 +56,21 @@ internal fun ChunkEntity.merge(roomId: String,
|
||||||
this.isLastBackward = chunkToMerge.isLastBackward
|
this.isLastBackward = chunkToMerge.isLastBackward
|
||||||
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
}
|
}
|
||||||
val events = eventsToMerge.mapNotNull { it.root?.asDomain() }
|
return eventsToMerge
|
||||||
val eventIds = ArrayList<String>()
|
.mapNotNull {
|
||||||
events.forEach { event ->
|
val event = it.root?.asDomain() ?: return@mapNotNull null
|
||||||
add(roomId, event, direction, isUnlinked = isUnlinked)
|
add(roomId, event, direction)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.add(roomId: String,
|
internal fun ChunkEntity.add(roomId: String,
|
||||||
event: Event,
|
event: Event,
|
||||||
direction: PaginationDirection,
|
direction: PaginationDirection,
|
||||||
stateIndexOffset: Int = 0,
|
stateIndexOffset: Int = 0
|
||||||
isUnlinked: Boolean = false) {
|
): TimelineEventEntity? {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
|
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
||||||
if (direction == PaginationDirection.FORWARDS) {
|
if (direction == PaginationDirection.FORWARDS) {
|
||||||
|
@ -129,12 +92,15 @@ internal fun ChunkEntity.add(roomId: String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isChunkUnlinked = isUnlinked
|
||||||
val localId = TimelineEventEntity.nextId(realm)
|
val localId = TimelineEventEntity.nextId(realm)
|
||||||
val eventId = event.eventId ?: ""
|
val eventId = event.eventId ?: ""
|
||||||
val senderId = event.senderId ?: ""
|
val senderId = event.senderId ?: ""
|
||||||
|
|
||||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
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
|
// 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 {
|
val rootEvent = event.toEntity(roomId).apply {
|
||||||
it.root = event.toEntity(roomId).apply {
|
this.stateIndex = currentStateIndex
|
||||||
this.stateIndex = currentStateIndex
|
this.displayIndex = currentDisplayIndex
|
||||||
this.isUnlinked = isUnlinked
|
this.sendState = SendState.SYNCED
|
||||||
this.displayIndex = currentDisplayIndex
|
this.isUnlinked = isChunkUnlinked
|
||||||
this.sendState = SendState.SYNCED
|
}
|
||||||
}
|
val eventEntity = realm.createObject<TimelineEventEntity>().also {
|
||||||
|
it.localId = localId
|
||||||
|
it.root = realm.copyToRealm(rootEvent)
|
||||||
it.eventId = eventId
|
it.eventId = eventId
|
||||||
it.roomId = roomId
|
it.roomId = roomId
|
||||||
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
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
|
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
||||||
timelineEvents.add(position, eventEntity)
|
timelineEvents.add(position, eventEntity)
|
||||||
|
return eventEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
||||||
|
|
|
@ -60,7 +60,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
|
||||||
this.sendState = SendState.UNSENT
|
this.sendState = SendState.UNSENT
|
||||||
}
|
}
|
||||||
val roomMembers = RoomMembers(realm, roomId)
|
val roomMembers = RoomMembers(realm, roomId)
|
||||||
val myUser = roomMembers.get(senderId)
|
val myUser = roomMembers.getLastRoomMember(senderId)
|
||||||
val localId = TimelineEventEntity.nextId(realm)
|
val localId = TimelineEventEntity.nextId(realm)
|
||||||
val timelineEventEntity = TimelineEventEntity(localId).also {
|
val timelineEventEntity = TimelineEventEntity(localId).also {
|
||||||
it.root = eventEntity
|
it.root = eventEntity
|
||||||
|
@ -69,7 +69,6 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
|
||||||
it.senderName = myUser?.displayName
|
it.senderName = myUser?.displayName
|
||||||
it.senderAvatar = myUser?.avatarUrl
|
it.senderAvatar = myUser?.avatarUrl
|
||||||
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
|
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
|
||||||
it.senderMembershipEvent = roomMembers.queryRoomMemberEvent(senderId).findFirst()
|
|
||||||
}
|
}
|
||||||
sendingTimelineEvents.add(0, timelineEventEntity)
|
sendingTimelineEvents.add(0, timelineEventEntity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,74 +16,9 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database.helper
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||||
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 io.realm.Realm
|
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 {
|
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
||||||
val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
|
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
|
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,
|
readMarkerId = roomSummaryEntity.readMarkerId,
|
||||||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
||||||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
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 backwardsDisplayIndex: Int? = null,
|
||||||
var forwardsDisplayIndex: Int? = null,
|
var forwardsDisplayIndex: Int? = null,
|
||||||
var backwardsStateIndex: Int? = null,
|
var backwardsStateIndex: Int? = null,
|
||||||
var forwardsStateIndex: Int? = null
|
var forwardsStateIndex: Int? = null,
|
||||||
|
var isUnlinked: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
fun identifier() = "${prevToken}_$nextToken"
|
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 breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||||
var canonicalAlias: String? = null,
|
var canonicalAlias: String? = null,
|
||||||
var aliases: RealmList<String> = RealmList(),
|
var aliases: RealmList<String> = RealmList(),
|
||||||
var flatAliases: String = ""
|
// this is required for querying
|
||||||
|
var flatAliases: String = "",
|
||||||
|
var isEncrypted: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
|
|
@ -50,6 +50,7 @@ import io.realm.annotations.RealmModule
|
||||||
ReadMarkerEntity::class,
|
ReadMarkerEntity::class,
|
||||||
UserDraftsEntity::class,
|
UserDraftsEntity::class,
|
||||||
DraftEntity::class,
|
DraftEntity::class,
|
||||||
HomeServerCapabilitiesEntity::class
|
HomeServerCapabilitiesEntity::class,
|
||||||
|
RoomMemberEntity::class
|
||||||
])
|
])
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
|
|
@ -29,7 +29,7 @@ internal open class TimelineEventEntity(var localId: Long = 0,
|
||||||
var senderName: String? = null,
|
var senderName: String? = null,
|
||||||
var isUniqueDisplayName: Boolean = false,
|
var isUniqueDisplayName: Boolean = false,
|
||||||
var senderAvatar: String? = null,
|
var senderAvatar: String? = null,
|
||||||
var senderMembershipEvent: EventEntity? = null,
|
var senderMembershipEventId: String? = null,
|
||||||
var readReceipts: ReadReceiptsSummaryEntity? = null
|
var readReceipts: ReadReceiptsSummaryEntity? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue