mirror of
https://github.com/element-hq/element-android
synced 2024-10-27 13:17:23 +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:
|
plugins:
|
||||||
- docker#v3.1.0:
|
- docker#v3.1.0:
|
||||||
image: "openjdk"
|
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 files: exclude everything except dictionnaries
|
||||||
.idea/caches
|
.idea/caches
|
||||||
.idea/libraries
|
.idea/libraries
|
||||||
|
.idea/inspectionProfiles
|
||||||
.idea/*.xml
|
.idea/*.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
|
@ -13,7 +14,3 @@
|
||||||
/tmp
|
/tmp
|
||||||
|
|
||||||
ktlint
|
ktlint
|
||||||
.idea/copyright/New_vector.xml
|
|
||||||
.idea/copyright/profiles_settings.xml
|
|
||||||
|
|
||||||
.idea/copyright/New_Vector_Ltd.xml
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="RIGHT_MARGIN" value="160" />
|
||||||
<AndroidXmlCodeStyleSettings>
|
<AndroidXmlCodeStyleSettings>
|
||||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||||
</AndroidXmlCodeStyleSettings>
|
</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)
|
Changes in RiotX 0.14.3 (2020-02-03)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
12
build.gradle
12
build.gradle
|
@ -47,23 +47,11 @@ allprojects {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile).all {
|
|
||||||
options.compilerArgs += [
|
|
||||||
'-Adagger.gradle.incremental=enabled'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
// Warnings are potential errors, so stop ignoring them
|
// Warnings are potential errors, so stop ignoring them
|
||||||
kotlinOptions.allWarningsAsErrors = true
|
kotlinOptions.allWarningsAsErrors = true
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEvaluate {
|
|
||||||
extensions.findByName("kapt")?.arguments {
|
|
||||||
arg("dagger.gradle.incremental", "enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.android.MainThreadDisposable
|
import io.reactivex.android.MainThreadDisposable
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
|
||||||
private class LiveDataObservable<T>(
|
private class LiveDataObservable<T>(
|
||||||
|
@ -60,3 +61,11 @@ private class LiveDataObservable<T>(
|
||||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||||
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
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
|
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.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
import im.vector.matrix.android.api.session.room.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.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
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>> {
|
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||||
val summaryObservable = room.getRoomSummaryLive()
|
return room.getRoomSummaryLive()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.startWith(room.roomSummary().toOptional())
|
.startWithCallable { 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}") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||||
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
|
return room.getRoomMembersLive(queryParams).asObservable()
|
||||||
.startWith(room.getRoomMembers(queryParams))
|
.startWithCallable {
|
||||||
.doOnNext { Timber.v("RX: room members emitted. Size: ${it.size}") }
|
room.getRoomMembers(queryParams)
|
||||||
|
}
|
||||||
// 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}") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||||
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
||||||
.startWith(room.getEventAnnotationsSummary(eventId).toOptional())
|
.startWithCallable {
|
||||||
|
room.getEventAnnotationsSummary(eventId).toOptional()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
||||||
return room.getTimeLineEventLive(eventId).asObservable()
|
return room.getTimeLineEventLive(eventId).asObservable()
|
||||||
.startWith(room.getTimeLineEvent(eventId).toOptional())
|
.startWithCallable {
|
||||||
|
room.getTimeLineEvent(eventId).toOptional()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveStateEvent(eventType: String): Observable<Optional<Event>> {
|
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
|
||||||
return room.getStateEventLive(eventType).asObservable()
|
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||||
.startWith(room.getStateEvent(eventType).toOptional())
|
.startWithCallable {
|
||||||
|
room.getStateEvent(eventType, stateKey).toOptional()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveReadMarker(): Observable<Optional<String>> {
|
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 {
|
fun Room.rx(): RxRoom {
|
||||||
return RxRoom(this, session)
|
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 im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.functions.BiFunction
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
|
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||||
.startWith(session.getRoomSummaries(queryParams))
|
.startWithCallable {
|
||||||
.doOnNext { Timber.v("RX: summaries emitted: size: ${it.size}") }
|
session.getRoomSummaries(queryParams)
|
||||||
|
}
|
||||||
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}") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||||
return session.getGroupSummariesLive(queryParams).asObservable()
|
return session.getGroupSummariesLive(queryParams).asObservable()
|
||||||
.startWith(session.getGroupSummaries(queryParams))
|
.startWithCallable {
|
||||||
|
session.getGroupSummaries(queryParams)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||||
return session.getBreadcrumbsLive().asObservable()
|
return session.getBreadcrumbsLive().asObservable()
|
||||||
.startWith(session.getBreadcrumbs())
|
.startWithCallable {
|
||||||
|
session.getBreadcrumbs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
|
@ -87,7 +67,9 @@ class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||||
return session.getUserLive(userId).asObservable()
|
return session.getUserLive(userId).asObservable()
|
||||||
.startWith(session.getUser(userId).toOptional())
|
.startWithCallable {
|
||||||
|
session.getUser(userId).toOptional()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveUsers(): Observable<List<User>> {
|
fun liveUsers(): Observable<List<User>> {
|
||||||
|
@ -128,12 +110,16 @@ class RxSession(private val session: Session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
|
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>> {
|
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
|
||||||
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable()
|
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 arrow_version = "0.8.2"
|
||||||
def moshi_version = '1.8.0'
|
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 coroutines_version = "1.3.2"
|
||||||
def markwon_version = '3.1.0'
|
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.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
|
@ -104,14 +105,13 @@ dependencies {
|
||||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
||||||
implementation 'com.squareup.retrofit2:converter-moshi:2.6.2'
|
implementation 'com.squareup.retrofit2:converter-moshi:2.6.2'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor: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"
|
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ dependencies {
|
||||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
implementation "androidx.work:work-runtime-ktx:2.3.0"
|
||||||
|
|
||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
|
@ -167,7 +167,7 @@ dependencies {
|
||||||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
// 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 '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"
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,10 @@ class CommonTestHelper(context: Context) {
|
||||||
override fun onTimelineFailure(throwable: Throwable) {
|
override fun onTimelineFailure(throwable: Throwable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
// TODO Count only new messages?
|
// TODO Count only new messages?
|
||||||
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {
|
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {
|
||||||
|
|
|
@ -74,7 +74,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
val room = aliceSession.getRoom(roomId!!)!!
|
val room = aliceSession.getRoom(roomId!!)!!
|
||||||
|
|
||||||
val lock2 = CountDownLatch(1)
|
val lock2 = CountDownLatch(1)
|
||||||
room.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM, object : TestMatrixCallback<Unit>(lock2) {})
|
room.enableEncryption(callback = TestMatrixCallback(lock2))
|
||||||
mTestHelper.await(lock2)
|
mTestHelper.await(lock2)
|
||||||
|
|
||||||
return CryptoTestData(aliceSession, roomId!!)
|
return CryptoTestData(aliceSession, roomId!!)
|
||||||
|
@ -241,6 +241,11 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
|
|
||||||
val bobEventsListener = object : Timeline.Listener {
|
val bobEventsListener = object : Timeline.Listener {
|
||||||
override fun onTimelineFailure(throwable: Throwable) {
|
override fun onTimelineFailure(throwable: Throwable) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||||
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
|
|
|
@ -20,15 +20,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.internal.database.helper.add
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
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.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.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
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.createFakeMessageEvent
|
||||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeRoomMemberEvent
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
|
@ -58,8 +58,11 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
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
|
chunk.timelineEvents.size shouldEqual 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,65 +71,23 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
val fakeEvent = createFakeMessageEvent()
|
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
|
||||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
realm.copyToRealmOrUpdate(it)
|
||||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
}
|
||||||
|
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
|
||||||
|
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
|
||||||
chunk.timelineEvents.size shouldEqual 1
|
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
|
@Test
|
||||||
fun merge_shouldAddEvents_whenMergingBackward() {
|
fun merge_shouldAddEvents_whenMergingBackward() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk1: ChunkEntity = realm.createObject()
|
val chunk1: ChunkEntity = realm.createObject()
|
||||||
val chunk2: ChunkEntity = realm.createObject()
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
|
||||||
chunk1.timelineEvents.size shouldEqual 60
|
chunk1.timelineEvents.size shouldEqual 60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,9 +101,9 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
|
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
|
||||||
chunk1.isLastForward = true
|
chunk1.isLastForward = true
|
||||||
chunk2.isLastForward = false
|
chunk2.isLastForward = false
|
||||||
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
|
chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS)
|
||||||
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
|
chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
|
||||||
chunk1.timelineEvents.size shouldEqual 40
|
chunk1.timelineEvents.size shouldEqual 40
|
||||||
chunk1.isLastForward.shouldBeTrue()
|
chunk1.isLastForward.shouldBeTrue()
|
||||||
}
|
}
|
||||||
|
@ -155,9 +116,9 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
val chunk2: ChunkEntity = realm.createObject()
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
val prevToken = "prev_token"
|
val prevToken = "prev_token"
|
||||||
chunk1.prevToken = prevToken
|
chunk1.prevToken = prevToken
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS)
|
||||||
chunk1.prevToken shouldEqual prevToken
|
chunk1.prevToken shouldEqual prevToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,19 +130,25 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||||
val chunk2: ChunkEntity = realm.createObject()
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
val nextToken = "next_token"
|
val nextToken = "next_token"
|
||||||
chunk1.nextToken = nextToken
|
chunk1.nextToken = nextToken
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
|
||||||
chunk1.nextToken shouldEqual nextToken
|
chunk1.nextToken shouldEqual nextToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ChunkEntity.addAll(roomId: String,
|
private fun ChunkEntity.addAll(roomId: String,
|
||||||
events: List<Event>,
|
events: List<Event>,
|
||||||
direction: PaginationDirection,
|
direction: PaginationDirection) {
|
||||||
stateIndexOffset: Int = 0) {
|
|
||||||
events.forEach { event ->
|
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 com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
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.SessionManager
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
|
@ -35,7 +36,8 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
data class MatrixConfiguration(
|
data class MatrixConfiguration(
|
||||||
val applicationFlavor: String = "Default-application-flavor"
|
val applicationFlavor: String = "Default-application-flavor",
|
||||||
|
val cryptoConfig: MXCryptoConfig = MXCryptoConfig()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
interface Provider {
|
interface Provider {
|
||||||
|
@ -57,12 +59,11 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Monarchy.init(context)
|
Monarchy.init(context)
|
||||||
DaggerMatrixComponent.factory().create(context).inject(this)
|
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
||||||
if (context.applicationContext !is Configuration.Provider) {
|
if (context.applicationContext !is Configuration.Provider) {
|
||||||
WorkManager.initialize(context, Configuration.Builder().build())
|
WorkManager.initialize(context, Configuration.Builder().build())
|
||||||
}
|
}
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||||
userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserAgent() = userAgentHolder.userAgent
|
fun getUserAgent() = userAgentHolder.userAgent
|
||||||
|
|
|
@ -38,3 +38,8 @@ interface MatrixCallback<in T> {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic no op implementation
|
||||||
|
*/
|
||||||
|
class NoOpMatrixCallback<T>: MatrixCallback<T>
|
||||||
|
|
|
@ -14,14 +14,14 @@
|
||||||
* limitations under the License.
|
* 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.
|
* Class to define the parameters used to customize or configure the end-to-end crypto.
|
||||||
*/
|
*/
|
||||||
data class MXCryptoConfig(
|
data class MXCryptoConfig(
|
||||||
// Tell whether the encryption of the event content is enabled for the invited members.
|
// Tell whether the encryption of the event content is enabled for the invited members.
|
||||||
// By default, we encrypt messages only for the joined members.
|
// SDK clients can disable this by settings it to false.
|
||||||
// The encryption for the invited members will be blocked if the history visibility is "joined".
|
// Note that the encryption for the invited members will be blocked if the history visibility is "joined".
|
||||||
var enableEncryptionForInvitedMembers: Boolean = false
|
var enableEncryptionForInvitedMembers: Boolean = true
|
||||||
)
|
)
|
|
@ -15,30 +15,32 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.api.pushrules
|
package im.vector.matrix.android.api.pushrules
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
abstract class Condition(val kind: Kind) {
|
abstract class Condition(val kind: Kind) {
|
||||||
|
|
||||||
enum class Kind(val value: String) {
|
enum class Kind(val value: String) {
|
||||||
event_match("event_match"),
|
EventMatch("event_match"),
|
||||||
contains_display_name("contains_display_name"),
|
ContainsDisplayName("contains_display_name"),
|
||||||
room_member_count("room_member_count"),
|
RoomMemberCount("room_member_count"),
|
||||||
sender_notification_permission("sender_notification_permission"),
|
SenderNotificationPermission("sender_notification_permission"),
|
||||||
UNRECOGNIZE("");
|
Unrecognised("");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun fromString(value: String): Kind {
|
fun fromString(value: String): Kind {
|
||||||
return when (value) {
|
return when (value) {
|
||||||
"event_match" -> event_match
|
"event_match" -> EventMatch
|
||||||
"contains_display_name" -> contains_display_name
|
"contains_display_name" -> ContainsDisplayName
|
||||||
"room_member_count" -> room_member_count
|
"room_member_count" -> RoomMemberCount
|
||||||
"sender_notification_permission" -> sender_notification_permission
|
"sender_notification_permission" -> SenderNotificationPermission
|
||||||
else -> UNRECOGNIZE
|
else -> Unrecognised
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean
|
abstract fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean
|
||||||
|
|
||||||
open fun technicalDescription(): String {
|
open fun technicalDescription(): String {
|
||||||
return "Kind: $kind"
|
return "Kind: $kind"
|
||||||
|
|
|
@ -15,14 +15,22 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.api.pushrules
|
package im.vector.matrix.android.api.pushrules
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Acts like a visitor on Conditions.
|
* Acts like a visitor on Conditions.
|
||||||
* This class as all required context needed to evaluate rules
|
* This class as all required context needed to evaluate rules
|
||||||
*/
|
*/
|
||||||
interface ConditionResolver {
|
interface ConditionResolver {
|
||||||
|
fun resolveEventMatchCondition(event: Event,
|
||||||
|
condition: EventMatchCondition): Boolean
|
||||||
|
|
||||||
fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean
|
fun resolveRoomMemberCountCondition(event: Event,
|
||||||
fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean
|
condition: RoomMemberCountCondition): Boolean
|
||||||
fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean
|
|
||||||
fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : 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 im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
class ContainsDisplayNameCondition : Condition(Kind.ContainsDisplayName) {
|
||||||
|
|
||||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||||
return conditionResolver.resolveContainsDisplayNameCondition(this)
|
return conditionResolver.resolveContainsDisplayNameCondition(event, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun technicalDescription(): String {
|
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 im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import timber.log.Timber
|
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 {
|
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||||
return conditionResolver.resolveEventMatchCondition(this)
|
return conditionResolver.resolveEventMatchCondition(event, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun technicalDescription(): String {
|
override fun technicalDescription(): String {
|
||||||
|
|
|
@ -16,25 +16,32 @@
|
||||||
package im.vector.matrix.android.api.pushrules
|
package im.vector.matrix.android.api.pushrules
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.internal.session.room.RoomGetter
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
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 {
|
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||||
return conditionResolver.resolveRoomMemberCountCondition(this)
|
return conditionResolver.resolveRoomMemberCountCondition(event, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun technicalDescription(): String {
|
override fun technicalDescription(): String {
|
||||||
return "Room member count is $iz"
|
return "Room member count is $iz"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
internal fun isSatisfied(event: Event, roomGetter: RoomGetter): Boolean {
|
||||||
// sanity check^
|
// sanity checks
|
||||||
val roomId = event.roomId ?: return false
|
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
|
// Parse the is field into prefix and number the first time
|
||||||
val (prefix, count) = parseIsField() ?: return false
|
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
|
val (prefix, count) = match.destructured
|
||||||
return prefix to count.toInt()
|
return prefix to count.toInt()
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Timber.d(t)
|
Timber.e(t, "Unable to parse 'is' field")
|
||||||
}
|
}
|
||||||
return null
|
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.model.PowerLevelsContent
|
||||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
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 {
|
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||||
return conditionResolver.resolveSenderNotificationPermissionCondition(this)
|
return conditionResolver.resolveSenderNotificationPermissionCondition(event, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun technicalDescription(): String {
|
override fun technicalDescription(): String {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
|
||||||
* All push rulesets for a user.
|
* All push rulesets for a user.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class GetPushRulesResponse(
|
internal data class GetPushRulesResponse(
|
||||||
/**
|
/**
|
||||||
* Global rules, account level applying to all devices
|
* 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.Json
|
||||||
import com.squareup.moshi.JsonClass
|
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
|
import timber.log.Timber
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@ -30,13 +34,13 @@ data class PushCondition(
|
||||||
/**
|
/**
|
||||||
* Required for event_match conditions. The dot- separated field of the event to match.
|
* Required for event_match conditions. The dot- separated field of the event to match.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
/**
|
|
||||||
*Required for event_match conditions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required for event_match conditions.
|
||||||
|
*/
|
||||||
val pattern: String? = null,
|
val pattern: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required for room_member_count conditions.
|
* Required for room_member_count conditions.
|
||||||
* A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
|
* A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
|
||||||
|
@ -47,30 +51,35 @@ data class PushCondition(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun asExecutableCondition(): Condition? {
|
fun asExecutableCondition(): Condition? {
|
||||||
return when (Condition.Kind.fromString(this.kind)) {
|
return when (Condition.Kind.fromString(kind)) {
|
||||||
Condition.Kind.event_match -> {
|
Condition.Kind.EventMatch -> {
|
||||||
if (this.key != null && this.pattern != null) {
|
if (key != null && pattern != null) {
|
||||||
EventMatchCondition(key, pattern)
|
EventMatchCondition(key, pattern)
|
||||||
} else {
|
} else {
|
||||||
Timber.e("Malformed Event match condition")
|
Timber.e("Malformed Event match condition")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Condition.Kind.contains_display_name -> {
|
Condition.Kind.ContainsDisplayName -> {
|
||||||
ContainsDisplayNameCondition()
|
ContainsDisplayNameCondition()
|
||||||
}
|
}
|
||||||
Condition.Kind.room_member_count -> {
|
Condition.Kind.RoomMemberCount -> {
|
||||||
if (this.iz.isNullOrBlank()) {
|
if (iz.isNullOrEmpty()) {
|
||||||
Timber.e("Malformed ROOM_MEMBER_COUNT condition")
|
Timber.e("Malformed ROOM_MEMBER_COUNT condition")
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
RoomMemberCountCondition(this.iz)
|
RoomMemberCountCondition(iz)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Condition.Kind.sender_notification_permission -> {
|
Condition.Kind.SenderNotificationPermission -> {
|
||||||
this.key?.let { SenderNotificationPermissionCondition(it) }
|
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")
|
Timber.e("Unknown kind $kind")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.api.pushrules.rest
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Ruleset(
|
internal data class Ruleset(
|
||||||
val content: List<PushRule>? = null,
|
val content: List<PushRule>? = null,
|
||||||
val override: List<PushRule>? = null,
|
val override: List<PushRule>? = null,
|
||||||
val room: 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 androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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.api.util.Optional
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
||||||
|
@ -63,6 +62,4 @@ interface CrossSigningService {
|
||||||
fun checkDeviceTrust(otherUserId: String,
|
fun checkDeviceTrust(otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String,
|
||||||
locallyTrusted: Boolean?): DeviceTrustResult
|
locallyTrusted: Boolean?): DeviceTrustResult
|
||||||
|
|
||||||
fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,7 @@ data class Event(
|
||||||
|
|
||||||
fun Event.isTextMessage(): Boolean {
|
fun Event.isTextMessage(): Boolean {
|
||||||
return getClearType() == EventType.MESSAGE
|
return getClearType() == EventType.MESSAGE
|
||||||
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||||
MessageType.MSGTYPE_TEXT,
|
MessageType.MSGTYPE_TEXT,
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
MessageType.MSGTYPE_NOTICE -> true
|
MessageType.MSGTYPE_NOTICE -> true
|
||||||
|
@ -210,7 +210,7 @@ fun Event.isTextMessage(): Boolean {
|
||||||
|
|
||||||
fun Event.isImageMessage(): Boolean {
|
fun Event.isImageMessage(): Boolean {
|
||||||
return getClearType() == EventType.MESSAGE
|
return getClearType() == EventType.MESSAGE
|
||||||
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||||
MessageType.MSGTYPE_IMAGE -> true
|
MessageType.MSGTYPE_IMAGE -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.api.session.room.crypto
|
package im.vector.matrix.android.api.session.room.crypto
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
|
||||||
interface RoomCryptoService {
|
interface RoomCryptoService {
|
||||||
|
|
||||||
|
@ -26,5 +27,9 @@ interface RoomCryptoService {
|
||||||
|
|
||||||
fun shouldEncryptForInvitedMembers(): Boolean
|
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
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
|
||||||
|
*/
|
||||||
enum class RoomHistoryVisibility {
|
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,
|
@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 = "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
|
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
|
* 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 membership: Membership,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val displayName: String? = null,
|
val displayName: String? = null,
|
||||||
val avatarUrl: String? = null,
|
val avatarUrl: String? = null
|
||||||
// TODO Warning: Will not be populated if not using RxRoom
|
|
||||||
val userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,9 +25,9 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageAudioContent(
|
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'.
|
* 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,
|
@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,
|
@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
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
interface MessageContent {
|
interface MessageContent {
|
||||||
// TODO Rename to msgType
|
val msgType: String
|
||||||
val type: String
|
|
||||||
val body: String
|
val body: String
|
||||||
val relatesTo: RelationDefaultContent?
|
val relatesTo: RelationDefaultContent?
|
||||||
val newContent: Content?
|
val newContent: Content?
|
||||||
|
|
|
@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageDefaultContent(
|
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 = "body") override val body: String,
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = 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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageEmoteContent(
|
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,
|
@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,
|
@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 = "formatted_body") val formattedBody: String? = null,
|
||||||
|
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
) : MessageContent
|
) : MessageContent
|
||||||
|
|
|
@ -23,10 +23,13 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
*/
|
*/
|
||||||
interface MessageEncryptedContent : MessageContent {
|
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?
|
val url: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
||||||
|
*/
|
||||||
val encryptedFileInfo: EncryptedFileInfo?
|
val encryptedFileInfo: EncryptedFileInfo?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,9 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageFileContent(
|
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.
|
* 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,
|
@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 = "url") override val url: String? = null,
|
||||||
|
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = 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
|
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||||
) : MessageEncryptedContent {
|
) : 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,10 +14,8 @@
|
||||||
* limitations under the License.
|
* 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(
|
object MessageFormat {
|
||||||
val filename: String,
|
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
|
||||||
val fileSizeInBytes: Long,
|
}
|
||||||
val homeServerLimitInBytes: Long
|
|
||||||
)
|
|
|
@ -27,7 +27,7 @@ data class MessageImageContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.image'.
|
* 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,
|
* 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,
|
@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,
|
@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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageLocationContent(
|
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'.
|
* 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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageNoticeContent(
|
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,
|
@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,
|
@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 = "formatted_body") val formattedBody: String? = null,
|
||||||
|
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
) : MessageContent
|
) : MessageContent
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model.message
|
package im.vector.matrix.android.api.session.room.model.message
|
||||||
|
@ -28,7 +27,7 @@ data class MessageStickerContent(
|
||||||
/**
|
/**
|
||||||
* Set in local, not from server
|
* 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,
|
* 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,
|
@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,
|
@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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageTextContent(
|
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,
|
@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,
|
@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 = "formatted_body") val formattedBody: String? = null,
|
||||||
|
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
) : MessageContent
|
) : MessageContent
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package im.vector.matrix.android.api.session.room.model.message
|
package im.vector.matrix.android.api.session.room.model.message
|
||||||
|
|
||||||
object MessageType {
|
object MessageType {
|
||||||
|
|
||||||
const val MSGTYPE_TEXT = "m.text"
|
const val MSGTYPE_TEXT = "m.text"
|
||||||
const val MSGTYPE_EMOTE = "m.emote"
|
const val MSGTYPE_EMOTE = "m.emote"
|
||||||
const val MSGTYPE_NOTICE = "m.notice"
|
const val MSGTYPE_NOTICE = "m.notice"
|
||||||
|
@ -27,7 +26,6 @@ object MessageType {
|
||||||
const val MSGTYPE_LOCATION = "m.location"
|
const val MSGTYPE_LOCATION = "m.location"
|
||||||
const val MSGTYPE_FILE = "m.file"
|
const val MSGTYPE_FILE = "m.file"
|
||||||
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
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
|
// 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
|
// Because sticker isn't a message type but a event type without msgtype field
|
||||||
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageVerificationRequestContent(
|
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 = "body") override val body: String,
|
||||||
@Json(name = "from_device") override val fromDevice: String?,
|
@Json(name = "from_device") override val fromDevice: String?,
|
||||||
@Json(name = "methods") override val methods: List<String>,
|
@Json(name = "methods") override val methods: List<String>,
|
||||||
|
|
|
@ -27,7 +27,7 @@ data class MessageVideoContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.video'.
|
* 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'.
|
* 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,
|
@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,
|
@Json(name = "url") override val url: String? = null,
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,16 @@ import im.vector.matrix.android.api.util.Optional
|
||||||
*/
|
*/
|
||||||
interface ReadService {
|
interface ReadService {
|
||||||
|
|
||||||
|
enum class MarkAsReadParams {
|
||||||
|
READ_RECEIPT,
|
||||||
|
READ_MARKER,
|
||||||
|
BOTH
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force the read marker to be set on the latest event.
|
* 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.
|
* Set the read receipt on the event with provided eventId.
|
||||||
|
|
|
@ -17,18 +17,20 @@
|
||||||
package im.vector.matrix.android.api.session.room.send
|
package im.vector.matrix.android.api.session.room.send
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
interface DraftService {
|
interface DraftService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save or update a draft to the room
|
* 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
|
* 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
|
* Return the current drafts if any, as a live data
|
||||||
|
|
|
@ -28,12 +28,7 @@ interface StateService {
|
||||||
*/
|
*/
|
||||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
/**
|
fun getStateEvent(eventType: String, stateKey: String): Event?
|
||||||
* Enable encryption of the room
|
|
||||||
*/
|
|
||||||
fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
fun getStateEvent(eventType: String): Event?
|
fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>>
|
||||||
|
|
||||||
fun getStateEventLive(eventType: String): LiveData<Optional<Event>>
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,11 @@ interface Timeline {
|
||||||
* Called whenever an error we can't recover from occurred
|
* Called whenever an error we can't recover from occurred
|
||||||
*/
|
*/
|
||||||
fun onTimelineFailure(throwable: Throwable)
|
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(
|
data class TimelineEvent(
|
||||||
val root: Event,
|
val root: Event,
|
||||||
val localId: Long,
|
val localId: Long,
|
||||||
|
val eventId: String,
|
||||||
val displayIndex: Int,
|
val displayIndex: Int,
|
||||||
val senderName: String?,
|
val senderName: String?,
|
||||||
val isUniqueDisplayName: Boolean,
|
val isUniqueDisplayName: Boolean,
|
||||||
|
|
|
@ -19,10 +19,11 @@ package im.vector.matrix.android.internal.crypto
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
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.CryptoService
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
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.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.crosssigning.DefaultCrossSigningService
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
|
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
||||||
|
@ -137,15 +138,6 @@ internal abstract class CryptoModule {
|
||||||
return RealmClearCacheTask(realmConfiguration)
|
return RealmClearCacheTask(realmConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Provides
|
|
||||||
fun providesCryptoStore(@CryptoDatabase
|
|
||||||
realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore {
|
|
||||||
return RealmCryptoStore(
|
|
||||||
realmConfiguration,
|
|
||||||
credentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
@ -159,13 +151,6 @@ internal abstract class CryptoModule {
|
||||||
fun providesRoomKeysAPI(retrofit: Retrofit): RoomKeysApi {
|
fun providesRoomKeysAPI(retrofit: Retrofit): RoomKeysApi {
|
||||||
return retrofit.create(RoomKeysApi::class.java)
|
return retrofit.create(RoomKeysApi::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Provides
|
|
||||||
@SessionScope
|
|
||||||
fun providesCryptoConfig(): MXCryptoConfig {
|
|
||||||
return MXCryptoConfig()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
|
@ -256,4 +241,10 @@ internal abstract class CryptoModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
|
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 com.zhuinden.monarchy.Monarchy
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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.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.failure.Failure
|
||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
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.crypto.verification.DefaultVerificationService
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
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.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.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
@ -116,7 +118,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
// Olm device
|
// Olm device
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
// Set of parameters used to configure/customize the end-to-end crypto.
|
// Set of parameters used to configure/customize the end-to-end crypto.
|
||||||
private val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
|
private val mxCryptoConfig: MXCryptoConfig,
|
||||||
// Device list manager
|
// Device list manager
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
// The key backup service.
|
// The key backup service.
|
||||||
|
@ -189,7 +191,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
// bg refresh of crypto device
|
// bg refresh of crypto device
|
||||||
downloadKeys(listOf(credentials.userId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {})
|
downloadKeys(listOf(credentials.userId), true, NoOpMatrixCallback())
|
||||||
callback.onSuccess(data)
|
callback.onSuccess(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,6 +401,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
|
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
|
||||||
}
|
}
|
||||||
|
@ -531,7 +534,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
*/
|
*/
|
||||||
override fun isRoomEncrypted(roomId: String): Boolean {
|
override fun isRoomEncrypted(roomId: String): Boolean {
|
||||||
val encryptionEvent = monarchy.fetchCopied { realm ->
|
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\"")
|
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
||||||
.findFirst()
|
.findFirst()
|
||||||
}
|
}
|
||||||
|
@ -545,8 +548,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
|
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isEncryptionEnabledForInvitedUser(): Boolean {
|
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||||
return cryptoConfig.enableEncryptionForInvitedMembers
|
return mxCryptoConfig.enableEncryptionForInvitedMembers
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEncryptionAlgorithm(roomId: String): String? {
|
override fun getEncryptionAlgorithm(roomId: String): String? {
|
||||||
|
@ -779,7 +782,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
deviceListManager.startTrackingDeviceList(listOf(userId))
|
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||||
} else if (membership == Membership.INVITE
|
} else if (membership == Membership.INVITE
|
||||||
&& shouldEncryptForInvitedMembers(roomId)
|
&& shouldEncryptForInvitedMembers(roomId)
|
||||||
&& cryptoConfig.enableEncryptionForInvitedMembers) {
|
&& isEncryptionEnabledForInvitedUser()) {
|
||||||
// track the deviceList for this invited user.
|
// track the deviceList for this invited user.
|
||||||
// Caution: there's a big edge case here in that federated servers do not
|
// 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.
|
// 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.crypto.tasks.DownloadKeysForUsersTask
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
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 timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -36,10 +39,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val syncTokenStore: SyncTokenStore,
|
private val syncTokenStore: SyncTokenStore,
|
||||||
private val credentials: Credentials,
|
private val credentials: Credentials,
|
||||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
|
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
|
||||||
|
coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
taskExecutor: TaskExecutor) {
|
||||||
|
|
||||||
interface UserDevicesUpdateListener {
|
interface UserDevicesUpdateListener {
|
||||||
fun onUsersDeviceUpdate(users: List<String>)
|
fun onUsersDeviceUpdate(userIds: List<String>)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
|
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
|
||||||
|
@ -72,17 +77,19 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
private val notReadyToRetryHS = mutableSetOf<String>()
|
private val notReadyToRetryHS = mutableSetOf<String>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
var isUpdated = false
|
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
|
||||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
var isUpdated = false
|
||||||
for ((userId, status) in deviceTrackingStatuses) {
|
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||||
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
|
for ((userId, status) in deviceTrackingStatuses) {
|
||||||
// if a download was in progress when we got shut down, it isn't any more.
|
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
|
||||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
// if a download was in progress when we got shut down, it isn't any more.
|
||||||
isUpdated = true
|
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 =
|
// al devices =
|
||||||
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
|
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
|
||||||
|
|
||||||
|
|
||||||
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
|
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
|
||||||
if (!models.isNullOrEmpty()) {
|
if (!models.isNullOrEmpty()) {
|
||||||
val workingCopy = models.toMutableMap()
|
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 androidx.lifecycle.LiveData
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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.CrossSigningService
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import im.vector.matrix.android.api.util.Optional
|
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.crypto.tasks.UploadSigningKeysTask
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
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.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.TaskThread
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.withoutPrefix
|
import im.vector.matrix.android.internal.util.withoutPrefix
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.olm.OlmPkSigning
|
import org.matrix.olm.OlmPkSigning
|
||||||
import org.matrix.olm.OlmUtility
|
import org.matrix.olm.OlmUtility
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -57,9 +57,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
||||||
private val uploadSignaturesTask: UploadSignaturesTask,
|
private val uploadSignaturesTask: UploadSignaturesTask,
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
private val computeTrustTask: ComputeTrustTask,
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
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
|
private var olmUtility: OlmUtility? = null
|
||||||
|
|
||||||
|
@ -211,7 +213,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
|
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
|
||||||
|
|
||||||
uploadSigningKeysTask.configureWith(params) {
|
uploadSigningKeysTask.configureWith(params) {
|
||||||
this.constraints = TaskConstraints(true)
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
Timber.i("## CrossSigning - Keys successfully uploaded")
|
Timber.i("## CrossSigning - Keys successfully uploaded")
|
||||||
|
@ -247,7 +249,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
resetTrustOnKeyChange()
|
resetTrustOnKeyChange()
|
||||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
|
||||||
// this.retryCount = 3
|
// this.retryCount = 3
|
||||||
this.constraints = TaskConstraints(true)
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
Timber.i("## CrossSigning - signatures successfully uploaded")
|
Timber.i("## CrossSigning - signatures successfully uploaded")
|
||||||
|
@ -396,7 +398,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
return@forEach
|
return@forEach
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// log
|
// 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))
|
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
|
||||||
.build()
|
.build()
|
||||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||||
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}.executeBy(taskExecutor)
|
}.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -546,6 +549,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
.withDeviceInfo(toUpload)
|
.withDeviceInfo(toUpload)
|
||||||
.build()
|
.build()
|
||||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||||
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}.executeBy(taskExecutor)
|
}.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -611,81 +615,52 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUsersDeviceUpdate(users: List<String>) {
|
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
||||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users")
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
users.forEach { otherUserId ->
|
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 {
|
// TODO if my keys have changes, i should recheck all devices of all users?
|
||||||
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
|
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
||||||
setUserKeysAsTrusted(otherUserId, it.isVerified())
|
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?
|
if (otherUserId == userId) {
|
||||||
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
// It's me, i should check if a newly trusted device is signing my master key
|
||||||
devices?.forEach { device ->
|
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
||||||
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
||||||
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) {
|
eventBus.post(CryptoToSessionUserTrustChange(userIds))
|
||||||
// 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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||||
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
||||||
|
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
||||||
// If it's me, recheck trust of all users and devices?
|
// If it's me, recheck trust of all users and devices?
|
||||||
val users = ArrayList<String>()
|
val users = ArrayList<String>()
|
||||||
if (otherUserId == userId && currentTrust != trusted) {
|
if (otherUserId == userId && currentTrust != trusted) {
|
||||||
cryptoStore.updateUsersTrust {
|
cryptoStore.updateUsersTrust {
|
||||||
users.add(it)
|
users.add(it)
|
||||||
checkUserTrust(it).isVerified()
|
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())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
|
users.forEach {
|
||||||
val allTrusted = userIds
|
cryptoStore.getUserDeviceList(it)?.forEach { device ->
|
||||||
.filter { getUserCrossSigningKeys(it)?.isTrusted() == true }
|
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||||
|
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||||
val allUsersAreVerified = userIds.size == allTrusted.size
|
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||||
|
}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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 CryptoToSessionUserTrustChange(
|
||||||
|
val userIds: List<String>
|
||||||
data class DownloadFileState(
|
)
|
||||||
val mimeType: String,
|
|
||||||
val file: File?,
|
|
||||||
val throwable: Throwable?
|
|
||||||
)
|
|
|
@ -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)
|
olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySignature)
|
||||||
isSignatureValid = true
|
isSignatureValid = true
|
||||||
} catch (e: OlmException) {
|
} 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.get
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.query.getById
|
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.crypto.store.db.query.getOrCreate
|
||||||
|
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
@ -70,11 +71,13 @@ import io.realm.kotlin.where
|
||||||
import org.matrix.olm.OlmAccount
|
import org.matrix.olm.OlmAccount
|
||||||
import org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class RealmCryptoStore(private val realmConfiguration: RealmConfiguration,
|
internal class RealmCryptoStore @Inject constructor(
|
||||||
private val credentials: Credentials) : IMXCryptoStore {
|
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
|
||||||
|
private val credentials: Credentials) : IMXCryptoStore {
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Memory cache, to correctly release JNI objects
|
* Memory cache, to correctly release JNI objects
|
||||||
|
@ -403,14 +406,14 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
||||||
{ realm: Realm ->
|
{ realm: Realm ->
|
||||||
realm
|
realm
|
||||||
.where<UserEntity>()
|
.where<UserEntity>()
|
||||||
.`in`(UserEntityFields.USER_ID, userIds.toTypedArray())
|
.`in`(UserEntityFields.USER_ID, userIds.distinct().toTypedArray())
|
||||||
},
|
},
|
||||||
{ entity ->
|
{ entity ->
|
||||||
entity.devices.map { CryptoMapper.mapToModel(it) }
|
entity.devices.map { CryptoMapper.mapToModel(it) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return Transformations.map(liveData) {
|
return Transformations.map(liveData) {
|
||||||
it.firstOrNull() ?: emptyList()
|
it.flatten()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||||
// done from another device of mine
|
// done from another device of mine
|
||||||
|
|
||||||
if (EventType.MESSAGE == event.type) {
|
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) {
|
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
|
||||||
event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
|
event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
|
||||||
if (it.fromDevice != deviceId) {
|
if (it.fromDevice != deviceId) {
|
||||||
|
@ -144,7 +144,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||||
params.verificationService.onRoomEvent(event)
|
params.verificationService.onRoomEvent(event)
|
||||||
}
|
}
|
||||||
EventType.MESSAGE -> {
|
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)
|
params.verificationService.onRoomRequestReceived(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,9 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
|
||||||
override suspend fun execute(params: UploadSignaturesTask.Params) {
|
override suspend fun execute(params: UploadSignaturesTask.Params) {
|
||||||
try {
|
try {
|
||||||
val response = executeRequest<SignatureUploadResponse>(eventBus) {
|
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) {
|
if (response.failures?.isNotEmpty() == true) {
|
||||||
throw Throwable(response.failures.toString())
|
throw Throwable(response.failures.toString())
|
||||||
|
|
|
@ -165,7 +165,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
onRoomDoneReceived(event)
|
onRoomDoneReceived(event)
|
||||||
}
|
}
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.type) {
|
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
|
||||||
onRoomRequestReceived(event)
|
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.RealmLiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
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.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.di.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
@ -42,7 +42,7 @@ internal class VerificationMessageLiveObserver @Inject constructor(
|
||||||
) : RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
) : RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
||||||
|
|
||||||
override val query = Monarchy.Query {
|
override val query = Monarchy.Query {
|
||||||
EventEntity.types(it, listOf(
|
EventEntity.whereTypes(it, listOf(
|
||||||
EventType.KEY_VERIFICATION_START,
|
EventType.KEY_VERIFICATION_START,
|
||||||
EventType.KEY_VERIFICATION_ACCEPT,
|
EventType.KEY_VERIFICATION_ACCEPT,
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
|
|
|
@ -331,7 +331,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
content = content,
|
content = content,
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||||
).also {
|
).also {
|
||||||
localEchoEventFactory.saveLocalEcho(monarchy, it)
|
localEchoEventFactory.createLocalEcho(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,19 +16,27 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database.helper
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.*
|
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.find
|
||||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import io.realm.Realm
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
internal fun ChunkEntity.deleteOnCascade() {
|
internal fun ChunkEntity.deleteOnCascade() {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
|
@ -36,116 +44,154 @@ internal fun ChunkEntity.deleteOnCascade() {
|
||||||
this.deleteFromRealm()
|
this.deleteFromRealm()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.merge(roomId: String,
|
internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direction: PaginationDirection) {
|
||||||
chunkToMerge: ChunkEntity,
|
|
||||||
direction: PaginationDirection): List<TimelineEventEntity> {
|
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked
|
val localRealm = this.realm
|
||||||
val isCurrentChunkUnlinked = isUnlinked
|
|
||||||
|
|
||||||
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
|
||||||
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
|
||||||
}
|
|
||||||
val eventsToMerge: List<TimelineEventEntity>
|
val eventsToMerge: List<TimelineEventEntity>
|
||||||
if (direction == PaginationDirection.FORWARDS) {
|
if (direction == PaginationDirection.FORWARDS) {
|
||||||
this.nextToken = chunkToMerge.nextToken
|
this.nextToken = chunkToMerge.nextToken
|
||||||
this.isLastForward = chunkToMerge.isLastForward
|
this.isLastForward = chunkToMerge.isLastForward
|
||||||
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
} else {
|
} else {
|
||||||
this.prevToken = chunkToMerge.prevToken
|
this.prevToken = chunkToMerge.prevToken
|
||||||
this.isLastBackward = chunkToMerge.isLastBackward
|
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
|
return eventsToMerge
|
||||||
.mapNotNull {
|
.forEach {
|
||||||
val event = it.root?.asDomain() ?: return@mapNotNull null
|
addTimelineEventFromMerge(localRealm, it, direction)
|
||||||
add(roomId, event, direction)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.add(roomId: String,
|
internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) {
|
||||||
event: Event,
|
if (direction == PaginationDirection.BACKWARDS) {
|
||||||
direction: PaginationDirection,
|
Timber.v("We don't keep chunk state events when paginating backward")
|
||||||
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
|
|
||||||
} else {
|
} else {
|
||||||
currentDisplayIndex -= 1
|
val stateKey = stateEvent.stateKey ?: return
|
||||||
backwardsDisplayIndex = currentDisplayIndex
|
val type = stateEvent.type
|
||||||
}
|
val pastStateEvent = stateEvents.where()
|
||||||
var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset)
|
.equalTo(EventEntityFields.ROOM_ID, roomId)
|
||||||
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.type)) {
|
.equalTo(EventEntityFields.STATE_KEY, stateKey)
|
||||||
currentStateIndex += 1
|
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
||||||
forwardsStateIndex = currentStateIndex
|
.findFirst()
|
||||||
} else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) {
|
|
||||||
val lastEventType = timelineEvents.last()?.root?.type ?: ""
|
if (pastStateEvent != null) {
|
||||||
if (EventType.isStateEvent(lastEventType)) {
|
stateEvents.remove(pastStateEvent)
|
||||||
currentStateIndex -= 1
|
|
||||||
backwardsStateIndex = currentStateIndex
|
|
||||||
}
|
}
|
||||||
|
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 localId = TimelineEventEntity.nextId(realm)
|
||||||
val eventId = event.eventId ?: ""
|
val senderId = eventEntity.sender ?: ""
|
||||||
val senderId = event.senderId ?: ""
|
|
||||||
|
|
||||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
|
||||||
?: realm.createObject<ReadReceiptsSummaryEntity>(eventId).apply {
|
|
||||||
this.roomId = roomId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update RR for the sender of a new message with a dummy one
|
// Update RR for the sender of a new message with a dummy one
|
||||||
|
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) {
|
private fun computeIsUnique(
|
||||||
val timestampOfEvent = event.originServerTs.toDouble()
|
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)
|
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId)
|
||||||
// If the synced RR is older, update
|
// If the synced RR is older, update
|
||||||
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
|
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
|
||||||
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
|
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
|
||||||
readReceiptOfSender.eventId = eventId
|
readReceiptOfSender.eventId = eventEntity.eventId
|
||||||
readReceiptOfSender.originServerTs = timestampOfEvent
|
readReceiptOfSender.originServerTs = timestampOfEvent
|
||||||
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
|
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
|
||||||
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
|
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return readReceiptsSummaryEntity
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int {
|
||||||
return when (direction) {
|
return when (direction) {
|
||||||
PaginationDirection.FORWARDS -> forwardsDisplayIndex
|
PaginationDirection.FORWARDS -> {
|
||||||
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
|
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1
|
||||||
} ?: defaultValue
|
}
|
||||||
}
|
PaginationDirection.BACKWARDS -> {
|
||||||
|
(timelineEvents.where().min(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) - 1
|
||||||
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
}
|
||||||
return when (direction) {
|
}
|
||||||
PaginationDirection.FORWARDS -> forwardsStateIndex
|
|
||||||
PaginationDirection.BACKWARDS -> backwardsStateIndex
|
|
||||||
} ?: defaultValue
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,8 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database.helper
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.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.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
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) {
|
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
|
||||||
chunks.remove(chunkEntity)
|
chunks.remove(chunkEntity)
|
||||||
|
@ -36,39 +29,3 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
|
||||||
chunks.add(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.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
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.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
@ -43,7 +44,6 @@ internal object EventMapper {
|
||||||
eventEntity.redacts = event.redacts
|
eventEntity.redacts = event.redacts
|
||||||
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
|
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
|
||||||
eventEntity.unsignedData = uds
|
eventEntity.unsignedData = uds
|
||||||
eventEntity.ageLocalTs = event.ageLocalTs
|
|
||||||
return eventEntity
|
return eventEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,9 @@ internal fun EventEntity.asDomain(): Event {
|
||||||
return EventMapper.map(this)
|
return EventMapper.map(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Event.toEntity(roomId: String): EventEntity {
|
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): EventEntity {
|
||||||
return EventMapper.map(this, roomId)
|
return EventMapper.map(this, roomId).apply {
|
||||||
|
this.sendState = sendState
|
||||||
|
this.ageLocalTs = ageLocalTs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ internal object PushRulesMapper {
|
||||||
enabled = pushrule.enabled,
|
enabled = pushrule.enabled,
|
||||||
ruleId = pushrule.ruleId,
|
ruleId = pushrule.ruleId,
|
||||||
conditions = listOf(
|
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,
|
enabled = pushrule.enabled,
|
||||||
ruleId = pushrule.ruleId,
|
ruleId = pushrule.ruleId,
|
||||||
conditions = listOf(
|
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,
|
enabled = pushrule.enabled,
|
||||||
ruleId = pushrule.ruleId,
|
ruleId = pushrule.ruleId,
|
||||||
conditions = listOf(
|
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,
|
userId = roomMemberSummaryEntity.userId,
|
||||||
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
||||||
displayName = roomMemberSummaryEntity.displayName,
|
displayName = roomMemberSummaryEntity.displayName,
|
||||||
membership = roomMemberSummaryEntity.membership,
|
membership = roomMemberSummaryEntity.membership
|
||||||
userEncryptionTrustLevel = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
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.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.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
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.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -49,7 +49,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
||||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
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(),
|
aliases = roomSummaryEntity.aliases.toList(),
|
||||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
|
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(
|
return TimelineEvent(
|
||||||
root = timelineEventEntity.root?.asDomain()
|
root = timelineEventEntity.root?.asDomain()
|
||||||
?: Event("", timelineEventEntity.eventId),
|
?: Event("", timelineEventEntity.eventId),
|
||||||
|
eventId = timelineEventEntity.eventId,
|
||||||
annotations = timelineEventEntity.annotations?.asDomain(),
|
annotations = timelineEventEntity.annotations?.asDomain(),
|
||||||
localId = timelineEventEntity.localId,
|
localId = timelineEventEntity.localId,
|
||||||
displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
|
displayIndex = timelineEventEntity.displayIndex,
|
||||||
senderName = timelineEventEntity.senderName,
|
senderName = timelineEventEntity.senderName,
|
||||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||||
senderAvatar = timelineEventEntity.senderAvatar,
|
senderAvatar = timelineEventEntity.senderAvatar,
|
||||||
readReceipts = readReceipts?.sortedByDescending {
|
readReceipts = readReceipts
|
||||||
it.originServerTs
|
?.distinctBy {
|
||||||
} ?: emptyList()
|
it.user
|
||||||
|
}?.sortedByDescending {
|
||||||
|
it.originServerTs
|
||||||
|
} ?: emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,10 @@ import io.realm.annotations.LinkingObjects
|
||||||
|
|
||||||
internal open class ChunkEntity(@Index var prevToken: String? = null,
|
internal open class ChunkEntity(@Index var prevToken: String? = null,
|
||||||
@Index var nextToken: String? = null,
|
@Index var nextToken: String? = null,
|
||||||
|
var stateEvents: RealmList<EventEntity> = RealmList(),
|
||||||
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||||
@Index var isLastForward: Boolean = false,
|
@Index var isLastForward: Boolean = false,
|
||||||
@Index var isLastBackward: 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
|
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
fun identifier() = "${prevToken}_$nextToken"
|
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.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.RealmResults
|
|
||||||
import io.realm.annotations.Index
|
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 roomId: String = "",
|
||||||
@Index var type: String = "",
|
@Index var type: String = "",
|
||||||
var content: String? = null,
|
var content: String? = null,
|
||||||
|
@ -36,20 +35,11 @@ internal open class EventEntity(@Index var eventId: String = "",
|
||||||
var age: Long? = 0,
|
var age: Long? = 0,
|
||||||
var unsignedData: String? = null,
|
var unsignedData: String? = null,
|
||||||
var redacts: 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 decryptionResultJson: String? = null,
|
||||||
var decryptionErrorCode: String? = null,
|
var decryptionErrorCode: String? = null,
|
||||||
var ageLocalTs: Long? = null
|
var ageLocalTs: Long? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
enum class LinkFilterMode {
|
|
||||||
LINKED_ONLY,
|
|
||||||
UNLINKED_ONLY,
|
|
||||||
BOTH
|
|
||||||
}
|
|
||||||
|
|
||||||
private var sendStateStr: String = SendState.UNKNOWN.name
|
private var sendStateStr: String = SendState.UNKNOWN.name
|
||||||
|
|
||||||
var sendState: SendState
|
var sendState: SendState
|
||||||
|
@ -62,12 +52,6 @@ internal open class EventEntity(@Index var eventId: String = "",
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
||||||
@LinkingObjects("untimelinedStateEvents")
|
|
||||||
val room: RealmResults<RoomEntity>? = null
|
|
||||||
|
|
||||||
@LinkingObjects("root")
|
|
||||||
val timelineEventEntity: RealmResults<TimelineEventEntity>? = null
|
|
||||||
|
|
||||||
fun setDecryptionResult(result: MXEventDecryptionResult) {
|
fun setDecryptionResult(result: MXEventDecryptionResult) {
|
||||||
val decryptionResult = OlmDecryptionResult(
|
val decryptionResult = OlmDecryptionResult(
|
||||||
payload = result.clearEvent,
|
payload = result.clearEvent,
|
||||||
|
@ -78,6 +62,5 @@ internal open class EventEntity(@Index var eventId: String = "",
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java)
|
val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java)
|
||||||
decryptionResultJson = adapter.toJson(decryptionResult)
|
decryptionResultJson = adapter.toJson(decryptionResult)
|
||||||
decryptionErrorCode = null
|
decryptionErrorCode = null
|
||||||
timelineEventEntity?.firstOrNull()?.root = this
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||||
var chunks: RealmList<ChunkEntity> = RealmList(),
|
var chunks: RealmList<ChunkEntity> = RealmList(),
|
||||||
var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
|
|
||||||
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||||
var areAllMembersLoaded: Boolean = false
|
var areAllMembersLoaded: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database.model
|
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.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||||
|
@ -47,7 +48,8 @@ internal open class RoomSummaryEntity(
|
||||||
// this is required for querying
|
// this is required for querying
|
||||||
var flatAliases: String = "",
|
var flatAliases: String = "",
|
||||||
var isEncrypted: Boolean = false,
|
var isEncrypted: Boolean = false,
|
||||||
var typingUserIds: RealmList<String> = RealmList()
|
var typingUserIds: RealmList<String> = RealmList(),
|
||||||
|
var roomEncryptionTrustLevelStr: String? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
@ -68,5 +70,19 @@ internal open class RoomSummaryEntity(
|
||||||
versioningStateStr = value.name
|
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
|
companion object
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,35 +22,36 @@ import io.realm.annotations.RealmModule
|
||||||
* Realm module for Session
|
* Realm module for Session
|
||||||
*/
|
*/
|
||||||
@RealmModule(library = true,
|
@RealmModule(library = true,
|
||||||
classes = [
|
classes = [
|
||||||
ChunkEntity::class,
|
ChunkEntity::class,
|
||||||
EventEntity::class,
|
EventEntity::class,
|
||||||
TimelineEventEntity::class,
|
TimelineEventEntity::class,
|
||||||
FilterEntity::class,
|
FilterEntity::class,
|
||||||
GroupEntity::class,
|
GroupEntity::class,
|
||||||
GroupSummaryEntity::class,
|
GroupSummaryEntity::class,
|
||||||
ReadReceiptEntity::class,
|
ReadReceiptEntity::class,
|
||||||
RoomEntity::class,
|
RoomEntity::class,
|
||||||
RoomSummaryEntity::class,
|
RoomSummaryEntity::class,
|
||||||
RoomTagEntity::class,
|
RoomTagEntity::class,
|
||||||
SyncEntity::class,
|
SyncEntity::class,
|
||||||
UserEntity::class,
|
UserEntity::class,
|
||||||
IgnoredUserEntity::class,
|
IgnoredUserEntity::class,
|
||||||
BreadcrumbsEntity::class,
|
BreadcrumbsEntity::class,
|
||||||
EventAnnotationsSummaryEntity::class,
|
EventAnnotationsSummaryEntity::class,
|
||||||
ReferencesAggregatedSummaryEntity::class,
|
ReactionAggregatedSummaryEntity::class,
|
||||||
ReactionAggregatedSummaryEntity::class,
|
EditAggregatedSummaryEntity::class,
|
||||||
EditAggregatedSummaryEntity::class,
|
ReferencesAggregatedSummaryEntity::class,
|
||||||
PushRulesEntity::class,
|
PushRulesEntity::class,
|
||||||
PushRuleEntity::class,
|
PushRuleEntity::class,
|
||||||
PushConditionEntity::class,
|
PushConditionEntity::class,
|
||||||
PusherEntity::class,
|
PusherEntity::class,
|
||||||
PusherDataEntity::class,
|
PusherDataEntity::class,
|
||||||
ReadReceiptsSummaryEntity::class,
|
ReadReceiptsSummaryEntity::class,
|
||||||
ReadMarkerEntity::class,
|
ReadMarkerEntity::class,
|
||||||
UserDraftsEntity::class,
|
UserDraftsEntity::class,
|
||||||
DraftEntity::class,
|
DraftEntity::class,
|
||||||
HomeServerCapabilitiesEntity::class,
|
HomeServerCapabilitiesEntity::class,
|
||||||
RoomMemberSummaryEntity::class
|
RoomMemberSummaryEntity::class,
|
||||||
])
|
CurrentStateEventEntity::class
|
||||||
|
])
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
|
|
@ -24,6 +24,7 @@ import io.realm.annotations.LinkingObjects
|
||||||
internal open class TimelineEventEntity(var localId: Long = 0,
|
internal open class TimelineEventEntity(var localId: Long = 0,
|
||||||
@Index var eventId: String = "",
|
@Index var eventId: String = "",
|
||||||
@Index var roomId: String = "",
|
@Index var roomId: String = "",
|
||||||
|
@Index var displayIndex: Int = 0,
|
||||||
var root: EventEntity? = null,
|
var root: EventEntity? = null,
|
||||||
var annotations: EventAnnotationsSummaryEntity? = null,
|
var annotations: EventAnnotationsSummaryEntity? = null,
|
||||||
var senderName: String? = null,
|
var senderName: String? = null,
|
||||||
|
|
|
@ -60,12 +60,10 @@ internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: Str
|
||||||
internal fun ChunkEntity.Companion.create(
|
internal fun ChunkEntity.Companion.create(
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
prevToken: String?,
|
prevToken: String?,
|
||||||
nextToken: String?,
|
nextToken: String?
|
||||||
isUnlinked: Boolean
|
|
||||||
): ChunkEntity {
|
): ChunkEntity {
|
||||||
return realm.createObject<ChunkEntity>().apply {
|
return realm.createObject<ChunkEntity>().apply {
|
||||||
this.prevToken = prevToken
|
this.prevToken = prevToken
|
||||||
this.nextToken = nextToken
|
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
|
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
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.*
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.Sort
|
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventEntity> {
|
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())
|
.`in`(EventEntityFields.EVENT_ID, eventIds.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun EventEntity.Companion.where(realm: Realm,
|
internal fun EventEntity.Companion.whereType(realm: Realm,
|
||||||
roomId: String? = null,
|
type: String,
|
||||||
type: String? = null,
|
roomId: String? = null
|
||||||
linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery<EventEntity> {
|
): RealmQuery<EventEntity> {
|
||||||
val query = realm.where<EventEntity>()
|
val query = realm.where<EventEntity>()
|
||||||
if (roomId != null) {
|
if (roomId != null) {
|
||||||
query.equalTo(EventEntityFields.ROOM_ID, roomId)
|
query.equalTo(EventEntityFields.ROOM_ID, roomId)
|
||||||
}
|
}
|
||||||
if (type != null) {
|
return query.equalTo(EventEntityFields.TYPE, type)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun EventEntity.Companion.types(realm: Realm,
|
internal fun EventEntity.Companion.whereTypes(realm: Realm,
|
||||||
typeList: List<String> = emptyList()): RealmQuery<EventEntity> {
|
typeList: List<String> = emptyList(),
|
||||||
|
roomId: String? = null): RealmQuery<EventEntity> {
|
||||||
val query = realm.where<EventEntity>()
|
val query = realm.where<EventEntity>()
|
||||||
query.`in`(EventEntityFields.TYPE, typeList.toTypedArray())
|
query.`in`(EventEntityFields.TYPE, typeList.toTypedArray())
|
||||||
|
if (roomId != null) {
|
||||||
|
query.equalTo(EventEntityFields.ROOM_ID, roomId)
|
||||||
|
}
|
||||||
return query
|
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? {
|
internal fun RealmList<EventEntity>.find(eventId: String): EventEntity? {
|
||||||
return this.where()
|
return this.where()
|
||||||
.equalTo(EventEntityFields.EVENT_ID, eventId)
|
.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.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
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.ReadReceiptEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
|
||||||
internal fun isEventRead(monarchy: Monarchy,
|
internal fun isEventRead(monarchy: Monarchy,
|
||||||
|
@ -36,16 +37,15 @@ internal fun isEventRead(monarchy: Monarchy,
|
||||||
|
|
||||||
monarchy.doWithRealm { realm ->
|
monarchy.doWithRealm { realm ->
|
||||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm
|
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm
|
||||||
val eventToCheck = liveChunk.timelineEvents.find(eventId)?.root
|
val eventToCheck = liveChunk.timelineEvents.find(eventId)
|
||||||
|
isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) {
|
||||||
isEventRead = if (eventToCheck?.sender == userId) {
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst()
|
val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst()
|
||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex
|
||||||
?: Int.MIN_VALUE
|
?: Int.MIN_VALUE
|
||||||
val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE
|
val eventToCheckIndex = eventToCheck.displayIndex
|
||||||
|
|
||||||
eventToCheckIndex <= readReceiptIndex
|
eventToCheckIndex <= readReceiptIndex
|
||||||
}
|
}
|
||||||
|
@ -61,13 +61,17 @@ internal fun isReadMarkerMoreRecent(monarchy: Monarchy,
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return false
|
val eventToCheck = TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()
|
||||||
val eventToCheck = liveChunk.timelineEvents.find(eventId)?.root
|
val eventToCheckChunk = eventToCheck?.chunk?.firstOrNull()
|
||||||
|
|
||||||
val readMarker = ReadMarkerEntity.where(realm, roomId).findFirst() ?: return false
|
val readMarker = ReadMarkerEntity.where(realm, roomId).findFirst() ?: return false
|
||||||
val readMarkerIndex = liveChunk.timelineEvents.find(readMarker.eventId)?.root?.displayIndex
|
val readMarkerEvent = TimelineEventEntity.where(realm, roomId = roomId, eventId = readMarker.eventId).findFirst()
|
||||||
?: Int.MIN_VALUE
|
val readMarkerChunk = readMarkerEvent?.chunk?.firstOrNull()
|
||||||
val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE
|
if (eventToCheckChunk == readMarkerChunk) {
|
||||||
eventToCheckIndex <= readMarkerIndex
|
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.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.internal.database.model.*
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.*
|
|
||||||
import io.realm.*
|
import io.realm.*
|
||||||
import io.realm.kotlin.where
|
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())
|
.`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.where(realm: Realm,
|
internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm,
|
||||||
roomId: String? = null,
|
roomId: String): RealmQuery<TimelineEventEntity> {
|
||||||
type: String? = null,
|
return realm.where<TimelineEventEntity>()
|
||||||
linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery<TimelineEventEntity> {
|
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||||
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.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
|
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
|
||||||
|
@ -71,7 +58,7 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
||||||
liveEvents
|
liveEvents
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
?.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
?.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
?.findFirst()
|
?.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? {
|
internal fun RealmList<TimelineEventEntity>.find(eventId: String): TimelineEventEntity? {
|
||||||
return this.where()
|
return this.where()
|
||||||
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
|
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
|
||||||
|
|
|
@ -22,11 +22,11 @@ import com.squareup.moshi.Moshi
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
import im.vector.matrix.android.api.Matrix
|
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.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.AuthModule
|
import im.vector.matrix.android.internal.auth.AuthModule
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
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.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
@ -48,6 +48,8 @@ internal interface MatrixComponent {
|
||||||
|
|
||||||
fun context(): Context
|
fun context(): Context
|
||||||
|
|
||||||
|
fun matrixConfiguration(): MatrixConfiguration
|
||||||
|
|
||||||
fun resources(): Resources
|
fun resources(): Resources
|
||||||
|
|
||||||
fun olmManager(): OlmManager
|
fun olmManager(): OlmManager
|
||||||
|
@ -56,8 +58,6 @@ internal interface MatrixComponent {
|
||||||
|
|
||||||
fun sessionParamsStore(): SessionParamsStore
|
fun sessionParamsStore(): SessionParamsStore
|
||||||
|
|
||||||
fun networkConnectivityChecker(): NetworkConnectivityChecker
|
|
||||||
|
|
||||||
fun backgroundDetectionObserver(): BackgroundDetectionObserver
|
fun backgroundDetectionObserver(): BackgroundDetectionObserver
|
||||||
|
|
||||||
fun sessionManager(): SessionManager
|
fun sessionManager(): SessionManager
|
||||||
|
@ -66,6 +66,7 @@ internal interface MatrixComponent {
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface 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
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.novoda.merlin.Merlin
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import com.novoda.merlin.MerlinsBeard
|
import im.vector.matrix.android.internal.session.homeserver.HomeServerPinger
|
||||||
import im.vector.matrix.android.internal.di.MatrixScope
|
|
||||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
import timber.log.Timber
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
@MatrixScope
|
interface NetworkConnectivityChecker {
|
||||||
internal class NetworkConnectivityChecker @Inject constructor(private val context: Context,
|
/**
|
||||||
private val backgroundDetectionObserver: BackgroundDetectionObserver)
|
* Returns true when internet is available
|
||||||
: BackgroundDetectionObserver.Listener {
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
fun hasInternetAccess(forcePing: Boolean): Boolean
|
||||||
|
|
||||||
private val merlin = Merlin.Builder()
|
fun register(listener: Listener)
|
||||||
.withConnectableCallbacks()
|
fun unregister(listener: Listener)
|
||||||
.withDisconnectableCallbacks()
|
|
||||||
.build(context)
|
|
||||||
|
|
||||||
private val merlinsBeard = MerlinsBeard.Builder().build(context)
|
interface Listener {
|
||||||
|
fun onConnectivityChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val listeners = Collections.synchronizedSet(LinkedHashSet<Listener>())
|
@SessionScope
|
||||||
private var hasInternetAccess = merlinsBeard.isConnected
|
internal class DefaultNetworkConnectivityChecker @Inject constructor(private val homeServerPinger: HomeServerPinger,
|
||||||
|
private val backgroundDetectionObserver: BackgroundDetectionObserver,
|
||||||
|
private val networkCallbackStrategy: NetworkCallbackStrategy)
|
||||||
|
: NetworkConnectivityChecker {
|
||||||
|
|
||||||
init {
|
private val hasInternetAccess = AtomicBoolean(true)
|
||||||
backgroundDetectionObserver.register(this)
|
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
|
* Returns true when internet is available
|
||||||
*/
|
*/
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun hasInternetAccess(): Boolean {
|
override fun hasInternetAccess(forcePing: Boolean): Boolean {
|
||||||
// If we are in background we have unbound merlin, so we have to check
|
return if (forcePing) {
|
||||||
return if (backgroundDetectionObserver.isInBackground) {
|
runBlocking {
|
||||||
merlinsBeard.hasInternetAccess()
|
homeServerPinger.canReachHomeServer()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
hasInternetAccess
|
hasInternetAccess.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMoveToForeground() {
|
override fun register(listener: NetworkConnectivityChecker.Listener) {
|
||||||
merlin.bind()
|
if (listeners.isEmpty()) {
|
||||||
merlinsBeard.hasInternetAccess {
|
if (backgroundDetectionObserver.isInBackground) {
|
||||||
hasInternetAccess = it
|
unbind()
|
||||||
}
|
} else {
|
||||||
merlin.registerDisconnectable {
|
bind()
|
||||||
if (hasInternetAccess) {
|
|
||||||
Timber.v("On Disconnect")
|
|
||||||
hasInternetAccess = false
|
|
||||||
val localListeners = listeners.toList()
|
|
||||||
localListeners.forEach {
|
|
||||||
it.onDisconnect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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)
|
listeners.add(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregister(listener: Listener) {
|
override fun unregister(listener: NetworkConnectivityChecker.Listener) {
|
||||||
listeners.remove(listener)
|
listeners.remove(listener)
|
||||||
|
if (listeners.isEmpty()) {
|
||||||
|
backgroundDetectionObserver.unregister(backgroundDetectionObserverListener)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Listener {
|
private fun bind() {
|
||||||
fun onConnect() {
|
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 im.vector.matrix.android.api.failure.Failure
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -27,11 +28,17 @@ internal suspend inline fun <DATA> executeRequest(eventBus: EventBus?,
|
||||||
|
|
||||||
internal class Request<DATA>(private val 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>
|
lateinit var apiCall: Call<DATA>
|
||||||
|
|
||||||
suspend fun execute(): DATA {
|
suspend fun execute(): DATA {
|
||||||
return try {
|
return try {
|
||||||
val response = apiCall.awaitResponse()
|
val response = apiCall.clone().awaitResponse()
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
response.body()
|
response.body()
|
||||||
?: throw IllegalStateException("The request returned a null 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)
|
throw response.toFailure(eventBus)
|
||||||
}
|
}
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
throw when (exception) {
|
if (isRetryable && currentRetryCount++ < maxRetryCount && exception is IOException) {
|
||||||
is IOException -> Failure.NetworkConnection(exception)
|
delay(currentDelay)
|
||||||
is Failure.ServerError,
|
currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay)
|
||||||
is Failure.OtherServerError -> exception
|
return execute()
|
||||||
is CancellationException -> Failure.Cancelled(exception)
|
} else {
|
||||||
else -> Failure.Unknown(exception)
|
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 android.content.Context
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
|
import im.vector.matrix.android.api.MatrixConfiguration
|
||||||
import im.vector.matrix.android.internal.di.MatrixScope
|
import im.vector.matrix.android.internal.di.MatrixScope
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
internal class UserAgentHolder @Inject constructor(private val context: Context) {
|
internal class UserAgentHolder @Inject constructor(private val context: Context,
|
||||||
|
matrixConfiguration: MatrixConfiguration) {
|
||||||
|
|
||||||
var userAgent: String = ""
|
var userAgent: String = ""
|
||||||
private set
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setApplicationFlavor("NoFlavor")
|
setApplicationFlavor(matrixConfiguration.applicationFlavor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +40,7 @@ internal class UserAgentHolder @Inject constructor(private val context: Context)
|
||||||
*
|
*
|
||||||
* @param flavorDescription the flavor description
|
* @param flavorDescription the flavor description
|
||||||
*/
|
*/
|
||||||
fun setApplicationFlavor(flavorDescription: String) {
|
private fun setApplicationFlavor(flavorDescription: String) {
|
||||||
var appName = ""
|
var appName = ""
|
||||||
var appVersion = ""
|
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.api.session.user.UserService
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
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.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.di.SessionId
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||||
|
@ -89,7 +90,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
||||||
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
||||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
|
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
|
||||||
|
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||||
: Session,
|
: Session,
|
||||||
RoomService by roomService.get(),
|
RoomService by roomService.get(),
|
||||||
RoomDirectoryService by roomDirectoryService.get(),
|
RoomDirectoryService by roomDirectoryService.get(),
|
||||||
|
@ -119,6 +121,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
isOpen = true
|
isOpen = true
|
||||||
liveEntityObservers.forEach { it.start() }
|
liveEntityObservers.forEach { it.start() }
|
||||||
eventBus.register(this)
|
eventBus.register(this)
|
||||||
|
shieldTrustUpdater.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requireBackgroundSync() {
|
override fun requireBackgroundSync() {
|
||||||
|
@ -160,6 +163,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
isOpen = false
|
isOpen = false
|
||||||
eventBus.unregister(this)
|
eventBus.unregister(this)
|
||||||
syncTaskSequencer.close()
|
syncTaskSequencer.close()
|
||||||
|
shieldTrustUpdater.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSyncStateLive(): LiveData<SyncState> {
|
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||||
|
|
|
@ -17,16 +17,19 @@
|
||||||
package im.vector.matrix.android.internal.session
|
package im.vector.matrix.android.internal.session
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.multibindings.IntoSet
|
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.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.auth.data.sessionId
|
import 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.InitialSyncProgressService
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
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.crypto.verification.VerificationMessageLiveObserver
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
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.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.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
||||||
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
||||||
|
@ -51,6 +67,7 @@ import okhttp3.OkHttpClient
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
internal abstract class SessionModule {
|
internal abstract class SessionModule {
|
||||||
|
@ -59,6 +76,11 @@ internal abstract class SessionModule {
|
||||||
companion object {
|
companion object {
|
||||||
internal fun getKeyAlias(userMd5: String) = "session_db_$userMd5"
|
internal fun getKeyAlias(userMd5: String) = "session_db_$userMd5"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rules:
|
||||||
|
* Annotate methods with @SessionScope only the @Provides annotated methods with computation and logic.
|
||||||
|
*/
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
fun providesHomeServerConnectionConfig(sessionParams: SessionParams): HomeServerConnectionConfig {
|
fun providesHomeServerConnectionConfig(sessionParams: SessionParams): HomeServerConnectionConfig {
|
||||||
|
@ -74,6 +96,7 @@ internal abstract class SessionModule {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@UserId
|
@UserId
|
||||||
@Provides
|
@Provides
|
||||||
|
@SessionScope
|
||||||
fun providesUserId(credentials: Credentials): String {
|
fun providesUserId(credentials: Credentials): String {
|
||||||
return credentials.userId
|
return credentials.userId
|
||||||
}
|
}
|
||||||
|
@ -88,6 +111,7 @@ internal abstract class SessionModule {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@UserMd5
|
@UserMd5
|
||||||
@Provides
|
@Provides
|
||||||
|
@SessionScope
|
||||||
fun providesUserMd5(@UserId userId: String): String {
|
fun providesUserMd5(@UserId userId: String): String {
|
||||||
return userId.md5()
|
return userId.md5()
|
||||||
}
|
}
|
||||||
|
@ -95,6 +119,7 @@ internal abstract class SessionModule {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@SessionId
|
@SessionId
|
||||||
@Provides
|
@Provides
|
||||||
|
@SessionScope
|
||||||
fun providesSessionId(credentials: Credentials): String {
|
fun providesSessionId(credentials: Credentials): String {
|
||||||
return credentials.sessionId()
|
return credentials.sessionId()
|
||||||
}
|
}
|
||||||
|
@ -178,11 +203,34 @@ internal abstract class SessionModule {
|
||||||
fun providesEventBus(): EventBus {
|
fun providesEventBus(): EventBus {
|
||||||
return EventBus.builder().build()
|
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
|
@Binds
|
||||||
abstract fun bindSession(session: DefaultSession): Session
|
abstract fun bindSession(session: DefaultSession): Session
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindNetworkConnectivityChecker(networkConnectivityChecker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindGroupSummaryUpdater(groupSummaryUpdater: GroupSummaryUpdater): LiveEntityObserver
|
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.GroupSummaryResponse
|
||||||
import im.vector.matrix.android.internal.session.group.model.GroupUsers
|
import im.vector.matrix.android.internal.session.group.model.GroupUsers
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -53,12 +54,12 @@ internal class DefaultGetGroupDataTask @Inject constructor(
|
||||||
insertInDb(groupSummary, groupRooms, groupUsers, groupId)
|
insertInDb(groupSummary, groupRooms, groupUsers, groupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertInDb(groupSummary: GroupSummaryResponse,
|
private suspend fun insertInDb(groupSummary: GroupSummaryResponse,
|
||||||
groupRooms: GroupRooms,
|
groupRooms: GroupRooms,
|
||||||
groupUsers: GroupUsers,
|
groupUsers: GroupUsers,
|
||||||
groupId: String) {
|
groupId: String) {
|
||||||
monarchy
|
monarchy
|
||||||
.writeAsync { realm ->
|
.awaitTransaction { realm ->
|
||||||
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
|
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
|
||||||
?: realm.createObject(GroupSummaryEntity::class.java, groupId)
|
?: realm.createObject(GroupSummaryEntity::class.java, groupId)
|
||||||
|
|
||||||
|
|
|
@ -27,4 +27,10 @@ internal interface CapabilitiesAPI {
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config")
|
@GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config")
|
||||||
fun getUploadCapabilities(): Call<GetUploadCapabilitiesResult>
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultPushRuleService @Inject constructor(private val getPushRulesTask: GetPushRulesTask,
|
internal class DefaultPushRuleService @Inject constructor(
|
||||||
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
private val getPushRulesTask: GetPushRulesTask,
|
||||||
private val addPushRuleTask: AddPushRuleTask,
|
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
||||||
private val removePushRuleTask: RemovePushRuleTask,
|
private val addPushRuleTask: AddPushRuleTask,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val removePushRuleTask: RemovePushRuleTask,
|
||||||
private val monarchy: Monarchy
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val monarchy: Monarchy
|
||||||
) : PushRuleService {
|
) : PushRuleService {
|
||||||
|
|
||||||
private var listeners = mutableSetOf<PushRuleService.PushRuleListener>()
|
private var listeners = mutableSetOf<PushRuleService.PushRuleListener>()
|
||||||
|
|
|
@ -16,12 +16,11 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.notification
|
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.pushrules.rest.PushRule
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
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.session.sync.model.RoomsSyncResponse
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -36,7 +35,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
|
||||||
|
|
||||||
internal class DefaultProcessEventForPushTask @Inject constructor(
|
internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||||
private val defaultPushRuleService: DefaultPushRuleService,
|
private val defaultPushRuleService: DefaultPushRuleService,
|
||||||
private val roomService: RoomService,
|
private val conditionResolver: ConditionResolver,
|
||||||
@UserId private val userId: String
|
@UserId private val userId: String
|
||||||
) : ProcessEventForPushTask {
|
) : ProcessEventForPushTask {
|
||||||
|
|
||||||
|
@ -97,12 +96,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
|
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
|
||||||
// TODO This should be injected
|
|
||||||
val conditionResolver = DefaultConditionResolver(event, roomService, userId)
|
|
||||||
return rules.firstOrNull { rule ->
|
return rules.firstOrNull { rule ->
|
||||||
// All conditions must hold true for an event in order to apply the action for the event.
|
// All conditions must hold true for an event in order to apply the action for the event.
|
||||||
rule.enabled && rule.conditions?.all {
|
rule.enabled && rule.conditions?.all {
|
||||||
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false
|
it.asExecutableCondition()?.isSatisfied(event, conditionResolver) ?: false
|
||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,37 +15,52 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.session.pushers
|
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.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 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 @Inject constructor(
|
||||||
internal class DefaultConditionResolver(private val event: Event,
|
private val roomGetter: RoomGetter,
|
||||||
private val roomService: RoomService,
|
@UserId private val userId: String
|
||||||
@UserId private val userId: String) : ConditionResolver {
|
) : ConditionResolver {
|
||||||
|
|
||||||
override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean {
|
override fun resolveEventMatchCondition(event: Event,
|
||||||
return eventMatchCondition.isSatisfied(event)
|
condition: EventMatchCondition): Boolean {
|
||||||
|
return condition.isSatisfied(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean {
|
override fun resolveRoomMemberCountCondition(event: Event,
|
||||||
return roomMemberCountCondition.isSatisfied(event, roomService)
|
condition: RoomMemberCountCondition): Boolean {
|
||||||
|
return condition.isSatisfied(event, roomGetter)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean {
|
override fun resolveSenderNotificationPermissionCondition(event: Event,
|
||||||
// val roomId = event.roomId ?: return false
|
condition: SenderNotificationPermissionCondition): Boolean {
|
||||||
// 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 {
|
|
||||||
val roomId = event.roomId ?: return false
|
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
|
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 com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
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.Room
|
||||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.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.session.room.typing.TypingService
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
import 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.mapper.RoomSummaryMapper
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
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.model.RoomSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultRoom @Inject constructor(override val roomId: String,
|
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 cryptoService: CryptoService,
|
||||||
private val relationService: RelationService,
|
private val relationService: RelationService,
|
||||||
private val roomMembersService: MembershipService,
|
private val roomMembersService: MembershipService,
|
||||||
private val roomPushRuleService: RoomPushRuleService) :
|
private val roomPushRuleService: RoomPushRuleService,
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val sendStateTask: SendStateTask) :
|
||||||
Room,
|
Room,
|
||||||
TimelineService by timelineService,
|
TimelineService by timelineService,
|
||||||
SendService by sendService,
|
SendService by sendService,
|
||||||
|
@ -96,11 +104,27 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
return cryptoService.shouldEncryptForInvitedMembers(roomId)
|
return cryptoService.shouldEncryptForInvitedMembers(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableEncryptionWithAlgorithm(algorithm: String, callback: MatrixCallback<Unit>) {
|
override fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>) {
|
||||||
if (isEncrypted()) {
|
when {
|
||||||
callback.onFailure(IllegalStateException("Encryption is already enabled for this room"))
|
isEncrypted() -> {
|
||||||
} else {
|
callback.onFailure(IllegalStateException("Encryption is already enabled for this room"))
|
||||||
stateService.enableEncryption(algorithm, callback)
|
}
|
||||||
|
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