mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-01-11 02:37:36 +03:00
Merge branch 'release/0.15.0'
This commit is contained in:
commit
c498416075
273 changed files with 6899 additions and 3374 deletions
|
@ -86,3 +86,10 @@ steps:
|
|||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "openjdk"
|
||||
|
||||
# Check that indonesians files are identical.
|
||||
# Due to Android issue, the resource folder must be values-in/, and Weblate export data into values-id/.
|
||||
# If this step fails, it means that Weblate has updated the file in value-id/ so to fix it, copy the file to values-in/
|
||||
- label: "Indonesian"
|
||||
command:
|
||||
- "diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml"
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -4,6 +4,7 @@
|
|||
# idea files: exclude everything except dictionnaries
|
||||
.idea/caches
|
||||
.idea/libraries
|
||||
.idea/inspectionProfiles
|
||||
.idea/*.xml
|
||||
.DS_Store
|
||||
/build
|
||||
|
@ -13,7 +14,3 @@
|
|||
/tmp
|
||||
|
||||
ktlint
|
||||
.idea/copyright/New_vector.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
|
||||
.idea/copyright/New_Vector_Ltd.xml
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
|
|
6
.idea/copyright/NewVector.xml
Normal file
6
.idea/copyright/NewVector.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright (c) &#36;today.year 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." />
|
||||
<option name="myName" value="NewVector" />
|
||||
</copyright>
|
||||
</component>
|
8
.idea/copyright/profiles_settings.xml
Normal file
8
.idea/copyright/profiles_settings.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<component name="CopyrightManager">
|
||||
<settings default="NewVector">
|
||||
<LanguageOptions name="XML">
|
||||
<option name="fileTypeOverride" value="1" />
|
||||
<option name="prefixLines" value="false" />
|
||||
</LanguageOptions>
|
||||
</settings>
|
||||
</component>
|
14
CHANGES.md
14
CHANGES.md
|
@ -1,3 +1,17 @@
|
|||
Changes in RiotX 0.15.0 (2020-02-10)
|
||||
===================================================
|
||||
|
||||
Improvements 🙌:
|
||||
- Improve navigation to the timeline (#789, #862)
|
||||
- Improve network detection. It is now based on the sync request status (#873, #882)
|
||||
|
||||
Other changes:
|
||||
- Support SSO login with Firefox account (#606)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Ask for permission before opening the camera (#934)
|
||||
- Encrypt for invited users by default, if the room state allows it (#803)
|
||||
|
||||
Changes in RiotX 0.14.3 (2020-02-03)
|
||||
===================================================
|
||||
|
||||
|
|
12
build.gradle
12
build.gradle
|
@ -47,23 +47,11 @@ allprojects {
|
|||
jcenter()
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).all {
|
||||
options.compilerArgs += [
|
||||
'-Adagger.gradle.incremental=enabled'
|
||||
]
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
// Warnings are potential errors, so stop ignoring them
|
||||
kotlinOptions.allWarningsAsErrors = true
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
extensions.findByName("kapt")?.arguments {
|
||||
arg("dagger.gradle.incremental", "enabled")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.Observer
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.MainThreadDisposable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
private class LiveDataObservable<T>(
|
||||
|
@ -60,3 +61,11 @@ private class LiveDataObservable<T>(
|
|||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
internal fun <T> Observable<T>.startWithCallable(supplier: () -> T): Observable<T> {
|
||||
val startObservable = Observable
|
||||
.fromCallable(supplier)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
return startWith(startObservable)
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
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.room.Room
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
|
@ -30,114 +28,43 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
|
|||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.BiFunction
|
||||
import timber.log.Timber
|
||||
|
||||
class RxRoom(private val room: Room, private val session: Session) {
|
||||
class RxRoom(private val room: Room) {
|
||||
|
||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||
val summaryObservable = room.getRoomSummaryLive()
|
||||
return room.getRoomSummaryLive()
|
||||
.asObservable()
|
||||
.startWith(room.roomSummary().toOptional())
|
||||
.doOnNext { Timber.v("RX: summary emitted for: ${it.getOrNull()?.roomId}") }
|
||||
|
||||
val memberIdsChangeObservable = summaryObservable
|
||||
.map {
|
||||
it.getOrNull()?.let { roomSummary ->
|
||||
if (roomSummary.isEncrypted) {
|
||||
// Return the list of other users
|
||||
roomSummary.otherMemberIds + listOf(session.myUserId)
|
||||
} else {
|
||||
// Return an empty list, the room is not encrypted
|
||||
emptyList()
|
||||
}
|
||||
}.orEmpty()
|
||||
}.distinctUntilChanged()
|
||||
.doOnNext { Timber.v("RX: memberIds emitted. Size: ${it.size}") }
|
||||
|
||||
// Observe the device info of the users in the room
|
||||
val cryptoDeviceInfoObservable = memberIdsChangeObservable
|
||||
.switchMap { membersIds ->
|
||||
session.getLiveCryptoDeviceInfo(membersIds)
|
||||
.asObservable()
|
||||
.map {
|
||||
// If any key change, emit the userIds list
|
||||
membersIds
|
||||
}
|
||||
.startWith(membersIds)
|
||||
.doOnNext { Timber.v("RX: CryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||
}
|
||||
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted 2. Size: ${it.size}") }
|
||||
|
||||
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
|
||||
.map { userIds ->
|
||||
if (userIds.isEmpty()) {
|
||||
Optional<RoomEncryptionTrustLevel>(null)
|
||||
} else {
|
||||
session.getCrossSigningService().getTrustLevelForUsers(userIds).toOptional()
|
||||
}
|
||||
}
|
||||
.doOnNext { Timber.v("RX: roomEncryptionTrustLevel emitted: ${it.getOrNull()?.name}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
|
||||
summaryObservable,
|
||||
roomEncryptionTrustLevelObservable,
|
||||
BiFunction { summary, level ->
|
||||
summary.getOrNull()?.copy(
|
||||
roomEncryptionTrustLevel = level.getOrNull()
|
||||
).toOptional()
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.v("RX: final room summary emitted for ${it.getOrNull()?.roomId}") }
|
||||
.startWithCallable { room.roomSummary().toOptional() }
|
||||
}
|
||||
|
||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
|
||||
.startWith(room.getRoomMembers(queryParams))
|
||||
.doOnNext { Timber.v("RX: room members emitted. Size: ${it.size}") }
|
||||
|
||||
// TODO Do it only for room members of the room (switchMap)
|
||||
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||
.startWith(emptyList<CryptoDeviceInfo>())
|
||||
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<List<RoomMemberSummary>, List<CryptoDeviceInfo>, List<RoomMemberSummary>>(
|
||||
roomMembersObservable,
|
||||
cryptoDeviceInfoObservable,
|
||||
BiFunction { summaries, _ ->
|
||||
summaries.map {
|
||||
if (room.isEncrypted()) {
|
||||
it.copy(
|
||||
// Get the trust level of a virtual room with only this user
|
||||
userEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(listOf(it.userId))
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.v("RX: final room members emitted. Size: ${it.size}") }
|
||||
return room.getRoomMembersLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
room.getRoomMembers(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
||||
.startWith(room.getEventAnnotationsSummary(eventId).toOptional())
|
||||
.startWithCallable {
|
||||
room.getEventAnnotationsSummary(eventId).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
||||
return room.getTimeLineEventLive(eventId).asObservable()
|
||||
.startWith(room.getTimeLineEvent(eventId).toOptional())
|
||||
.startWithCallable {
|
||||
room.getTimeLineEvent(eventId).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveStateEvent(eventType: String): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType).asObservable()
|
||||
.startWith(room.getStateEvent(eventType).toOptional())
|
||||
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||
.startWithCallable {
|
||||
room.getStateEvent(eventType, stateKey).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveReadMarker(): Observable<Optional<String>> {
|
||||
|
@ -170,6 +97,6 @@ class RxRoom(private val room: Room, private val session: Session) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Room.rx(session: Session): RxRoom {
|
||||
return RxRoom(this, session)
|
||||
fun Room.rx(): RxRoom {
|
||||
return RxRoom(this)
|
||||
}
|
||||
|
|
|
@ -33,48 +33,28 @@ import im.vector.matrix.android.api.util.toOptional
|
|||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.BiFunction
|
||||
import timber.log.Timber
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
|
||||
.startWith(session.getRoomSummaries(queryParams))
|
||||
.doOnNext { Timber.v("RX: summaries emitted: size: ${it.size}") }
|
||||
|
||||
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||
.startWith(emptyList<CryptoDeviceInfo>())
|
||||
.doOnNext { Timber.v("RX: crypto device info emitted: size: ${it.size}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
|
||||
summariesObservable,
|
||||
cryptoDeviceInfoObservable,
|
||||
BiFunction { summaries, _ ->
|
||||
summaries.map {
|
||||
if (it.isEncrypted) {
|
||||
it.copy(
|
||||
roomEncryptionTrustLevel = session.getCrossSigningService()
|
||||
.getTrustLevelForUsers(it.otherMemberIds + session.myUserId)
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.d("RX: final summaries emitted: size: ${it.size}") }
|
||||
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
session.getRoomSummaries(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||
return session.getGroupSummariesLive(queryParams).asObservable()
|
||||
.startWith(session.getGroupSummaries(queryParams))
|
||||
.startWithCallable {
|
||||
session.getGroupSummaries(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||
return session.getBreadcrumbsLive().asObservable()
|
||||
.startWith(session.getBreadcrumbs())
|
||||
.startWithCallable {
|
||||
session.getBreadcrumbs()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveSyncState(): Observable<SyncState> {
|
||||
|
@ -87,7 +67,9 @@ class RxSession(private val session: Session) {
|
|||
|
||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||
return session.getUserLive(userId).asObservable()
|
||||
.startWith(session.getUser(userId).toOptional())
|
||||
.startWithCallable {
|
||||
session.getUser(userId).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveUsers(): Observable<List<User>> {
|
||||
|
@ -128,12 +110,16 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
|
||||
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
|
||||
return session.getLiveCryptoDeviceInfo(userId).asObservable()
|
||||
return session.getLiveCryptoDeviceInfo(userId).asObservable().startWithCallable {
|
||||
session.getCryptoDeviceInfo(userId)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
|
||||
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable()
|
||||
.startWith(session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional())
|
||||
.startWithCallable {
|
||||
session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,10 +92,11 @@ dependencies {
|
|||
|
||||
def arrow_version = "0.8.2"
|
||||
def moshi_version = '1.8.0'
|
||||
def lifecycle_version = '2.1.0'
|
||||
def lifecycle_version = '2.2.0'
|
||||
def arch_version = '2.1.0'
|
||||
def coroutines_version = "1.3.2"
|
||||
def markwon_version = '3.1.0'
|
||||
def daggerVersion = '2.24'
|
||||
def daggerVersion = '2.25.4'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
|
@ -104,14 +105,13 @@ dependencies {
|
|||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
// Network
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.6.2'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
|
||||
implementation 'com.novoda:merlin:1.2.0'
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||
|
||||
|
@ -125,7 +125,7 @@ dependencies {
|
|||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.0"
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
|
@ -167,7 +167,7 @@ dependencies {
|
|||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
|
||||
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
|
||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
}
|
||||
|
|
|
@ -117,6 +117,10 @@ class CommonTestHelper(context: Context) {
|
|||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// TODO Count only new messages?
|
||||
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {
|
||||
|
|
|
@ -74,7 +74,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
|||
val room = aliceSession.getRoom(roomId!!)!!
|
||||
|
||||
val lock2 = CountDownLatch(1)
|
||||
room.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM, object : TestMatrixCallback<Unit>(lock2) {})
|
||||
room.enableEncryption(callback = TestMatrixCallback(lock2))
|
||||
mTestHelper.await(lock2)
|
||||
|
||||
return CryptoTestData(aliceSession, roomId!!)
|
||||
|
@ -241,6 +241,11 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
|||
|
||||
val bobEventsListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
|
|
|
@ -20,15 +20,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
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.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.database.helper.addTimelineEvent
|
||||
import im.vector.matrix.android.internal.database.helper.merge
|
||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeRoomMemberEvent
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.kotlin.createObject
|
||||
|
@ -58,8 +58,11 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeMessageEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
|
||||
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
|
||||
realm.copyToRealmOrUpdate(it)
|
||||
}
|
||||
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
|
||||
chunk.timelineEvents.size shouldEqual 1
|
||||
}
|
||||
}
|
||||
|
@ -68,65 +71,23 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeMessageEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
|
||||
realm.copyToRealmOrUpdate(it)
|
||||
}
|
||||
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
|
||||
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
|
||||
chunk.timelineEvents.size shouldEqual 1
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeRoomMemberEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeMessageEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addAll_shouldStateIndexIncremented_whenStateEventsAreAddedForward() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvents = createFakeListOfEvents(30)
|
||||
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
|
||||
chunk.addAll("roomId", fakeEvents, PaginationDirection.FORWARDS)
|
||||
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual numberOfStateEvents
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addAll_shouldStateIndexDecremented_whenStateEventsAreAddedBackward() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvents = createFakeListOfEvents(30)
|
||||
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
|
||||
val lastIsState = fakeEvents.last().isStateEvent()
|
||||
val expectedStateIndex = if (lastIsState) -numberOfStateEvents + 1 else -numberOfStateEvents
|
||||
chunk.addAll("roomId", fakeEvents, PaginationDirection.BACKWARDS)
|
||||
chunk.lastStateIndex(PaginationDirection.BACKWARDS) shouldEqual expectedStateIndex
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_shouldAddEvents_whenMergingBackward() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk1: ChunkEntity = realm.createObject()
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.timelineEvents.size shouldEqual 60
|
||||
}
|
||||
}
|
||||
|
@ -140,9 +101,9 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
|
||||
chunk1.isLastForward = true
|
||||
chunk2.isLastForward = false
|
||||
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
|
||||
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS)
|
||||
chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.timelineEvents.size shouldEqual 40
|
||||
chunk1.isLastForward.shouldBeTrue()
|
||||
}
|
||||
|
@ -155,9 +116,9 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||
val chunk2: ChunkEntity = realm.createObject()
|
||||
val prevToken = "prev_token"
|
||||
chunk1.prevToken = prevToken
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
||||
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS)
|
||||
chunk1.prevToken shouldEqual prevToken
|
||||
}
|
||||
}
|
||||
|
@ -169,19 +130,25 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||
val chunk2: ChunkEntity = realm.createObject()
|
||||
val nextToken = "next_token"
|
||||
chunk1.nextToken = nextToken
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.nextToken shouldEqual nextToken
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChunkEntity.addAll(roomId: String,
|
||||
events: List<Event>,
|
||||
direction: PaginationDirection,
|
||||
stateIndexOffset: Int = 0) {
|
||||
direction: PaginationDirection) {
|
||||
events.forEach { event ->
|
||||
add(roomId, event, direction, stateIndexOffset)
|
||||
val fakeEvent = event.toEntity(roomId, SendState.SYNCED).let {
|
||||
realm.copyToRealmOrUpdate(it)
|
||||
}
|
||||
addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ROOM_ID = "roomId"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.work.WorkManager
|
|||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.api.crypto.MXCryptoConfig
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
|
@ -35,7 +36,8 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||
import javax.inject.Inject
|
||||
|
||||
data class MatrixConfiguration(
|
||||
val applicationFlavor: String = "Default-application-flavor"
|
||||
val applicationFlavor: String = "Default-application-flavor",
|
||||
val cryptoConfig: MXCryptoConfig = MXCryptoConfig()
|
||||
) {
|
||||
|
||||
interface Provider {
|
||||
|
@ -57,12 +59,11 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
|
||||
init {
|
||||
Monarchy.init(context)
|
||||
DaggerMatrixComponent.factory().create(context).inject(this)
|
||||
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
||||
if (context.applicationContext !is Configuration.Provider) {
|
||||
WorkManager.initialize(context, Configuration.Builder().build())
|
||||
}
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor)
|
||||
}
|
||||
|
||||
fun getUserAgent() = userAgentHolder.userAgent
|
||||
|
|
|
@ -38,3 +38,8 @@ interface MatrixCallback<in T> {
|
|||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic no op implementation
|
||||
*/
|
||||
class NoOpMatrixCallback<T>: MatrixCallback<T>
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
package im.vector.matrix.android.api.crypto
|
||||
|
||||
/**
|
||||
* Class to define the parameters used to customize or configure the end-to-end crypto.
|
||||
*/
|
||||
data class MXCryptoConfig(
|
||||
// Tell whether the encryption of the event content is enabled for the invited members.
|
||||
// By default, we encrypt messages only for the joined members.
|
||||
// The encryption for the invited members will be blocked if the history visibility is "joined".
|
||||
var enableEncryptionForInvitedMembers: Boolean = false
|
||||
// SDK clients can disable this by settings it to false.
|
||||
// Note that the encryption for the invited members will be blocked if the history visibility is "joined".
|
||||
var enableEncryptionForInvitedMembers: Boolean = true
|
||||
)
|
|
@ -15,30 +15,32 @@
|
|||
*/
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
abstract class Condition(val kind: Kind) {
|
||||
|
||||
enum class Kind(val value: String) {
|
||||
event_match("event_match"),
|
||||
contains_display_name("contains_display_name"),
|
||||
room_member_count("room_member_count"),
|
||||
sender_notification_permission("sender_notification_permission"),
|
||||
UNRECOGNIZE("");
|
||||
EventMatch("event_match"),
|
||||
ContainsDisplayName("contains_display_name"),
|
||||
RoomMemberCount("room_member_count"),
|
||||
SenderNotificationPermission("sender_notification_permission"),
|
||||
Unrecognised("");
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromString(value: String): Kind {
|
||||
return when (value) {
|
||||
"event_match" -> event_match
|
||||
"contains_display_name" -> contains_display_name
|
||||
"room_member_count" -> room_member_count
|
||||
"sender_notification_permission" -> sender_notification_permission
|
||||
else -> UNRECOGNIZE
|
||||
"event_match" -> EventMatch
|
||||
"contains_display_name" -> ContainsDisplayName
|
||||
"room_member_count" -> RoomMemberCount
|
||||
"sender_notification_permission" -> SenderNotificationPermission
|
||||
else -> Unrecognised
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean
|
||||
abstract fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean
|
||||
|
||||
open fun technicalDescription(): String {
|
||||
return "Kind: $kind"
|
||||
|
|
|
@ -15,14 +15,22 @@
|
|||
*/
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
/**
|
||||
* Acts like a visitor on Conditions.
|
||||
* This class as all required context needed to evaluate rules
|
||||
*/
|
||||
interface ConditionResolver {
|
||||
fun resolveEventMatchCondition(event: Event,
|
||||
condition: EventMatchCondition): Boolean
|
||||
|
||||
fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean
|
||||
fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean
|
||||
fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean
|
||||
fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean
|
||||
fun resolveRoomMemberCountCondition(event: Event,
|
||||
condition: RoomMemberCountCondition): Boolean
|
||||
|
||||
fun resolveSenderNotificationPermissionCondition(event: Event,
|
||||
condition: SenderNotificationPermissionCondition): Boolean
|
||||
|
||||
fun resolveContainsDisplayNameCondition(event: Event,
|
||||
condition: ContainsDisplayNameCondition): Boolean
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import timber.log.Timber
|
||||
|
||||
class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
class ContainsDisplayNameCondition : Condition(Kind.ContainsDisplayName) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveContainsDisplayNameCondition(this)
|
||||
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveContainsDisplayNameCondition(event, this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
|
|
|
@ -19,10 +19,20 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import timber.log.Timber
|
||||
|
||||
class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind.event_match) {
|
||||
class EventMatchCondition(
|
||||
/**
|
||||
* The dot-separated field of the event to match, e.g. content.body
|
||||
*/
|
||||
val key: String,
|
||||
/**
|
||||
* The glob-style pattern to match against. Patterns with no special glob characters should
|
||||
* be treated as having asterisks prepended and appended when testing the condition.
|
||||
*/
|
||||
val pattern: String
|
||||
) : Condition(Kind.EventMatch) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver) : Boolean {
|
||||
return conditionResolver.resolveEventMatchCondition(this)
|
||||
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveEventMatchCondition(event, this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
|
|
|
@ -16,25 +16,32 @@
|
|||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import im.vector.matrix.android.internal.session.room.RoomGetter
|
||||
import timber.log.Timber
|
||||
|
||||
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
|
||||
class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
|
||||
class RoomMemberCountCondition(
|
||||
/**
|
||||
* A decimal integer optionally prefixed by one of ==, <, >, >= or <=.
|
||||
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth.
|
||||
* If no prefix is present, this parameter defaults to ==.
|
||||
*/
|
||||
val iz: String
|
||||
) : Condition(Kind.RoomMemberCount) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveRoomMemberCountCondition(this)
|
||||
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveRoomMemberCountCondition(event, this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
return "Room member count is $iz"
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
||||
// sanity check^
|
||||
internal fun isSatisfied(event: Event, roomGetter: RoomGetter): Boolean {
|
||||
// sanity checks
|
||||
val roomId = event.roomId ?: return false
|
||||
val room = session?.getRoom(roomId) ?: return false
|
||||
val room = roomGetter.getRoom(roomId) ?: return false
|
||||
|
||||
// Parse the is field into prefix and number the first time
|
||||
val (prefix, count) = parseIsField() ?: return false
|
||||
|
@ -59,7 +66,7 @@ class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_coun
|
|||
val (prefix, count) = match.destructured
|
||||
return prefix to count.toInt()
|
||||
} catch (t: Throwable) {
|
||||
Timber.d(t)
|
||||
Timber.e(t, "Unable to parse 'is' field")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -19,10 +19,18 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||
|
||||
class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) {
|
||||
class SenderNotificationPermissionCondition(
|
||||
/**
|
||||
* A string that determines the power level the sender must have to trigger notifications of a given type,
|
||||
* such as room. Refer to the m.room.power_levels event schema for information about what the defaults are
|
||||
* and how to interpret the event. The key is used to look up the power level required to send a notification
|
||||
* type from the notifications object in the power level event content.
|
||||
*/
|
||||
val key: String
|
||||
) : Condition(Kind.SenderNotificationPermission) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveSenderNotificationPermissionCondition(this)
|
||||
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveSenderNotificationPermissionCondition(event, this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
|
|
|
@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
|
|||
* All push rulesets for a user.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetPushRulesResponse(
|
||||
internal data class GetPushRulesResponse(
|
||||
/**
|
||||
* Global rules, account level applying to all devices
|
||||
*/
|
||||
|
|
|
@ -17,7 +17,11 @@ package im.vector.matrix.android.api.pushrules.rest
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.pushrules.*
|
||||
import im.vector.matrix.android.api.pushrules.Condition
|
||||
import im.vector.matrix.android.api.pushrules.ContainsDisplayNameCondition
|
||||
import im.vector.matrix.android.api.pushrules.EventMatchCondition
|
||||
import im.vector.matrix.android.api.pushrules.RoomMemberCountCondition
|
||||
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
@ -30,13 +34,13 @@ data class PushCondition(
|
|||
/**
|
||||
* Required for event_match conditions. The dot- separated field of the event to match.
|
||||
*/
|
||||
|
||||
val key: String? = null,
|
||||
/**
|
||||
*Required for event_match conditions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Required for event_match conditions.
|
||||
*/
|
||||
val pattern: String? = null,
|
||||
|
||||
/**
|
||||
* Required for room_member_count conditions.
|
||||
* A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
|
||||
|
@ -47,30 +51,35 @@ data class PushCondition(
|
|||
) {
|
||||
|
||||
fun asExecutableCondition(): Condition? {
|
||||
return when (Condition.Kind.fromString(this.kind)) {
|
||||
Condition.Kind.event_match -> {
|
||||
if (this.key != null && this.pattern != null) {
|
||||
return when (Condition.Kind.fromString(kind)) {
|
||||
Condition.Kind.EventMatch -> {
|
||||
if (key != null && pattern != null) {
|
||||
EventMatchCondition(key, pattern)
|
||||
} else {
|
||||
Timber.e("Malformed Event match condition")
|
||||
null
|
||||
}
|
||||
}
|
||||
Condition.Kind.contains_display_name -> {
|
||||
Condition.Kind.ContainsDisplayName -> {
|
||||
ContainsDisplayNameCondition()
|
||||
}
|
||||
Condition.Kind.room_member_count -> {
|
||||
if (this.iz.isNullOrBlank()) {
|
||||
Condition.Kind.RoomMemberCount -> {
|
||||
if (iz.isNullOrEmpty()) {
|
||||
Timber.e("Malformed ROOM_MEMBER_COUNT condition")
|
||||
null
|
||||
} else {
|
||||
RoomMemberCountCondition(this.iz)
|
||||
RoomMemberCountCondition(iz)
|
||||
}
|
||||
}
|
||||
Condition.Kind.sender_notification_permission -> {
|
||||
this.key?.let { SenderNotificationPermissionCondition(it) }
|
||||
Condition.Kind.SenderNotificationPermission -> {
|
||||
if (key == null) {
|
||||
Timber.e("Malformed Sender Notification Permission condition")
|
||||
null
|
||||
} else {
|
||||
SenderNotificationPermissionCondition(key)
|
||||
}
|
||||
}
|
||||
Condition.Kind.UNRECOGNIZE -> {
|
||||
Condition.Kind.Unrecognised -> {
|
||||
Timber.e("Unknown kind $kind")
|
||||
null
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.api.pushrules.rest
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Ruleset(
|
||||
internal data class Ruleset(
|
||||
val content: List<PushRule>? = null,
|
||||
val override: List<PushRule>? = null,
|
||||
val room: List<PushRule>? = null,
|
||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.api.session.crypto.crosssigning
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
||||
|
@ -63,6 +62,4 @@ interface CrossSigningService {
|
|||
fun checkDeviceTrust(otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
locallyTrusted: Boolean?): DeviceTrustResult
|
||||
|
||||
fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ data class Event(
|
|||
|
||||
fun Event.isTextMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_TEXT,
|
||||
MessageType.MSGTYPE_EMOTE,
|
||||
MessageType.MSGTYPE_NOTICE -> true
|
||||
|
@ -210,7 +210,7 @@ fun Event.isTextMessage(): Boolean {
|
|||
|
||||
fun Event.isImageMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_IMAGE -> true
|
||||
else -> false
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.api.session.room.crypto
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
|
||||
interface RoomCryptoService {
|
||||
|
||||
|
@ -26,5 +27,9 @@ interface RoomCryptoService {
|
|||
|
||||
fun shouldEncryptForInvitedMembers(): Boolean
|
||||
|
||||
fun enableEncryptionWithAlgorithm(algorithm: String, callback: MatrixCallback<Unit>)
|
||||
/**
|
||||
* Enable encryption of the room
|
||||
*/
|
||||
fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM,
|
||||
callback: MatrixCallback<Unit>)
|
||||
}
|
||||
|
|
|
@ -18,9 +18,28 @@ package im.vector.matrix.android.api.session.room.model
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
|
||||
*/
|
||||
enum class RoomHistoryVisibility {
|
||||
/**
|
||||
* All events while this is the m.room.history_visibility value may be shared by any
|
||||
* participating homeserver with anyone, regardless of whether they have ever joined the room.
|
||||
*/
|
||||
@Json(name = "world_readable") WORLD_READABLE,
|
||||
/**
|
||||
* Previous events are always accessible to newly joined members. All events in the
|
||||
* room are accessible, even those sent when the member was not a part of the room.
|
||||
*/
|
||||
@Json(name = "shared") SHARED,
|
||||
/**
|
||||
* Events are accessible to newly joined members from the point they were invited onwards.
|
||||
* Events stop being accessible when the member's state changes to something other than invite or join.
|
||||
*/
|
||||
@Json(name = "invited") INVITED,
|
||||
@Json(name = "joined") JOINED,
|
||||
@Json(name = "world_readable") WORLD_READABLE
|
||||
/**
|
||||
* Events are accessible to newly joined members from the point they joined the room onwards.
|
||||
* Events stop being accessible when the member's state changes to something other than join.
|
||||
*/
|
||||
@Json(name = "joined") JOINED
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
|
||||
/**
|
||||
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
|
||||
*/
|
||||
|
@ -25,7 +23,5 @@ data class RoomMemberSummary constructor(
|
|||
val membership: Membership,
|
||||
val userId: String,
|
||||
val displayName: String? = null,
|
||||
val avatarUrl: String? = null,
|
||||
// TODO Warning: Will not be populated if not using RxRoom
|
||||
val userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||
val avatarUrl: String? = null
|
||||
)
|
||||
|
|
|
@ -25,9 +25,9 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class MessageAudioContent(
|
||||
/**
|
||||
* Not documented
|
||||
* Required. Must be 'm.audio'.
|
||||
*/
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'.
|
||||
|
@ -40,7 +40,7 @@ data class MessageAudioContent(
|
|||
@Json(name = "info") val audioInfo: AudioInfo? = null,
|
||||
|
||||
/**
|
||||
* Required. Required if the file is not encrypted. The URL (typically MXC URI) to the audio clip.
|
||||
* Required if the file is not encrypted. The URL (typically MXC URI) to the audio clip.
|
||||
*/
|
||||
@Json(name = "url") override val url: String? = null,
|
||||
|
||||
|
|
|
@ -20,8 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content
|
|||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
|
||||
interface MessageContent {
|
||||
// TODO Rename to msgType
|
||||
val type: String
|
||||
val msgType: String
|
||||
val body: String
|
||||
val relatesTo: RelationDefaultContent?
|
||||
val newContent: Content?
|
||||
|
|
|
@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageDefaultContent(
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
@Json(name = "body") override val body: String,
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
|
|
|
@ -23,10 +23,26 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageEmoteContent(
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
/**
|
||||
* Required. Must be 'm.emote'.
|
||||
*/
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. The emote action to perform.
|
||||
*/
|
||||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
|
|
|
@ -23,10 +23,13 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
|||
*/
|
||||
interface MessageEncryptedContent : MessageContent {
|
||||
/**
|
||||
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
|
||||
* Required if the file is unencrypted. The URL (typically MXC URI) to the image.
|
||||
*/
|
||||
val url: String?
|
||||
|
||||
/**
|
||||
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
||||
*/
|
||||
val encryptedFileInfo: EncryptedFileInfo?
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class MessageFileContent(
|
||||
/**
|
||||
* Not documented
|
||||
* Required. Must be 'm.file'.
|
||||
*/
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. A human-readable description of the file. This is recommended to be the filename of the original upload.
|
||||
|
@ -46,13 +46,16 @@ data class MessageFileContent(
|
|||
@Json(name = "info") val info: FileInfo? = null,
|
||||
|
||||
/**
|
||||
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the file.
|
||||
* Required if the file is unencrypted. The URL (typically MXC URI) to the file.
|
||||
*/
|
||||
@Json(name = "url") override val url: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||
|
||||
/**
|
||||
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
||||
*/
|
||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||
) : MessageEncryptedContent {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* 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.
|
||||
|
@ -14,10 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.room.detail
|
||||
package im.vector.matrix.android.api.session.room.model.message
|
||||
|
||||
data class FileTooBigError(
|
||||
val filename: String,
|
||||
val fileSizeInBytes: Long,
|
||||
val homeServerLimitInBytes: Long
|
||||
)
|
||||
object MessageFormat {
|
||||
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
|
||||
}
|
|
@ -27,7 +27,7 @@ data class MessageImageContent(
|
|||
/**
|
||||
* Required. Must be 'm.image'.
|
||||
*/
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
|
||||
|
@ -41,7 +41,7 @@ data class MessageImageContent(
|
|||
@Json(name = "info") override val info: ImageInfo? = null,
|
||||
|
||||
/**
|
||||
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
|
||||
* Required if the file is unencrypted. The URL (typically MXC URI) to the image.
|
||||
*/
|
||||
@Json(name = "url") override val url: String? = null,
|
||||
|
||||
|
|
|
@ -24,9 +24,9 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class MessageLocationContent(
|
||||
/**
|
||||
* Not documented
|
||||
* Required. Must be 'm.location'.
|
||||
*/
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'.
|
||||
|
|
|
@ -23,10 +23,26 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageNoticeContent(
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
/**
|
||||
* Required. Must be 'm.notice'.
|
||||
*/
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. The notice text to send.
|
||||
*/
|
||||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
* 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.message
|
||||
|
@ -28,7 +27,7 @@ data class MessageStickerContent(
|
|||
/**
|
||||
* Set in local, not from server
|
||||
*/
|
||||
override val type: String = MessageType.MSGTYPE_STICKER_LOCAL,
|
||||
override val msgType: String = MessageType.MSGTYPE_STICKER_LOCAL,
|
||||
|
||||
/**
|
||||
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
|
||||
|
@ -42,7 +41,7 @@ data class MessageStickerContent(
|
|||
@Json(name = "info") override val info: ImageInfo? = null,
|
||||
|
||||
/**
|
||||
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
|
||||
* Required if the file is unencrypted. The URL (typically MXC URI) to the image.
|
||||
*/
|
||||
@Json(name = "url") override val url: String? = null,
|
||||
|
||||
|
|
|
@ -23,10 +23,26 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageTextContent(
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
/**
|
||||
* Required. Must be 'm.text'.
|
||||
*/
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. The body of the message.
|
||||
*/
|
||||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package im.vector.matrix.android.api.session.room.model.message
|
||||
|
||||
object MessageType {
|
||||
|
||||
const val MSGTYPE_TEXT = "m.text"
|
||||
const val MSGTYPE_EMOTE = "m.emote"
|
||||
const val MSGTYPE_NOTICE = "m.notice"
|
||||
|
@ -27,7 +26,6 @@ object MessageType {
|
|||
const val MSGTYPE_LOCATION = "m.location"
|
||||
const val MSGTYPE_FILE = "m.file"
|
||||
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
|
||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||
// Because sticker isn't a message type but a event type without msgtype field
|
||||
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReq
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageVerificationRequestContent(
|
||||
@Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
|
||||
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
|
||||
@Json(name = "body") override val body: String,
|
||||
@Json(name = "from_device") override val fromDevice: String?,
|
||||
@Json(name = "methods") override val methods: List<String>,
|
||||
|
|
|
@ -27,7 +27,7 @@ data class MessageVideoContent(
|
|||
/**
|
||||
* Required. Must be 'm.video'.
|
||||
*/
|
||||
@Json(name = "msgtype") override val type: String,
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
|
||||
|
@ -40,7 +40,7 @@ data class MessageVideoContent(
|
|||
@Json(name = "info") val videoInfo: VideoInfo? = null,
|
||||
|
||||
/**
|
||||
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the video clip.
|
||||
* Required if the file is unencrypted. The URL (typically MXC URI) to the video clip.
|
||||
*/
|
||||
@Json(name = "url") override val url: String? = null,
|
||||
|
||||
|
|
|
@ -26,10 +26,16 @@ import im.vector.matrix.android.api.util.Optional
|
|||
*/
|
||||
interface ReadService {
|
||||
|
||||
enum class MarkAsReadParams {
|
||||
READ_RECEIPT,
|
||||
READ_MARKER,
|
||||
BOTH
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the read marker to be set on the latest event.
|
||||
*/
|
||||
fun markAllAsRead(callback: MatrixCallback<Unit>)
|
||||
fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Set the read receipt on the event with provided eventId.
|
||||
|
|
|
@ -17,18 +17,20 @@
|
|||
package im.vector.matrix.android.api.session.room.send
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface DraftService {
|
||||
|
||||
/**
|
||||
* Save or update a draft to the room
|
||||
*/
|
||||
fun saveDraft(draft: UserDraft)
|
||||
fun saveDraft(draft: UserDraft, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Delete the last draft, basically just after sending the message
|
||||
*/
|
||||
fun deleteDraft()
|
||||
fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Return the current drafts if any, as a live data
|
||||
|
|
|
@ -28,12 +28,7 @@ interface StateService {
|
|||
*/
|
||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Enable encryption of the room
|
||||
*/
|
||||
fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>)
|
||||
fun getStateEvent(eventType: String, stateKey: String): Event?
|
||||
|
||||
fun getStateEvent(eventType: String): Event?
|
||||
|
||||
fun getStateEventLive(eventType: String): LiveData<Optional<Event>>
|
||||
fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>>
|
||||
}
|
||||
|
|
|
@ -112,6 +112,11 @@ interface Timeline {
|
|||
* Called whenever an error we can't recover from occurred
|
||||
*/
|
||||
fun onTimelineFailure(throwable: Throwable)
|
||||
|
||||
/**
|
||||
* Called when new events come through the sync
|
||||
*/
|
||||
fun onNewTimelineEvents(eventIds: List<String>)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventConten
|
|||
data class TimelineEvent(
|
||||
val root: Event,
|
||||
val localId: Long,
|
||||
val eventId: String,
|
||||
val displayIndex: Int,
|
||||
val senderName: String?,
|
||||
val isUniqueDisplayName: Boolean,
|
||||
|
|
|
@ -19,10 +19,11 @@ package im.vector.matrix.android.internal.crypto
|
|||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.ComputeTrustTask
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultComputeTrustTask
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
||||
|
@ -137,15 +138,6 @@ internal abstract class CryptoModule {
|
|||
return RealmClearCacheTask(realmConfiguration)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
fun providesCryptoStore(@CryptoDatabase
|
||||
realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore {
|
||||
return RealmCryptoStore(
|
||||
realmConfiguration,
|
||||
credentials)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SessionScope
|
||||
|
@ -159,13 +151,6 @@ internal abstract class CryptoModule {
|
|||
fun providesRoomKeysAPI(retrofit: Retrofit): RoomKeysApi {
|
||||
return retrofit.create(RoomKeysApi::class.java)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SessionScope
|
||||
fun providesCryptoConfig(): MXCryptoConfig {
|
||||
return MXCryptoConfig()
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
@ -256,4 +241,10 @@ internal abstract class CryptoModule {
|
|||
|
||||
@Binds
|
||||
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
|
||||
|
||||
@Binds
|
||||
abstract fun bindCryptoStore(realmCryptoStore: RealmCryptoStore): IMXCryptoStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindComputeShieldTrustTask(defaultShieldTrustUpdater: DefaultComputeTrustTask): ComputeTrustTask
|
||||
}
|
||||
|
|
|
@ -26,7 +26,9 @@ import com.squareup.moshi.Types
|
|||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.crypto.MXCryptoConfig
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
|
@ -70,7 +72,7 @@ import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
|||
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.database.query.whereType
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
|
@ -116,7 +118,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
// Olm device
|
||||
private val olmDevice: MXOlmDevice,
|
||||
// Set of parameters used to configure/customize the end-to-end crypto.
|
||||
private val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
|
||||
private val mxCryptoConfig: MXCryptoConfig,
|
||||
// Device list manager
|
||||
private val deviceListManager: DeviceListManager,
|
||||
// The key backup service.
|
||||
|
@ -189,7 +191,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// bg refresh of crypto device
|
||||
downloadKeys(listOf(credentials.userId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {})
|
||||
downloadKeys(listOf(credentials.userId), true, NoOpMatrixCallback())
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
|
||||
|
@ -399,6 +401,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
|
||||
}
|
||||
|
@ -531,7 +534,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*/
|
||||
override fun isRoomEncrypted(roomId: String): Boolean {
|
||||
val encryptionEvent = monarchy.fetchCopied { realm ->
|
||||
EventEntity.where(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
||||
.findFirst()
|
||||
}
|
||||
|
@ -545,8 +548,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
|
||||
}
|
||||
|
||||
fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||
return cryptoConfig.enableEncryptionForInvitedMembers
|
||||
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||
return mxCryptoConfig.enableEncryptionForInvitedMembers
|
||||
}
|
||||
|
||||
override fun getEncryptionAlgorithm(roomId: String): String? {
|
||||
|
@ -779,7 +782,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||
} else if (membership == Membership.INVITE
|
||||
&& shouldEncryptForInvitedMembers(roomId)
|
||||
&& cryptoConfig.enableEncryptionForInvitedMembers) {
|
||||
&& isEncryptionEnabledForInvitedUser()) {
|
||||
// track the deviceList for this invited user.
|
||||
// Caution: there's a big edge case here in that federated servers do not
|
||||
// know what other servers are in the room at the time they've been invited.
|
||||
|
|
|
@ -27,6 +27,9 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
|||
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -36,10 +39,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
private val olmDevice: MXOlmDevice,
|
||||
private val syncTokenStore: SyncTokenStore,
|
||||
private val credentials: Credentials,
|
||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
|
||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
|
||||
coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
taskExecutor: TaskExecutor) {
|
||||
|
||||
interface UserDevicesUpdateListener {
|
||||
fun onUsersDeviceUpdate(users: List<String>)
|
||||
fun onUsersDeviceUpdate(userIds: List<String>)
|
||||
}
|
||||
|
||||
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
|
||||
|
@ -72,17 +77,19 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
private val notReadyToRetryHS = mutableSetOf<String>()
|
||||
|
||||
init {
|
||||
var isUpdated = false
|
||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||
for ((userId, status) in deviceTrackingStatuses) {
|
||||
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
|
||||
// if a download was in progress when we got shut down, it isn't any more.
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||
isUpdated = true
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
|
||||
var isUpdated = false
|
||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||
for ((userId, status) in deviceTrackingStatuses) {
|
||||
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
|
||||
// if a download was in progress when we got shut down, it isn't any more.
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||
isUpdated = true
|
||||
}
|
||||
}
|
||||
if (isUpdated) {
|
||||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||
}
|
||||
}
|
||||
if (isUpdated) {
|
||||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,7 +327,6 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
// al devices =
|
||||
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
|
||||
|
||||
|
||||
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
|
||||
if (!models.isNullOrEmpty()) {
|
||||
val workingCopy = models.toMutableMap()
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.internal.crypto.crosssigning
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncryptionTrustLevel> {
|
||||
data class Params(
|
||||
val userIds: List<String>
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultComputeTrustTask @Inject constructor(
|
||||
val cryptoStore: IMXCryptoStore
|
||||
) : ComputeTrustTask {
|
||||
|
||||
override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel {
|
||||
val allTrustedUserIds = params.userIds
|
||||
.filter { userId -> getUserCrossSigningKeys(userId)?.isTrusted() == true }
|
||||
|
||||
return if (allTrustedUserIds.isEmpty()) {
|
||||
RoomEncryptionTrustLevel.Default
|
||||
} else {
|
||||
// If one of the verified user as an untrusted device -> warning
|
||||
// If all devices of all verified users are trusted -> green
|
||||
// else -> black
|
||||
allTrustedUserIds
|
||||
.mapNotNull { cryptoStore.getUserDeviceList(it) }
|
||||
.flatten()
|
||||
.let { allDevices ->
|
||||
if (getMyCrossSigningKeys() != null) {
|
||||
allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
|
||||
} else {
|
||||
// Legacy method
|
||||
allDevices.any { !it.isVerified }
|
||||
}
|
||||
}
|
||||
.let { hasWarning ->
|
||||
if (hasWarning) {
|
||||
RoomEncryptionTrustLevel.Warning
|
||||
} else {
|
||||
if (params.userIds.size == allTrustedUserIds.size) {
|
||||
// all users are trusted and all devices are verified
|
||||
RoomEncryptionTrustLevel.Trusted
|
||||
} else {
|
||||
RoomEncryptionTrustLevel.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||
return cryptoStore.getCrossSigningInfo(otherUserId)
|
||||
}
|
||||
|
||||
private fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||
return cryptoStore.getMyCrossSigningInfo()
|
||||
}
|
||||
}
|
|
@ -19,8 +19,6 @@ package im.vector.matrix.android.internal.crypto.crosssigning
|
|||
import androidx.lifecycle.LiveData
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
@ -36,13 +34,15 @@ import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
|
|||
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.task.TaskConstraints
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.TaskThread
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.withoutPrefix
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.olm.OlmPkSigning
|
||||
import org.matrix.olm.OlmUtility
|
||||
import timber.log.Timber
|
||||
|
@ -57,9 +57,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
private val deviceListManager: DeviceListManager,
|
||||
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
||||
private val uploadSignaturesTask: UploadSignaturesTask,
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val computeTrustTask: ComputeTrustTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
|
||||
|
||||
private var olmUtility: OlmUtility? = null
|
||||
|
||||
|
@ -211,7 +213,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
|
||||
|
||||
uploadSigningKeysTask.configureWith(params) {
|
||||
this.constraints = TaskConstraints(true)
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.i("## CrossSigning - Keys successfully uploaded")
|
||||
|
@ -247,7 +249,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
resetTrustOnKeyChange()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
|
||||
// this.retryCount = 3
|
||||
this.constraints = TaskConstraints(true)
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.i("## CrossSigning - signatures successfully uploaded")
|
||||
|
@ -396,7 +398,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
return@forEach
|
||||
} catch (failure: Throwable) {
|
||||
// log
|
||||
Timber.v(failure)
|
||||
Timber.w(failure, "Signature not valid?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -500,6 +502,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
|
||||
.build()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}.executeBy(taskExecutor)
|
||||
}
|
||||
|
@ -546,6 +549,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
.withDeviceInfo(toUpload)
|
||||
.build()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}.executeBy(taskExecutor)
|
||||
}
|
||||
|
@ -611,81 +615,52 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUsersDeviceUpdate(users: List<String>) {
|
||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users")
|
||||
users.forEach { otherUserId ->
|
||||
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users")
|
||||
userIds.forEach { otherUserId ->
|
||||
checkUserTrust(otherUserId).let {
|
||||
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
|
||||
setUserKeysAsTrusted(otherUserId, it.isVerified())
|
||||
}
|
||||
|
||||
checkUserTrust(otherUserId).let {
|
||||
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
|
||||
setUserKeysAsTrusted(otherUserId, it.isVerified())
|
||||
}
|
||||
// TODO if my keys have changes, i should recheck all devices of all users?
|
||||
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
||||
devices?.forEach { device ->
|
||||
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||
}
|
||||
|
||||
// TODO if my keys have changes, i should recheck all devices of all users?
|
||||
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
||||
devices?.forEach { device ->
|
||||
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||
}
|
||||
if (otherUserId == userId) {
|
||||
// It's me, i should check if a newly trusted device is signing my master key
|
||||
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
||||
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
||||
}
|
||||
|
||||
if (otherUserId == userId) {
|
||||
// It's me, i should check if a newly trusted device is signing my master key
|
||||
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
||||
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
||||
eventBus.post(CryptoToSessionUserTrustChange(userIds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
||||
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
||||
|
||||
// If it's me, recheck trust of all users and devices?
|
||||
val users = ArrayList<String>()
|
||||
if (otherUserId == userId && currentTrust != trusted) {
|
||||
cryptoStore.updateUsersTrust {
|
||||
users.add(it)
|
||||
checkUserTrust(it).isVerified()
|
||||
}
|
||||
|
||||
users.forEach {
|
||||
cryptoStore.getUserDeviceList(it)?.forEach { device ->
|
||||
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
||||
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
||||
// If it's me, recheck trust of all users and devices?
|
||||
val users = ArrayList<String>()
|
||||
if (otherUserId == userId && currentTrust != trusted) {
|
||||
cryptoStore.updateUsersTrust {
|
||||
users.add(it)
|
||||
checkUserTrust(it).isVerified()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
|
||||
val allTrusted = userIds
|
||||
.filter { getUserCrossSigningKeys(it)?.isTrusted() == true }
|
||||
|
||||
val allUsersAreVerified = userIds.size == allTrusted.size
|
||||
|
||||
return if (allTrusted.isEmpty()) {
|
||||
RoomEncryptionTrustLevel.Default
|
||||
} else {
|
||||
// If one of the verified user as an untrusted device -> warning
|
||||
// Green if all devices of all verified users are trusted -> green
|
||||
// else black
|
||||
val allDevices = allTrusted.mapNotNull {
|
||||
cryptoStore.getUserDeviceList(it)
|
||||
}.flatten()
|
||||
if (getMyCrossSigningKeys() != null) {
|
||||
val hasWarning = allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
|
||||
if (hasWarning) {
|
||||
RoomEncryptionTrustLevel.Warning
|
||||
} else {
|
||||
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
|
||||
}
|
||||
} else {
|
||||
val hasWarningLegacy = allDevices.any { !it.isVerified }
|
||||
if (hasWarningLegacy) {
|
||||
RoomEncryptionTrustLevel.Warning
|
||||
} else {
|
||||
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
|
||||
users.forEach {
|
||||
cryptoStore.getUserDeviceList(it)?.forEach { device ->
|
||||
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 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
|
||||
* 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,
|
||||
|
@ -13,13 +13,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.internal.crypto.crosssigning
|
||||
|
||||
package im.vector.riotx.features.home.room.detail
|
||||
data class SessionToCryptoRoomMembersUpdate(
|
||||
val roomId: String,
|
||||
val userIds: List<String>
|
||||
)
|
||||
|
||||
import java.io.File
|
||||
|
||||
data class DownloadFileState(
|
||||
val mimeType: String,
|
||||
val file: File?,
|
||||
val throwable: Throwable?
|
||||
)
|
||||
data class CryptoToSessionUserTrustChange(
|
||||
val userIds: List<String>
|
||||
)
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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.internal.crypto.crosssigning
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ShieldTrustUpdater @Inject constructor(
|
||||
private val eventBus: EventBus,
|
||||
private val computeTrustTask: ComputeTrustTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
@CryptoDatabase private val cryptoRealmConfiguration: RealmConfiguration,
|
||||
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
|
||||
}
|
||||
|
||||
private val backgroundCryptoRealm = AtomicReference<Realm>()
|
||||
private val backgroundSessionRealm = AtomicReference<Realm>()
|
||||
|
||||
// private var cryptoDevicesResult: RealmResults<DeviceInfoEntity>? = null
|
||||
|
||||
// private val cryptoDeviceChangeListener = object : OrderedRealmCollectionChangeListener<RealmResults<DeviceInfoEntity>> {
|
||||
// override fun onChange(t: RealmResults<DeviceInfoEntity>, changeSet: OrderedCollectionChangeSet) {
|
||||
// val grouped = t.groupBy { it.userId }
|
||||
// onCryptoDevicesChange(grouped.keys.mapNotNull { it })
|
||||
// }
|
||||
// }
|
||||
|
||||
fun start() {
|
||||
eventBus.register(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
val cryptoRealm = Realm.getInstance(cryptoRealmConfiguration)
|
||||
backgroundCryptoRealm.set(cryptoRealm)
|
||||
// cryptoDevicesResult = cryptoRealm.where<DeviceInfoEntity>().findAll()
|
||||
// cryptoDevicesResult?.addChangeListener(cryptoDeviceChangeListener)
|
||||
|
||||
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
eventBus.unregister(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
// cryptoDevicesResult?.removeAllChangeListeners()
|
||||
backgroundCryptoRealm.getAndSet(null).also {
|
||||
it?.close()
|
||||
}
|
||||
backgroundSessionRealm.getAndSet(null).also {
|
||||
it?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) {
|
||||
taskExecutor.executorScope.launch {
|
||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
|
||||
// We need to send that back to session base
|
||||
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.get().executeTransaction { realm ->
|
||||
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onTrustUpdate(update: CryptoToSessionUserTrustChange) {
|
||||
onCryptoDevicesChange(update.userIds)
|
||||
}
|
||||
|
||||
private fun onCryptoDevicesChange(users: List<String>) {
|
||||
BACKGROUND_HANDLER.post {
|
||||
val impactedRoomsId = backgroundSessionRealm.get().where(RoomMemberSummaryEntity::class.java)
|
||||
.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
|
||||
.findAll()
|
||||
.map { it.roomId }
|
||||
.distinct()
|
||||
|
||||
val map = HashMap<String, List<String>>()
|
||||
impactedRoomsId.forEach { roomId ->
|
||||
RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId)
|
||||
.findAll()
|
||||
.let { results ->
|
||||
map[roomId] = results.map { it.userId }
|
||||
}
|
||||
}
|
||||
|
||||
map.forEach { entry ->
|
||||
val roomId = entry.key
|
||||
val userList = entry.value
|
||||
taskExecutor.executorScope.launch {
|
||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.get().executeTransaction { realm ->
|
||||
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -414,7 +414,7 @@ internal class KeysBackup @Inject constructor(
|
|||
olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySignature)
|
||||
isSignatureValid = true
|
||||
} catch (e: OlmException) {
|
||||
Timber.v(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}")
|
||||
Timber.w(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ import im.vector.matrix.android.internal.crypto.store.db.query.delete
|
|||
import im.vector.matrix.android.internal.crypto.store.db.query.get
|
||||
import im.vector.matrix.android.internal.crypto.store.db.query.getById
|
||||
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
|
@ -70,11 +71,13 @@ import io.realm.kotlin.where
|
|||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmException
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.set
|
||||
|
||||
@SessionScope
|
||||
internal class RealmCryptoStore(private val realmConfiguration: RealmConfiguration,
|
||||
private val credentials: Credentials) : IMXCryptoStore {
|
||||
internal class RealmCryptoStore @Inject constructor(
|
||||
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
|
||||
private val credentials: Credentials) : IMXCryptoStore {
|
||||
|
||||
/* ==========================================================================================
|
||||
* Memory cache, to correctly release JNI objects
|
||||
|
@ -403,14 +406,14 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
|||
{ realm: Realm ->
|
||||
realm
|
||||
.where<UserEntity>()
|
||||
.`in`(UserEntityFields.USER_ID, userIds.toTypedArray())
|
||||
.`in`(UserEntityFields.USER_ID, userIds.distinct().toTypedArray())
|
||||
},
|
||||
{ entity ->
|
||||
entity.devices.map { CryptoMapper.mapToModel(it) }
|
||||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull() ?: emptyList()
|
||||
it.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
|||
// done from another device of mine
|
||||
|
||||
if (EventType.MESSAGE == event.type) {
|
||||
val msgType = event.getClearContent().toModel<MessageContent>()?.type
|
||||
val msgType = event.getClearContent().toModel<MessageContent>()?.msgType
|
||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
|
||||
event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
|
||||
if (it.fromDevice != deviceId) {
|
||||
|
@ -144,7 +144,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
|||
params.verificationService.onRoomEvent(event)
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.type) {
|
||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
|
||||
params.verificationService.onRoomRequestReceived(event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,9 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
|
|||
override suspend fun execute(params: UploadSignaturesTask.Params) {
|
||||
try {
|
||||
val response = executeRequest<SignatureUploadResponse>(eventBus) {
|
||||
apiCall = cryptoApi.uploadSignatures(params.signatures)
|
||||
this.isRetryable = true
|
||||
this.maxRetryCount = 10
|
||||
this.apiCall = cryptoApi.uploadSignatures(params.signatures)
|
||||
}
|
||||
if (response.failures?.isNotEmpty() == true) {
|
||||
throw Throwable(response.failures.toString())
|
||||
|
|
|
@ -165,7 +165,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
onRoomDoneReceived(event)
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.type) {
|
||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
|
||||
onRoomRequestReceived(event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask
|
|||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.query.types
|
||||
import im.vector.matrix.android.internal.database.query.whereTypes
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
|
@ -42,7 +42,7 @@ internal class VerificationMessageLiveObserver @Inject constructor(
|
|||
) : RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
||||
|
||||
override val query = Monarchy.Query {
|
||||
EventEntity.types(it, listOf(
|
||||
EventEntity.whereTypes(it, listOf(
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_KEY,
|
||||
|
|
|
@ -331,7 +331,7 @@ internal class VerificationTransportRoomMessage(
|
|||
content = content,
|
||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||
).also {
|
||||
localEchoEventFactory.saveLocalEcho(monarchy, it)
|
||||
localEchoEventFactory.createLocalEcho(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,19 +16,27 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database.helper
|
||||
|
||||
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.room.send.SendState
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||
import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.find
|
||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import io.realm.Realm
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.createObject
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun ChunkEntity.deleteOnCascade() {
|
||||
assertIsManaged()
|
||||
|
@ -36,116 +44,154 @@ internal fun ChunkEntity.deleteOnCascade() {
|
|||
this.deleteFromRealm()
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.merge(roomId: String,
|
||||
chunkToMerge: ChunkEntity,
|
||||
direction: PaginationDirection): List<TimelineEventEntity> {
|
||||
internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direction: PaginationDirection) {
|
||||
assertIsManaged()
|
||||
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked
|
||||
val isCurrentChunkUnlinked = isUnlinked
|
||||
|
||||
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
||||
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
||||
}
|
||||
val localRealm = this.realm
|
||||
val eventsToMerge: List<TimelineEventEntity>
|
||||
if (direction == PaginationDirection.FORWARDS) {
|
||||
this.nextToken = chunkToMerge.nextToken
|
||||
this.isLastForward = chunkToMerge.isLastForward
|
||||
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
||||
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
||||
} else {
|
||||
this.prevToken = chunkToMerge.prevToken
|
||||
this.isLastBackward = chunkToMerge.isLastBackward
|
||||
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
}
|
||||
chunkToMerge.stateEvents.forEach { stateEvent ->
|
||||
addStateEvent(roomId, stateEvent, direction)
|
||||
}
|
||||
return eventsToMerge
|
||||
.mapNotNull {
|
||||
val event = it.root?.asDomain() ?: return@mapNotNull null
|
||||
add(roomId, event, direction)
|
||||
.forEach {
|
||||
addTimelineEventFromMerge(localRealm, it, direction)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.add(roomId: String,
|
||||
event: Event,
|
||||
direction: PaginationDirection,
|
||||
stateIndexOffset: Int = 0
|
||||
): TimelineEventEntity? {
|
||||
assertIsManaged()
|
||||
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
|
||||
return null
|
||||
}
|
||||
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
||||
if (direction == PaginationDirection.FORWARDS) {
|
||||
currentDisplayIndex += 1
|
||||
forwardsDisplayIndex = currentDisplayIndex
|
||||
internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) {
|
||||
if (direction == PaginationDirection.BACKWARDS) {
|
||||
Timber.v("We don't keep chunk state events when paginating backward")
|
||||
} else {
|
||||
currentDisplayIndex -= 1
|
||||
backwardsDisplayIndex = currentDisplayIndex
|
||||
}
|
||||
var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset)
|
||||
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.type)) {
|
||||
currentStateIndex += 1
|
||||
forwardsStateIndex = currentStateIndex
|
||||
} else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) {
|
||||
val lastEventType = timelineEvents.last()?.root?.type ?: ""
|
||||
if (EventType.isStateEvent(lastEventType)) {
|
||||
currentStateIndex -= 1
|
||||
backwardsStateIndex = currentStateIndex
|
||||
val stateKey = stateEvent.stateKey ?: return
|
||||
val type = stateEvent.type
|
||||
val pastStateEvent = stateEvents.where()
|
||||
.equalTo(EventEntityFields.ROOM_ID, roomId)
|
||||
.equalTo(EventEntityFields.STATE_KEY, stateKey)
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
||||
.findFirst()
|
||||
|
||||
if (pastStateEvent != null) {
|
||||
stateEvents.remove(pastStateEvent)
|
||||
}
|
||||
stateEvents.add(stateEvent)
|
||||
}
|
||||
}
|
||||
|
||||
val isChunkUnlinked = isUnlinked
|
||||
internal fun ChunkEntity.addTimelineEvent(roomId: String,
|
||||
eventEntity: EventEntity,
|
||||
direction: PaginationDirection,
|
||||
roomMemberContentsByUser: Map<String, RoomMemberContent?>) {
|
||||
val eventId = eventEntity.eventId
|
||||
if (timelineEvents.find(eventId) != null) {
|
||||
return
|
||||
}
|
||||
val displayIndex = nextDisplayIndex(direction)
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val eventId = event.eventId ?: ""
|
||||
val senderId = event.senderId ?: ""
|
||||
|
||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
||||
?: realm.createObject<ReadReceiptsSummaryEntity>(eventId).apply {
|
||||
this.roomId = roomId
|
||||
}
|
||||
val senderId = eventEntity.sender ?: ""
|
||||
|
||||
// Update RR for the sender of a new message with a dummy one
|
||||
val readReceiptsSummaryEntity = handleReadReceipts(realm, roomId, eventEntity, senderId)
|
||||
val timelineEventEntity = realm.createObject<TimelineEventEntity>().apply {
|
||||
this.localId = localId
|
||||
this.root = eventEntity
|
||||
this.eventId = eventId
|
||||
this.roomId = roomId
|
||||
this.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||
this.readReceipts = readReceiptsSummaryEntity
|
||||
this.displayIndex = displayIndex
|
||||
val roomMemberContent = roomMemberContentsByUser[senderId]
|
||||
this.senderAvatar = roomMemberContent?.avatarUrl
|
||||
this.senderName = roomMemberContent?.displayName
|
||||
isUniqueDisplayName = if (roomMemberContent?.displayName != null) {
|
||||
computeIsUnique(realm, roomId, isLastForward, roomMemberContent, roomMemberContentsByUser)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
timelineEvents.add(timelineEventEntity)
|
||||
}
|
||||
|
||||
if (event.originServerTs != null) {
|
||||
val timestampOfEvent = event.originServerTs.toDouble()
|
||||
private fun computeIsUnique(
|
||||
realm: Realm,
|
||||
roomId: String,
|
||||
isLastForward: Boolean,
|
||||
myRoomMemberContent: RoomMemberContent,
|
||||
roomMemberContentsByUser: Map<String, RoomMemberContent?>
|
||||
): Boolean {
|
||||
val isHistoricalUnique = roomMemberContentsByUser.values.find {
|
||||
it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName
|
||||
} == null
|
||||
return if (isLastForward) {
|
||||
val isLiveUnique = RoomMemberSummaryEntity
|
||||
.where(realm, roomId)
|
||||
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, myRoomMemberContent.displayName)
|
||||
.findAll().none {
|
||||
!roomMemberContentsByUser.containsKey(it.userId)
|
||||
}
|
||||
isHistoricalUnique && isLiveUnique
|
||||
} else {
|
||||
isHistoricalUnique
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) {
|
||||
val eventId = timelineEventEntity.eventId
|
||||
if (timelineEvents.find(eventId) != null) {
|
||||
return
|
||||
}
|
||||
val displayIndex = nextDisplayIndex(direction)
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val copied = realm.createObject<TimelineEventEntity>().apply {
|
||||
this.localId = localId
|
||||
this.root = timelineEventEntity.root
|
||||
this.eventId = timelineEventEntity.eventId
|
||||
this.roomId = timelineEventEntity.roomId
|
||||
this.annotations = timelineEventEntity.annotations
|
||||
this.readReceipts = timelineEventEntity.readReceipts
|
||||
this.displayIndex = displayIndex
|
||||
this.senderAvatar = timelineEventEntity.senderAvatar
|
||||
this.senderName = timelineEventEntity.senderName
|
||||
this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName
|
||||
}
|
||||
timelineEvents.add(copied)
|
||||
}
|
||||
|
||||
private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity {
|
||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst()
|
||||
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {
|
||||
this.roomId = roomId
|
||||
}
|
||||
val originServerTs = eventEntity.originServerTs
|
||||
if (originServerTs != null) {
|
||||
val timestampOfEvent = originServerTs.toDouble()
|
||||
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId)
|
||||
// If the synced RR is older, update
|
||||
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
|
||||
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
|
||||
readReceiptOfSender.eventId = eventId
|
||||
readReceiptOfSender.eventId = eventEntity.eventId
|
||||
readReceiptOfSender.originServerTs = timestampOfEvent
|
||||
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
|
||||
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
|
||||
}
|
||||
}
|
||||
|
||||
val rootEvent = event.toEntity(roomId).apply {
|
||||
this.stateIndex = currentStateIndex
|
||||
this.displayIndex = currentDisplayIndex
|
||||
this.sendState = SendState.SYNCED
|
||||
this.isUnlinked = isChunkUnlinked
|
||||
}
|
||||
val eventEntity = realm.createObject<TimelineEventEntity>().also {
|
||||
it.localId = localId
|
||||
it.root = realm.copyToRealm(rootEvent)
|
||||
it.eventId = eventId
|
||||
it.roomId = roomId
|
||||
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||
it.readReceipts = readReceiptsSummaryEntity
|
||||
}
|
||||
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
||||
timelineEvents.add(position, eventEntity)
|
||||
return eventEntity
|
||||
return readReceiptsSummaryEntity
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
||||
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int {
|
||||
return when (direction) {
|
||||
PaginationDirection.FORWARDS -> forwardsDisplayIndex
|
||||
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
|
||||
} ?: defaultValue
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
||||
return when (direction) {
|
||||
PaginationDirection.FORWARDS -> forwardsStateIndex
|
||||
PaginationDirection.BACKWARDS -> backwardsStateIndex
|
||||
} ?: defaultValue
|
||||
PaginationDirection.FORWARDS -> {
|
||||
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1
|
||||
}
|
||||
PaginationDirection.BACKWARDS -> {
|
||||
(timelineEvents.where().min(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,8 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database.helper
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.query.fastContains
|
||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
|
||||
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
|
||||
chunks.remove(chunkEntity)
|
||||
|
@ -36,39 +29,3 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
|
|||
chunks.add(chunkEntity)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RoomEntity.addStateEvent(stateEvent: Event,
|
||||
stateIndex: Int = Int.MIN_VALUE,
|
||||
filterDuplicates: Boolean = false,
|
||||
isUnlinked: Boolean = false) {
|
||||
assertIsManaged()
|
||||
if (stateEvent.eventId == null || (filterDuplicates && fastContains(stateEvent.eventId))) {
|
||||
return
|
||||
} else {
|
||||
val entity = stateEvent.toEntity(roomId).apply {
|
||||
this.stateIndex = stateIndex
|
||||
this.isUnlinked = isUnlinked
|
||||
this.sendState = SendState.SYNCED
|
||||
}
|
||||
untimelinedStateEvents.add(entity)
|
||||
}
|
||||
}
|
||||
internal fun RoomEntity.addSendingEvent(event: Event) {
|
||||
assertIsManaged()
|
||||
val senderId = event.senderId ?: return
|
||||
val eventEntity = event.toEntity(roomId).apply {
|
||||
this.sendState = SendState.UNSENT
|
||||
}
|
||||
val roomMembers = RoomMemberHelper(realm, roomId)
|
||||
val myUser = roomMembers.getLastRoomMember(senderId)
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val timelineEventEntity = TimelineEventEntity(localId).also {
|
||||
it.root = eventEntity
|
||||
it.eventId = event.eventId ?: ""
|
||||
it.roomId = roomId
|
||||
it.senderName = myUser?.displayName
|
||||
it.senderAvatar = myUser?.avatarUrl
|
||||
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
|
||||
}
|
||||
sendingTimelineEvents.add(0, timelineEventEntity)
|
||||
}
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.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.RoomMemberHelper
|
||||
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 = RoomMemberHelper(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 = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
result.senderMembershipEventId = senderMembershipEvent?.eventId
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import com.squareup.moshi.JsonDataException
|
|||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
|
@ -43,7 +44,6 @@ internal object EventMapper {
|
|||
eventEntity.redacts = event.redacts
|
||||
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
|
||||
eventEntity.unsignedData = uds
|
||||
eventEntity.ageLocalTs = event.ageLocalTs
|
||||
return eventEntity
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,9 @@ internal fun EventEntity.asDomain(): Event {
|
|||
return EventMapper.map(this)
|
||||
}
|
||||
|
||||
internal fun Event.toEntity(roomId: String): EventEntity {
|
||||
return EventMapper.map(this, roomId)
|
||||
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): EventEntity {
|
||||
return EventMapper.map(this, roomId).apply {
|
||||
this.sendState = sendState
|
||||
this.ageLocalTs = ageLocalTs
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ internal object PushRulesMapper {
|
|||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
conditions = listOf(
|
||||
PushCondition(Condition.Kind.event_match.name, "content.body", pushrule.pattern)
|
||||
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ internal object PushRulesMapper {
|
|||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
conditions = listOf(
|
||||
PushCondition(Condition.Kind.event_match.name, "room_id", pushrule.ruleId)
|
||||
PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.ruleId)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ internal object PushRulesMapper {
|
|||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
conditions = listOf(
|
||||
PushCondition(Condition.Kind.event_match.name, "user_id", pushrule.ruleId)
|
||||
PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.ruleId)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -26,8 +26,7 @@ internal object RoomMemberSummaryMapper {
|
|||
userId = roomMemberSummaryEntity.userId,
|
||||
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
||||
displayName = roomMemberSummaryEntity.displayName,
|
||||
membership = roomMemberSummaryEntity.membership,
|
||||
userEncryptionTrustLevel = null
|
||||
membership = roomMemberSummaryEntity.membership
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -49,7 +49,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
} catch (e: MXCryptoError) {
|
||||
} catch (e: Throwable) {
|
||||
Timber.d(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +76,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||
aliases = roomSummaryEntity.aliases.toList(),
|
||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
|
||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex
|
||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,15 +37,19 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
|
|||
return TimelineEvent(
|
||||
root = timelineEventEntity.root?.asDomain()
|
||||
?: Event("", timelineEventEntity.eventId),
|
||||
eventId = timelineEventEntity.eventId,
|
||||
annotations = timelineEventEntity.annotations?.asDomain(),
|
||||
localId = timelineEventEntity.localId,
|
||||
displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
|
||||
displayIndex = timelineEventEntity.displayIndex,
|
||||
senderName = timelineEventEntity.senderName,
|
||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||
senderAvatar = timelineEventEntity.senderAvatar,
|
||||
readReceipts = readReceipts?.sortedByDescending {
|
||||
it.originServerTs
|
||||
} ?: emptyList()
|
||||
readReceipts = readReceipts
|
||||
?.distinctBy {
|
||||
it.user
|
||||
}?.sortedByDescending {
|
||||
it.originServerTs
|
||||
} ?: emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,14 +24,10 @@ import io.realm.annotations.LinkingObjects
|
|||
|
||||
internal open class ChunkEntity(@Index var prevToken: String? = null,
|
||||
@Index var nextToken: String? = null,
|
||||
var stateEvents: RealmList<EventEntity> = RealmList(),
|
||||
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||
@Index var isLastForward: Boolean = false,
|
||||
@Index var isLastBackward: Boolean = false,
|
||||
var backwardsDisplayIndex: Int? = null,
|
||||
var forwardsDisplayIndex: Int? = null,
|
||||
var backwardsStateIndex: Int? = null,
|
||||
var forwardsStateIndex: Int? = null,
|
||||
var isUnlinked: Boolean = false
|
||||
@Index var isLastBackward: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
fun identifier() = "${prevToken}_$nextToken"
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.internal.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.Index
|
||||
|
||||
internal open class CurrentStateEventEntity(var eventId: String = "",
|
||||
var root: EventEntity? = null,
|
||||
@Index var roomId: String = "",
|
||||
@Index var type: String = "",
|
||||
@Index var stateKey: String = ""
|
||||
) : RealmObject() {
|
||||
companion object
|
||||
}
|
|
@ -21,11 +21,10 @@ import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
|||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import io.realm.RealmObject
|
||||
import io.realm.RealmResults
|
||||
import io.realm.annotations.Index
|
||||
import io.realm.annotations.LinkingObjects
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class EventEntity(@Index var eventId: String = "",
|
||||
internal open class EventEntity(@PrimaryKey var eventId: String = "",
|
||||
@Index var roomId: String = "",
|
||||
@Index var type: String = "",
|
||||
var content: String? = null,
|
||||
|
@ -36,20 +35,11 @@ internal open class EventEntity(@Index var eventId: String = "",
|
|||
var age: Long? = 0,
|
||||
var unsignedData: String? = null,
|
||||
var redacts: String? = null,
|
||||
@Index var stateIndex: Int = 0,
|
||||
@Index var displayIndex: Int = 0,
|
||||
@Index var isUnlinked: Boolean = false,
|
||||
var decryptionResultJson: String? = null,
|
||||
var decryptionErrorCode: String? = null,
|
||||
var ageLocalTs: Long? = null
|
||||
) : RealmObject() {
|
||||
|
||||
enum class LinkFilterMode {
|
||||
LINKED_ONLY,
|
||||
UNLINKED_ONLY,
|
||||
BOTH
|
||||
}
|
||||
|
||||
private var sendStateStr: String = SendState.UNKNOWN.name
|
||||
|
||||
var sendState: SendState
|
||||
|
@ -62,12 +52,6 @@ internal open class EventEntity(@Index var eventId: String = "",
|
|||
|
||||
companion object
|
||||
|
||||
@LinkingObjects("untimelinedStateEvents")
|
||||
val room: RealmResults<RoomEntity>? = null
|
||||
|
||||
@LinkingObjects("root")
|
||||
val timelineEventEntity: RealmResults<TimelineEventEntity>? = null
|
||||
|
||||
fun setDecryptionResult(result: MXEventDecryptionResult) {
|
||||
val decryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
|
@ -78,6 +62,5 @@ internal open class EventEntity(@Index var eventId: String = "",
|
|||
val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java)
|
||||
decryptionResultJson = adapter.toJson(decryptionResult)
|
||||
decryptionErrorCode = null
|
||||
timelineEventEntity?.firstOrNull()?.root = this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import io.realm.annotations.PrimaryKey
|
|||
|
||||
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||
var chunks: RealmList<ChunkEntity> = RealmList(),
|
||||
var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
|
||||
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||
var areAllMembersLoaded: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database.model
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||
|
@ -47,7 +48,8 @@ internal open class RoomSummaryEntity(
|
|||
// this is required for querying
|
||||
var flatAliases: String = "",
|
||||
var isEncrypted: Boolean = false,
|
||||
var typingUserIds: RealmList<String> = RealmList()
|
||||
var typingUserIds: RealmList<String> = RealmList(),
|
||||
var roomEncryptionTrustLevelStr: String? = null
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
|
@ -68,5 +70,19 @@ internal open class RoomSummaryEntity(
|
|||
versioningStateStr = value.name
|
||||
}
|
||||
|
||||
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
|
||||
get() {
|
||||
return roomEncryptionTrustLevelStr?.let {
|
||||
try {
|
||||
RoomEncryptionTrustLevel.valueOf(it)
|
||||
} catch (failure: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
roomEncryptionTrustLevelStr = value?.name
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
|
@ -22,35 +22,36 @@ import io.realm.annotations.RealmModule
|
|||
* Realm module for Session
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
classes = [
|
||||
ChunkEntity::class,
|
||||
EventEntity::class,
|
||||
TimelineEventEntity::class,
|
||||
FilterEntity::class,
|
||||
GroupEntity::class,
|
||||
GroupSummaryEntity::class,
|
||||
ReadReceiptEntity::class,
|
||||
RoomEntity::class,
|
||||
RoomSummaryEntity::class,
|
||||
RoomTagEntity::class,
|
||||
SyncEntity::class,
|
||||
UserEntity::class,
|
||||
IgnoredUserEntity::class,
|
||||
BreadcrumbsEntity::class,
|
||||
EventAnnotationsSummaryEntity::class,
|
||||
ReferencesAggregatedSummaryEntity::class,
|
||||
ReactionAggregatedSummaryEntity::class,
|
||||
EditAggregatedSummaryEntity::class,
|
||||
PushRulesEntity::class,
|
||||
PushRuleEntity::class,
|
||||
PushConditionEntity::class,
|
||||
PusherEntity::class,
|
||||
PusherDataEntity::class,
|
||||
ReadReceiptsSummaryEntity::class,
|
||||
ReadMarkerEntity::class,
|
||||
UserDraftsEntity::class,
|
||||
DraftEntity::class,
|
||||
HomeServerCapabilitiesEntity::class,
|
||||
RoomMemberSummaryEntity::class
|
||||
])
|
||||
classes = [
|
||||
ChunkEntity::class,
|
||||
EventEntity::class,
|
||||
TimelineEventEntity::class,
|
||||
FilterEntity::class,
|
||||
GroupEntity::class,
|
||||
GroupSummaryEntity::class,
|
||||
ReadReceiptEntity::class,
|
||||
RoomEntity::class,
|
||||
RoomSummaryEntity::class,
|
||||
RoomTagEntity::class,
|
||||
SyncEntity::class,
|
||||
UserEntity::class,
|
||||
IgnoredUserEntity::class,
|
||||
BreadcrumbsEntity::class,
|
||||
EventAnnotationsSummaryEntity::class,
|
||||
ReactionAggregatedSummaryEntity::class,
|
||||
EditAggregatedSummaryEntity::class,
|
||||
ReferencesAggregatedSummaryEntity::class,
|
||||
PushRulesEntity::class,
|
||||
PushRuleEntity::class,
|
||||
PushConditionEntity::class,
|
||||
PusherEntity::class,
|
||||
PusherDataEntity::class,
|
||||
ReadReceiptsSummaryEntity::class,
|
||||
ReadMarkerEntity::class,
|
||||
UserDraftsEntity::class,
|
||||
DraftEntity::class,
|
||||
HomeServerCapabilitiesEntity::class,
|
||||
RoomMemberSummaryEntity::class,
|
||||
CurrentStateEventEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -24,6 +24,7 @@ import io.realm.annotations.LinkingObjects
|
|||
internal open class TimelineEventEntity(var localId: Long = 0,
|
||||
@Index var eventId: String = "",
|
||||
@Index var roomId: String = "",
|
||||
@Index var displayIndex: Int = 0,
|
||||
var root: EventEntity? = null,
|
||||
var annotations: EventAnnotationsSummaryEntity? = null,
|
||||
var senderName: String? = null,
|
||||
|
|
|
@ -60,12 +60,10 @@ internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: Str
|
|||
internal fun ChunkEntity.Companion.create(
|
||||
realm: Realm,
|
||||
prevToken: String?,
|
||||
nextToken: String?,
|
||||
isUnlinked: Boolean
|
||||
nextToken: String?
|
||||
): ChunkEntity {
|
||||
return realm.createObject<ChunkEntity>().apply {
|
||||
this.prevToken = prevToken
|
||||
this.nextToken = nextToken
|
||||
this.isUnlinked = isUnlinked
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.internal.database.query
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.createObject
|
||||
|
||||
internal fun CurrentStateEventEntity.Companion.where(realm: Realm, roomId: String, type: String): RealmQuery<CurrentStateEventEntity> {
|
||||
return realm.where(CurrentStateEventEntity::class.java)
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
||||
}
|
||||
|
||||
internal fun CurrentStateEventEntity.Companion.whereStateKey(realm: Realm, roomId: String, type: String, stateKey: String)
|
||||
: RealmQuery<CurrentStateEventEntity> {
|
||||
return where(realm = realm, roomId = roomId, type = type)
|
||||
.equalTo(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
||||
}
|
||||
|
||||
internal fun CurrentStateEventEntity.Companion.getOrNull(realm: Realm, roomId: String, stateKey: String, type: String): CurrentStateEventEntity? {
|
||||
return whereStateKey(realm = realm, roomId = roomId, type = type, stateKey = stateKey).findFirst()
|
||||
}
|
||||
|
||||
internal fun CurrentStateEventEntity.Companion.getOrCreate(realm: Realm, roomId: String, stateKey: String, type: String): CurrentStateEventEntity {
|
||||
return getOrNull(realm = realm, roomId = roomId, stateKey = stateKey, type = type) ?: create(realm, roomId, stateKey, type)
|
||||
}
|
||||
|
||||
private fun create(realm: Realm, roomId: String, stateKey: String, type: String): CurrentStateEventEntity {
|
||||
return realm.createObject<CurrentStateEventEntity>().apply {
|
||||
this.type = type
|
||||
this.roomId = roomId
|
||||
this.stateKey = stateKey
|
||||
}
|
||||
}
|
|
@ -17,12 +17,10 @@
|
|||
package im.vector.matrix.android.internal.database.query
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.*
|
||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventEntity> {
|
||||
|
@ -35,61 +33,28 @@ internal fun EventEntity.Companion.where(realm: Realm, eventIds: List<String>):
|
|||
.`in`(EventEntityFields.EVENT_ID, eventIds.toTypedArray())
|
||||
}
|
||||
|
||||
internal fun EventEntity.Companion.where(realm: Realm,
|
||||
roomId: String? = null,
|
||||
type: String? = null,
|
||||
linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery<EventEntity> {
|
||||
internal fun EventEntity.Companion.whereType(realm: Realm,
|
||||
type: String,
|
||||
roomId: String? = null
|
||||
): RealmQuery<EventEntity> {
|
||||
val query = realm.where<EventEntity>()
|
||||
if (roomId != null) {
|
||||
query.equalTo(EventEntityFields.ROOM_ID, roomId)
|
||||
}
|
||||
if (type != null) {
|
||||
query.equalTo(EventEntityFields.TYPE, type)
|
||||
}
|
||||
return when (linkFilterMode) {
|
||||
LINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, false)
|
||||
UNLINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, true)
|
||||
BOTH -> query
|
||||
}
|
||||
return query.equalTo(EventEntityFields.TYPE, type)
|
||||
}
|
||||
|
||||
internal fun EventEntity.Companion.types(realm: Realm,
|
||||
typeList: List<String> = emptyList()): RealmQuery<EventEntity> {
|
||||
internal fun EventEntity.Companion.whereTypes(realm: Realm,
|
||||
typeList: List<String> = emptyList(),
|
||||
roomId: String? = null): RealmQuery<EventEntity> {
|
||||
val query = realm.where<EventEntity>()
|
||||
query.`in`(EventEntityFields.TYPE, typeList.toTypedArray())
|
||||
if (roomId != null) {
|
||||
query.equalTo(EventEntityFields.ROOM_ID, roomId)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.descending(since: Int? = null, strict: Boolean = false): RealmQuery<EventEntity> {
|
||||
if (since != null) {
|
||||
if (strict) {
|
||||
this.lessThan(EventEntityFields.STATE_INDEX, since)
|
||||
} else {
|
||||
this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, since)
|
||||
}
|
||||
}
|
||||
return this.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.ascending(from: Int? = null, strict: Boolean = true): RealmQuery<EventEntity> {
|
||||
if (from != null) {
|
||||
if (strict) {
|
||||
this.greaterThan(EventEntityFields.STATE_INDEX, from)
|
||||
} else {
|
||||
this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from)
|
||||
}
|
||||
}
|
||||
return this.sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING)
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
|
||||
return this.ascending(from, strict).findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean = false): EventEntity? {
|
||||
return descending(since, strict).findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmList<EventEntity>.find(eventId: String): EventEntity? {
|
||||
return this.where()
|
||||
.equalTo(EventEntityFields.EVENT_ID, eventId)
|
||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.events.model.LocalEcho
|
|||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import io.realm.Realm
|
||||
|
||||
internal fun isEventRead(monarchy: Monarchy,
|
||||
|
@ -36,16 +37,15 @@ internal fun isEventRead(monarchy: Monarchy,
|
|||
|
||||
monarchy.doWithRealm { realm ->
|
||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm
|
||||
val eventToCheck = liveChunk.timelineEvents.find(eventId)?.root
|
||||
|
||||
isEventRead = if (eventToCheck?.sender == userId) {
|
||||
val eventToCheck = liveChunk.timelineEvents.find(eventId)
|
||||
isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) {
|
||||
true
|
||||
} else {
|
||||
val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst()
|
||||
?: return@doWithRealm
|
||||
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
||||
?: Int.MIN_VALUE
|
||||
val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE
|
||||
?: return@doWithRealm
|
||||
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex
|
||||
?: Int.MIN_VALUE
|
||||
val eventToCheckIndex = eventToCheck.displayIndex
|
||||
|
||||
eventToCheckIndex <= readReceiptIndex
|
||||
}
|
||||
|
@ -61,13 +61,17 @@ internal fun isReadMarkerMoreRecent(monarchy: Monarchy,
|
|||
return false
|
||||
}
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return false
|
||||
val eventToCheck = liveChunk.timelineEvents.find(eventId)?.root
|
||||
|
||||
val eventToCheck = TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()
|
||||
val eventToCheckChunk = eventToCheck?.chunk?.firstOrNull()
|
||||
val readMarker = ReadMarkerEntity.where(realm, roomId).findFirst() ?: return false
|
||||
val readMarkerIndex = liveChunk.timelineEvents.find(readMarker.eventId)?.root?.displayIndex
|
||||
?: Int.MIN_VALUE
|
||||
val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE
|
||||
eventToCheckIndex <= readMarkerIndex
|
||||
val readMarkerEvent = TimelineEventEntity.where(realm, roomId = roomId, eventId = readMarker.eventId).findFirst()
|
||||
val readMarkerChunk = readMarkerEvent?.chunk?.firstOrNull()
|
||||
if (eventToCheckChunk == readMarkerChunk) {
|
||||
val readMarkerIndex = readMarkerEvent?.displayIndex ?: Int.MIN_VALUE
|
||||
val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE
|
||||
eventToCheckIndex <= readMarkerIndex
|
||||
} else {
|
||||
eventToCheckChunk?.isLastForward == false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.database.query
|
|||
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.*
|
||||
import io.realm.*
|
||||
import io.realm.kotlin.where
|
||||
|
||||
|
@ -34,22 +33,10 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, e
|
|||
.`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray())
|
||||
}
|
||||
|
||||
internal fun TimelineEventEntity.Companion.where(realm: Realm,
|
||||
roomId: String? = null,
|
||||
type: String? = null,
|
||||
linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery<TimelineEventEntity> {
|
||||
val query = realm.where<TimelineEventEntity>()
|
||||
if (roomId != null) {
|
||||
query.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||
}
|
||||
if (type != null) {
|
||||
query.equalTo(TimelineEventEntityFields.ROOT.TYPE, type)
|
||||
}
|
||||
return when (linkFilterMode) {
|
||||
LINKED_ONLY -> query.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false)
|
||||
UNLINKED_ONLY -> query.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, true)
|
||||
BOTH -> query
|
||||
}
|
||||
internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm,
|
||||
roomId: String): RealmQuery<TimelineEventEntity> {
|
||||
return realm.where<TimelineEventEntity>()
|
||||
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||
}
|
||||
|
||||
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
|
||||
|
@ -71,7 +58,7 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
|||
liveEvents
|
||||
}
|
||||
return query
|
||||
?.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
?.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
?.findFirst()
|
||||
}
|
||||
|
||||
|
@ -83,32 +70,6 @@ internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<Strin
|
|||
}
|
||||
}
|
||||
|
||||
internal fun RealmQuery<TimelineEventEntity>.next(from: Int? = null, strict: Boolean = true): TimelineEventEntity? {
|
||||
if (from != null) {
|
||||
if (strict) {
|
||||
this.greaterThan(TimelineEventEntityFields.ROOT.STATE_INDEX, from)
|
||||
} else {
|
||||
this.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.STATE_INDEX, from)
|
||||
}
|
||||
}
|
||||
return this
|
||||
.sort(TimelineEventEntityFields.ROOT.STATE_INDEX, Sort.ASCENDING)
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmQuery<TimelineEventEntity>.prev(since: Int? = null, strict: Boolean = false): TimelineEventEntity? {
|
||||
if (since != null) {
|
||||
if (strict) {
|
||||
this.lessThan(TimelineEventEntityFields.ROOT.STATE_INDEX, since)
|
||||
} else {
|
||||
this.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.STATE_INDEX, since)
|
||||
}
|
||||
}
|
||||
return this
|
||||
.sort(TimelineEventEntityFields.ROOT.STATE_INDEX, Sort.DESCENDING)
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmList<TimelineEventEntity>.find(eventId: String): TimelineEventEntity? {
|
||||
return this.where()
|
||||
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
|
||||
|
|
|
@ -22,11 +22,11 @@ import com.squareup.moshi.Moshi
|
|||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.auth.AuthModule
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
|
@ -48,6 +48,8 @@ internal interface MatrixComponent {
|
|||
|
||||
fun context(): Context
|
||||
|
||||
fun matrixConfiguration(): MatrixConfiguration
|
||||
|
||||
fun resources(): Resources
|
||||
|
||||
fun olmManager(): OlmManager
|
||||
|
@ -56,8 +58,6 @@ internal interface MatrixComponent {
|
|||
|
||||
fun sessionParamsStore(): SessionParamsStore
|
||||
|
||||
fun networkConnectivityChecker(): NetworkConnectivityChecker
|
||||
|
||||
fun backgroundDetectionObserver(): BackgroundDetectionObserver
|
||||
|
||||
fun sessionManager(): SessionManager
|
||||
|
@ -66,6 +66,7 @@ internal interface MatrixComponent {
|
|||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance context: Context): MatrixComponent
|
||||
fun create(@BindsInstance context: Context,
|
||||
@BindsInstance matrixConfiguration: MatrixConfiguration): MatrixComponent
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.internal.network
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.IntentFilter
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.os.Build
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface NetworkCallbackStrategy {
|
||||
fun register(hasChanged: () -> Unit)
|
||||
fun unregister()
|
||||
}
|
||||
|
||||
internal class FallbackNetworkCallbackStrategy @Inject constructor(private val context: Context,
|
||||
private val networkInfoReceiver: NetworkInfoReceiver) : NetworkCallbackStrategy {
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||
|
||||
override fun register(hasChanged: () -> Unit) {
|
||||
networkInfoReceiver.isConnectedCallback = {
|
||||
hasChanged()
|
||||
}
|
||||
context.registerReceiver(networkInfoReceiver, filter)
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
networkInfoReceiver.isConnectedCallback = null
|
||||
context.unregisterReceiver(networkInfoReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
internal class PreferredNetworkCallbackStrategy @Inject constructor(context: Context) : NetworkCallbackStrategy {
|
||||
|
||||
private var hasChangedCallback: (() -> Unit)? = null
|
||||
private val conn = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
hasChangedCallback?.invoke()
|
||||
}
|
||||
|
||||
override fun onAvailable(network: Network) {
|
||||
hasChangedCallback?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
override fun register(hasChanged: () -> Unit) {
|
||||
hasChangedCallback = hasChanged
|
||||
conn.registerDefaultNetworkCallback(networkCallback)
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
hasChangedCallback = null
|
||||
conn.unregisterNetworkCallback(networkCallback)
|
||||
}
|
||||
}
|
|
@ -16,112 +16,94 @@
|
|||
|
||||
package im.vector.matrix.android.internal.network
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.novoda.merlin.Merlin
|
||||
import com.novoda.merlin.MerlinsBeard
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.homeserver.HomeServerPinger
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import timber.log.Timber
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@MatrixScope
|
||||
internal class NetworkConnectivityChecker @Inject constructor(private val context: Context,
|
||||
private val backgroundDetectionObserver: BackgroundDetectionObserver)
|
||||
: BackgroundDetectionObserver.Listener {
|
||||
interface NetworkConnectivityChecker {
|
||||
/**
|
||||
* Returns true when internet is available
|
||||
*/
|
||||
@WorkerThread
|
||||
fun hasInternetAccess(forcePing: Boolean): Boolean
|
||||
|
||||
private val merlin = Merlin.Builder()
|
||||
.withConnectableCallbacks()
|
||||
.withDisconnectableCallbacks()
|
||||
.build(context)
|
||||
fun register(listener: Listener)
|
||||
fun unregister(listener: Listener)
|
||||
|
||||
private val merlinsBeard = MerlinsBeard.Builder().build(context)
|
||||
interface Listener {
|
||||
fun onConnectivityChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private val listeners = Collections.synchronizedSet(LinkedHashSet<Listener>())
|
||||
private var hasInternetAccess = merlinsBeard.isConnected
|
||||
@SessionScope
|
||||
internal class DefaultNetworkConnectivityChecker @Inject constructor(private val homeServerPinger: HomeServerPinger,
|
||||
private val backgroundDetectionObserver: BackgroundDetectionObserver,
|
||||
private val networkCallbackStrategy: NetworkCallbackStrategy)
|
||||
: NetworkConnectivityChecker {
|
||||
|
||||
init {
|
||||
backgroundDetectionObserver.register(this)
|
||||
private val hasInternetAccess = AtomicBoolean(true)
|
||||
private val listeners = Collections.synchronizedSet(LinkedHashSet<NetworkConnectivityChecker.Listener>())
|
||||
private val backgroundDetectionObserverListener = object : BackgroundDetectionObserver.Listener {
|
||||
override fun onMoveToForeground() {
|
||||
bind()
|
||||
}
|
||||
|
||||
override fun onMoveToBackground() {
|
||||
unbind()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when internet is available
|
||||
*/
|
||||
@WorkerThread
|
||||
fun hasInternetAccess(): Boolean {
|
||||
// If we are in background we have unbound merlin, so we have to check
|
||||
return if (backgroundDetectionObserver.isInBackground) {
|
||||
merlinsBeard.hasInternetAccess()
|
||||
override fun hasInternetAccess(forcePing: Boolean): Boolean {
|
||||
return if (forcePing) {
|
||||
runBlocking {
|
||||
homeServerPinger.canReachHomeServer()
|
||||
}
|
||||
} else {
|
||||
hasInternetAccess
|
||||
hasInternetAccess.get()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMoveToForeground() {
|
||||
merlin.bind()
|
||||
merlinsBeard.hasInternetAccess {
|
||||
hasInternetAccess = it
|
||||
}
|
||||
merlin.registerDisconnectable {
|
||||
if (hasInternetAccess) {
|
||||
Timber.v("On Disconnect")
|
||||
hasInternetAccess = false
|
||||
val localListeners = listeners.toList()
|
||||
localListeners.forEach {
|
||||
it.onDisconnect()
|
||||
}
|
||||
override fun register(listener: NetworkConnectivityChecker.Listener) {
|
||||
if (listeners.isEmpty()) {
|
||||
if (backgroundDetectionObserver.isInBackground) {
|
||||
unbind()
|
||||
} else {
|
||||
bind()
|
||||
}
|
||||
backgroundDetectionObserver.register(backgroundDetectionObserverListener)
|
||||
}
|
||||
merlin.registerConnectable {
|
||||
if (!hasInternetAccess) {
|
||||
Timber.v("On Connect")
|
||||
hasInternetAccess = true
|
||||
val localListeners = listeners.toList()
|
||||
localListeners.forEach {
|
||||
it.onConnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMoveToBackground() {
|
||||
merlin.unbind()
|
||||
}
|
||||
|
||||
// In background you won't get notification as merlin is unbound
|
||||
suspend fun waitUntilConnected() {
|
||||
if (hasInternetAccess) {
|
||||
return
|
||||
} else {
|
||||
Timber.v("Waiting for network...")
|
||||
suspendCoroutine<Unit> { continuation ->
|
||||
register(object : Listener {
|
||||
override fun onConnect() {
|
||||
unregister(this)
|
||||
Timber.v("Connected to network...")
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun register(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
fun unregister(listener: Listener) {
|
||||
override fun unregister(listener: NetworkConnectivityChecker.Listener) {
|
||||
listeners.remove(listener)
|
||||
if (listeners.isEmpty()) {
|
||||
backgroundDetectionObserver.unregister(backgroundDetectionObserverListener)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onConnect() {
|
||||
private fun bind() {
|
||||
networkCallbackStrategy.register {
|
||||
val localListeners = listeners.toList()
|
||||
localListeners.forEach {
|
||||
it.onConnectivityChanged()
|
||||
}
|
||||
}
|
||||
homeServerPinger.canReachHomeServer {
|
||||
hasInternetAccess.set(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDisconnect() {
|
||||
}
|
||||
private fun unbind() {
|
||||
networkCallbackStrategy.unregister()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.internal.network
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkInfo
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class NetworkInfoReceiver @Inject constructor() : BroadcastReceiver() {
|
||||
|
||||
var isConnectedCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val conn = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val networkInfo: NetworkInfo? = conn.activeNetworkInfo
|
||||
isConnectedCallback?.invoke(networkInfo?.isConnected ?: false)
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.network
|
|||
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.delay
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import retrofit2.Call
|
||||
import java.io.IOException
|
||||
|
@ -27,11 +28,17 @@ internal suspend inline fun <DATA> executeRequest(eventBus: EventBus?,
|
|||
|
||||
internal class Request<DATA>(private val eventBus: EventBus?) {
|
||||
|
||||
var isRetryable = false
|
||||
var initialDelay: Long = 100L
|
||||
var maxDelay: Long = 10_000L
|
||||
var maxRetryCount = Int.MAX_VALUE
|
||||
private var currentRetryCount = 0
|
||||
private var currentDelay = initialDelay
|
||||
lateinit var apiCall: Call<DATA>
|
||||
|
||||
suspend fun execute(): DATA {
|
||||
return try {
|
||||
val response = apiCall.awaitResponse()
|
||||
val response = apiCall.clone().awaitResponse()
|
||||
if (response.isSuccessful) {
|
||||
response.body()
|
||||
?: throw IllegalStateException("The request returned a null body")
|
||||
|
@ -39,12 +46,18 @@ internal class Request<DATA>(private val eventBus: EventBus?) {
|
|||
throw response.toFailure(eventBus)
|
||||
}
|
||||
} catch (exception: Throwable) {
|
||||
throw when (exception) {
|
||||
is IOException -> Failure.NetworkConnection(exception)
|
||||
is Failure.ServerError,
|
||||
is Failure.OtherServerError -> exception
|
||||
is CancellationException -> Failure.Cancelled(exception)
|
||||
else -> Failure.Unknown(exception)
|
||||
if (isRetryable && currentRetryCount++ < maxRetryCount && exception is IOException) {
|
||||
delay(currentDelay)
|
||||
currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay)
|
||||
return execute()
|
||||
} else {
|
||||
throw when (exception) {
|
||||
is IOException -> Failure.NetworkConnection(exception)
|
||||
is Failure.ServerError,
|
||||
is Failure.OtherServerError -> exception
|
||||
is CancellationException -> Failure.Cancelled(exception)
|
||||
else -> Failure.Unknown(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,18 +18,20 @@ package im.vector.matrix.android.internal.network
|
|||
|
||||
import android.content.Context
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@MatrixScope
|
||||
internal class UserAgentHolder @Inject constructor(private val context: Context) {
|
||||
internal class UserAgentHolder @Inject constructor(private val context: Context,
|
||||
matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
var userAgent: String = ""
|
||||
private set
|
||||
|
||||
init {
|
||||
setApplicationFlavor("NoFlavor")
|
||||
setApplicationFlavor(matrixConfiguration.applicationFlavor)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +40,7 @@ internal class UserAgentHolder @Inject constructor(private val context: Context)
|
|||
*
|
||||
* @param flavorDescription the flavor description
|
||||
*/
|
||||
fun setApplicationFlavor(flavorDescription: String) {
|
||||
private fun setApplicationFlavor(flavorDescription: String) {
|
||||
var appName = ""
|
||||
var appVersion = ""
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.sync.SyncState
|
|||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||
|
@ -89,7 +90,8 @@ internal class DefaultSession @Inject constructor(
|
|||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
||||
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
|
||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||
: Session,
|
||||
RoomService by roomService.get(),
|
||||
RoomDirectoryService by roomDirectoryService.get(),
|
||||
|
@ -119,6 +121,7 @@ internal class DefaultSession @Inject constructor(
|
|||
isOpen = true
|
||||
liveEntityObservers.forEach { it.start() }
|
||||
eventBus.register(this)
|
||||
shieldTrustUpdater.start()
|
||||
}
|
||||
|
||||
override fun requireBackgroundSync() {
|
||||
|
@ -160,6 +163,7 @@ internal class DefaultSession @Inject constructor(
|
|||
isOpen = false
|
||||
eventBus.unregister(this)
|
||||
syncTaskSequencer.close()
|
||||
shieldTrustUpdater.stop()
|
||||
}
|
||||
|
||||
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||
|
|
|
@ -17,16 +17,19 @@
|
|||
package im.vector.matrix.android.internal.session
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.Binds
|
||||
import dagger.Lazy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.auth.data.sessionId
|
||||
import im.vector.matrix.android.api.crypto.MXCryptoConfig
|
||||
import im.vector.matrix.android.api.session.InitialSyncProgressService
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
|
@ -34,8 +37,21 @@ import im.vector.matrix.android.api.session.securestorage.SecureStorageService
|
|||
import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver
|
||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
||||
import im.vector.matrix.android.internal.di.*
|
||||
import im.vector.matrix.android.internal.di.Authenticated
|
||||
import im.vector.matrix.android.internal.di.DeviceId
|
||||
import im.vector.matrix.android.internal.di.SessionCacheDirectory
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.di.UserMd5
|
||||
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
||||
import im.vector.matrix.android.internal.network.DefaultNetworkConnectivityChecker
|
||||
import im.vector.matrix.android.internal.network.FallbackNetworkCallbackStrategy
|
||||
import im.vector.matrix.android.internal.network.NetworkCallbackStrategy
|
||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||
import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
||||
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
||||
|
@ -51,6 +67,7 @@ import okhttp3.OkHttpClient
|
|||
import org.greenrobot.eventbus.EventBus
|
||||
import retrofit2.Retrofit
|
||||
import java.io.File
|
||||
import javax.inject.Provider
|
||||
|
||||
@Module
|
||||
internal abstract class SessionModule {
|
||||
|
@ -59,6 +76,11 @@ internal abstract class SessionModule {
|
|||
companion object {
|
||||
internal fun getKeyAlias(userMd5: String) = "session_db_$userMd5"
|
||||
|
||||
/**
|
||||
* Rules:
|
||||
* Annotate methods with @SessionScope only the @Provides annotated methods with computation and logic.
|
||||
*/
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
fun providesHomeServerConnectionConfig(sessionParams: SessionParams): HomeServerConnectionConfig {
|
||||
|
@ -74,6 +96,7 @@ internal abstract class SessionModule {
|
|||
@JvmStatic
|
||||
@UserId
|
||||
@Provides
|
||||
@SessionScope
|
||||
fun providesUserId(credentials: Credentials): String {
|
||||
return credentials.userId
|
||||
}
|
||||
|
@ -88,6 +111,7 @@ internal abstract class SessionModule {
|
|||
@JvmStatic
|
||||
@UserMd5
|
||||
@Provides
|
||||
@SessionScope
|
||||
fun providesUserMd5(@UserId userId: String): String {
|
||||
return userId.md5()
|
||||
}
|
||||
|
@ -95,6 +119,7 @@ internal abstract class SessionModule {
|
|||
@JvmStatic
|
||||
@SessionId
|
||||
@Provides
|
||||
@SessionScope
|
||||
fun providesSessionId(credentials: Credentials): String {
|
||||
return credentials.sessionId()
|
||||
}
|
||||
|
@ -178,11 +203,34 @@ internal abstract class SessionModule {
|
|||
fun providesEventBus(): EventBus {
|
||||
return EventBus.builder().build()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SessionScope
|
||||
fun providesNetworkCallbackStrategy(fallbackNetworkCallbackStrategy: Provider<FallbackNetworkCallbackStrategy>,
|
||||
preferredNetworkCallbackStrategy: Provider<PreferredNetworkCallbackStrategy>
|
||||
): NetworkCallbackStrategy {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
preferredNetworkCallbackStrategy.get()
|
||||
} else {
|
||||
fallbackNetworkCallbackStrategy.get()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SessionScope
|
||||
fun providesMxCryptoConfig(matrixConfiguration: MatrixConfiguration): MXCryptoConfig {
|
||||
return matrixConfiguration.cryptoConfig
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindSession(session: DefaultSession): Session
|
||||
|
||||
@Binds
|
||||
abstract fun bindNetworkConnectivityChecker(networkConnectivityChecker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindGroupSummaryUpdater(groupSummaryUpdater: GroupSummaryUpdater): LiveEntityObserver
|
||||
|
|
|
@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.session.group.model.GroupRooms
|
|||
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
|
||||
import im.vector.matrix.android.internal.session.group.model.GroupUsers
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -53,12 +54,12 @@ internal class DefaultGetGroupDataTask @Inject constructor(
|
|||
insertInDb(groupSummary, groupRooms, groupUsers, groupId)
|
||||
}
|
||||
|
||||
private fun insertInDb(groupSummary: GroupSummaryResponse,
|
||||
private suspend fun insertInDb(groupSummary: GroupSummaryResponse,
|
||||
groupRooms: GroupRooms,
|
||||
groupUsers: GroupUsers,
|
||||
groupId: String) {
|
||||
monarchy
|
||||
.writeAsync { realm ->
|
||||
.awaitTransaction { realm ->
|
||||
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
|
||||
?: realm.createObject(GroupSummaryEntity::class.java, groupId)
|
||||
|
||||
|
|
|
@ -27,4 +27,10 @@ internal interface CapabilitiesAPI {
|
|||
*/
|
||||
@GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config")
|
||||
fun getUploadCapabilities(): Call<GetUploadCapabilitiesResult>
|
||||
|
||||
/**
|
||||
* Request the versions
|
||||
*/
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
|
||||
fun getVersions(): Call<Unit>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.internal.session.homeserver
|
||||
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class HomeServerPinger @Inject constructor(private val taskExecutor: TaskExecutor,
|
||||
private val capabilitiesAPI: CapabilitiesAPI) {
|
||||
|
||||
fun canReachHomeServer(callback: (Boolean) -> Unit) {
|
||||
taskExecutor.executorScope.launch {
|
||||
val canReach = canReachHomeServer()
|
||||
callback(canReach)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun canReachHomeServer(): Boolean {
|
||||
return try {
|
||||
executeRequest<Unit>(null) {
|
||||
apiCall = capabilitiesAPI.getVersions()
|
||||
}
|
||||
true
|
||||
} catch (throwable: Throwable) {
|
||||
if (throwable is Failure.OtherServerError) {
|
||||
(throwable.httpCode == 404 || throwable.httpCode == 400)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,12 +38,13 @@ import timber.log.Timber
|
|||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class DefaultPushRuleService @Inject constructor(private val getPushRulesTask: GetPushRulesTask,
|
||||
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
||||
private val addPushRuleTask: AddPushRuleTask,
|
||||
private val removePushRuleTask: RemovePushRuleTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val monarchy: Monarchy
|
||||
internal class DefaultPushRuleService @Inject constructor(
|
||||
private val getPushRulesTask: GetPushRulesTask,
|
||||
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
||||
private val addPushRuleTask: AddPushRuleTask,
|
||||
private val removePushRuleTask: RemovePushRuleTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val monarchy: Monarchy
|
||||
) : PushRuleService {
|
||||
|
||||
private var listeners = mutableSetOf<PushRuleService.PushRuleListener>()
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.notification
|
||||
|
||||
import im.vector.matrix.android.api.pushrules.ConditionResolver
|
||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||
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.room.RoomService
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver
|
||||
import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import timber.log.Timber
|
||||
|
@ -36,7 +35,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
|
|||
|
||||
internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
private val defaultPushRuleService: DefaultPushRuleService,
|
||||
private val roomService: RoomService,
|
||||
private val conditionResolver: ConditionResolver,
|
||||
@UserId private val userId: String
|
||||
) : ProcessEventForPushTask {
|
||||
|
||||
|
@ -97,12 +96,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
|||
}
|
||||
|
||||
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
|
||||
// TODO This should be injected
|
||||
val conditionResolver = DefaultConditionResolver(event, roomService, userId)
|
||||
return rules.firstOrNull { rule ->
|
||||
// All conditions must hold true for an event in order to apply the action for the event.
|
||||
rule.enabled && rule.conditions?.all {
|
||||
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false
|
||||
it.asExecutableCondition()?.isSatisfied(event, conditionResolver) ?: false
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,37 +15,52 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.session.pushers
|
||||
|
||||
import im.vector.matrix.android.api.pushrules.*
|
||||
import im.vector.matrix.android.api.pushrules.ConditionResolver
|
||||
import im.vector.matrix.android.api.pushrules.ContainsDisplayNameCondition
|
||||
import im.vector.matrix.android.api.pushrules.EventMatchCondition
|
||||
import im.vector.matrix.android.api.pushrules.RoomMemberCountCondition
|
||||
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
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.PowerLevelsContent
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import timber.log.Timber
|
||||
import im.vector.matrix.android.internal.session.room.RoomGetter
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO Inject constructor
|
||||
internal class DefaultConditionResolver(private val event: Event,
|
||||
private val roomService: RoomService,
|
||||
@UserId private val userId: String) : ConditionResolver {
|
||||
internal class DefaultConditionResolver @Inject constructor(
|
||||
private val roomGetter: RoomGetter,
|
||||
@UserId private val userId: String
|
||||
) : ConditionResolver {
|
||||
|
||||
override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean {
|
||||
return eventMatchCondition.isSatisfied(event)
|
||||
override fun resolveEventMatchCondition(event: Event,
|
||||
condition: EventMatchCondition): Boolean {
|
||||
return condition.isSatisfied(event)
|
||||
}
|
||||
|
||||
override fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean {
|
||||
return roomMemberCountCondition.isSatisfied(event, roomService)
|
||||
override fun resolveRoomMemberCountCondition(event: Event,
|
||||
condition: RoomMemberCountCondition): Boolean {
|
||||
return condition.isSatisfied(event, roomGetter)
|
||||
}
|
||||
|
||||
override fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean {
|
||||
// val roomId = event.roomId ?: return false
|
||||
// val room = roomService.getRoom(roomId) ?: return false
|
||||
// TODO RoomState not yet managed
|
||||
Timber.e("POWER LEVELS STATE NOT YET MANAGED BY RIOTX")
|
||||
return false // senderNotificationPermissionCondition.isSatisfied(event, )
|
||||
}
|
||||
|
||||
override fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition): Boolean {
|
||||
override fun resolveSenderNotificationPermissionCondition(event: Event,
|
||||
condition: SenderNotificationPermissionCondition): Boolean {
|
||||
val roomId = event.roomId ?: return false
|
||||
val room = roomService.getRoom(roomId) ?: return false
|
||||
val room = roomGetter.getRoom(roomId) ?: return false
|
||||
|
||||
val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||
?.content
|
||||
?.toModel<PowerLevelsContent>()
|
||||
?: PowerLevelsContent()
|
||||
|
||||
return condition.isSatisfied(event, powerLevelsContent)
|
||||
}
|
||||
|
||||
override fun resolveContainsDisplayNameCondition(event: Event,
|
||||
condition: ContainsDisplayNameCondition): Boolean {
|
||||
val roomId = event.roomId ?: return false
|
||||
val room = roomGetter.getRoom(roomId) ?: return false
|
||||
val myDisplayName = room.getRoomMember(userId)?.displayName ?: return false
|
||||
return containsDisplayNameCondition.isSatisfied(event, myDisplayName)
|
||||
return condition.isSatisfied(event, myDisplayName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.lifecycle.Transformations
|
|||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
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.members.MembershipService
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
|
@ -35,10 +36,15 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
|||
import im.vector.matrix.android.api.session.room.typing.TypingService
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import java.security.InvalidParameterException
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||
|
@ -54,7 +60,9 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
private val cryptoService: CryptoService,
|
||||
private val relationService: RelationService,
|
||||
private val roomMembersService: MembershipService,
|
||||
private val roomPushRuleService: RoomPushRuleService) :
|
||||
private val roomPushRuleService: RoomPushRuleService,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val sendStateTask: SendStateTask) :
|
||||
Room,
|
||||
TimelineService by timelineService,
|
||||
SendService by sendService,
|
||||
|
@ -96,11 +104,27 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
return cryptoService.shouldEncryptForInvitedMembers(roomId)
|
||||
}
|
||||
|
||||
override fun enableEncryptionWithAlgorithm(algorithm: String, callback: MatrixCallback<Unit>) {
|
||||
if (isEncrypted()) {
|
||||
callback.onFailure(IllegalStateException("Encryption is already enabled for this room"))
|
||||
} else {
|
||||
stateService.enableEncryption(algorithm, callback)
|
||||
override fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>) {
|
||||
when {
|
||||
isEncrypted() -> {
|
||||
callback.onFailure(IllegalStateException("Encryption is already enabled for this room"))
|
||||
}
|
||||
algorithm != MXCRYPTO_ALGORITHM_MEGOLM -> {
|
||||
callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported"))
|
||||
}
|
||||
else -> {
|
||||
val params = SendStateTask.Params(roomId,
|
||||
EventType.STATE_ROOM_ENCRYPTION,
|
||||
mapOf(
|
||||
"algorithm" to algorithm
|
||||
))
|
||||
|
||||
sendStateTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue