mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 08:12:46 +02:00
Compare commits
169 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
56677f0908 | ||
|
e20d1724c9 | ||
|
f6f9c349ec | ||
|
88b8ceaeb5 | ||
|
e4577d8fef | ||
|
030d6824e3 | ||
|
ab437e249d | ||
|
a250a895fe | ||
|
5808c1de22 | ||
|
d21604b791 | ||
|
fd135e1eeb | ||
|
f28e4cf991 | ||
|
1941862499 | ||
|
983593d647 | ||
|
1b413934b5 | ||
|
8a3e93ae96 | ||
|
5191cbaf93 | ||
|
f2e6900cfb | ||
|
0b7e757f3c | ||
|
5b2c947af1 | ||
|
6a69c6356d | ||
|
e492e4318b | ||
|
93d38843c3 | ||
|
7d94519064 | ||
|
21bac0f867 | ||
|
32c4ad9ecb | ||
|
e1342d096b | ||
|
e91c6c216d | ||
|
6013e1653b | ||
|
47f47e40c4 | ||
|
9016688aec | ||
|
d5f8a0c0fc | ||
|
33c657bff8 | ||
|
9a7bd35ddc | ||
|
3ac54c51f6 | ||
|
c4ea2507f8 | ||
|
6001ac60ab | ||
|
0cea26ec77 | ||
|
3dae220501 | ||
|
577c5a16b3 | ||
|
a0aebed3f7 | ||
|
3a044bd655 | ||
|
6acfab3242 | ||
|
bf02746d87 | ||
|
377d944228 | ||
|
6ff974b3ea | ||
|
7c5bb4ff5b | ||
|
3573aea600 | ||
|
251de0b89c | ||
|
6fce2a3066 | ||
|
b40da4ef0f | ||
|
96341df5e7 | ||
|
7ccc1c559c | ||
|
35ed22ab2b | ||
|
afbd9cff70 | ||
|
ce028f8bd2 | ||
|
a7274b9df0 | ||
|
c498416075 | ||
|
a9d6cb7be3 | ||
|
d71797319c | ||
|
ba0133a047 | ||
|
01a7ea0bd7 | ||
|
2e7fa23ce7 | ||
|
6750237764 | ||
|
588a644e02 | ||
|
1bc1c8ec4a | ||
|
7e362be568 | ||
|
83d83e0812 | ||
|
c97d298166 | ||
|
81413e49bc | ||
|
b24f133105 | ||
|
5b13be6332 | ||
|
abeb741cad | ||
|
84e1169525 | ||
|
27e217fce5 | ||
|
bdb1f850b2 | ||
|
0338535efa | ||
|
0dd3894a49 | ||
|
c8ff8d3c9e | ||
|
70973c3302 | ||
|
a930313bf3 | ||
|
35054dc5e8 | ||
|
33b123f719 | ||
|
24667f38b8 | ||
|
256a6e4322 | ||
|
7f5cc77ee0 | ||
|
c34307ecf7 | ||
|
dd13b6bd99 | ||
|
9df699db59 | ||
|
8bdb2b88fd | ||
|
2a534b5874 | ||
|
f719da96ed | ||
|
31e5c0eb1a | ||
|
34c5f37bbc | ||
|
9aadbbc3c7 | ||
|
57758af2d7 | ||
|
0a2474ef12 | ||
|
67bc100782 | ||
|
5fedfd9286 | ||
|
1917fbcc93 | ||
|
a48bf61ad7 | ||
|
5c1fcc47a1 | ||
|
d80c15f52f | ||
|
3e2219cbb5 | ||
|
bf2e01b8c3 | ||
|
75131fdf44 | ||
|
911ff8cf16 | ||
|
4506b7d6e6 | ||
|
320dc4accd | ||
|
ffc9a595dd | ||
|
1602c6544f | ||
|
79242c8c16 | ||
|
7d00aabc85 | ||
|
ef72f2246a | ||
|
51c2b9e1e9 | ||
|
2a4c8b3199 | ||
|
3189c114dc | ||
|
50814dafe9 | ||
|
dcd7d17ffd | ||
|
d9c007d017 | ||
|
05dd587fa8 | ||
|
3384d91adb | ||
|
f72e5c1d94 | ||
|
a3ec0e03a0 | ||
|
47ee2a24a7 | ||
|
b8096f21ea | ||
|
a17ec14dd7 | ||
|
062c4559a2 | ||
|
f7d511df38 | ||
|
6bff951c72 | ||
|
bd033866a8 | ||
|
3e9b2e4a06 | ||
|
ed9c3379bf | ||
|
1728d31401 | ||
|
225a6e00e6 | ||
|
96a729ab2b | ||
|
badb4042ce | ||
|
c3bb421c1e | ||
|
09212a05d0 | ||
|
e02430bcd0 | ||
|
4335fa4f72 | ||
|
6df5edbad1 | ||
|
362799ac08 | ||
|
f454078c6b | ||
|
783a514496 | ||
|
88755a79b4 | ||
|
e6cd8a3a86 | ||
|
37230b0614 | ||
|
ec6d78bf96 | ||
|
759b680e63 | ||
|
5e1b59f9d3 | ||
|
9fc3fa7f19 | ||
|
71a02a58af | ||
|
7f72af426b | ||
|
a8f783bbfa | ||
|
bf7c53ecab | ||
|
15b0bea870 | ||
|
e5e62dc4a7 | ||
|
f3db43f317 | ||
|
3a89a30056 | ||
|
1d8b81bb04 | ||
|
7bcae75314 | ||
|
4331d2ef47 | ||
|
c65f25d7ae | ||
|
fee2ec6b66 | ||
|
76065ac4fc | ||
|
d93050240a | ||
|
2bddf61afe | ||
|
d1b8d81fb1 |
@@ -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
.idea/codeStyles/Project.xml
generated
1
.idea/codeStyles/Project.xml
generated
@@ -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
generated
Normal file
6
.idea/copyright/NewVector.xml
generated
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
generated
Normal file
8
.idea/copyright/profiles_settings.xml
generated
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>
|
41
CHANGES.md
41
CHANGES.md
@@ -1,3 +1,36 @@
|
|||||||
|
Changes in RiotX 0.16.0 (2020-02-14)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Polls and Bot Buttons (MSC 2192 matrix-org/matrix-doc#2192)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Show confirmation dialog before deleting a message (#967, #1003)
|
||||||
|
- Open room member profile from reactions list and read receipts list (#875)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix crash by removing all notifications after clearing cache (#878)
|
||||||
|
- Fix issue with verification when other client declares it can only show QR code (#988)
|
||||||
|
- Fix too errors in the code (1941862499c9ec5268cc80882512ced379cafcfd, a250a895fe0a4acf08c671e03434edcd29ccd84f)
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- Javadoc improved for PushersService
|
||||||
|
- PushersService.pushers() has been renamed to PushersService.getPushers()
|
||||||
|
|
||||||
|
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)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
@@ -349,15 +382,17 @@ Features ✨:
|
|||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
-
|
-
|
||||||
|
|
||||||
Other changes:
|
|
||||||
-
|
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
-
|
-
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
-
|
||||||
|
|
||||||
Build 🧱:
|
Build 🧱:
|
||||||
-
|
-
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
-
|
||||||
|
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>) {
|
||||||
|
@@ -66,14 +66,10 @@ class SASTest : InstrumentedTest {
|
|||||||
val bobVerificationService = bobSession!!.getVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val bobTxCreatedLatch = CountDownLatch(1)
|
val bobTxCreatedLatch = CountDownLatch(1)
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
bobTxCreatedLatch.countDown()
|
bobTxCreatedLatch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
@@ -106,9 +102,7 @@ class SASTest : InstrumentedTest {
|
|||||||
// Let's cancel from alice side
|
// Let's cancel from alice side
|
||||||
val cancelLatch = CountDownLatch(1)
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
|
||||||
val bobListener2 = object : VerificationService.VerificationListener {
|
val bobListener2 = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (tx.transactionId == txID) {
|
if (tx.transactionId == txID) {
|
||||||
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
||||||
@@ -117,8 +111,6 @@ class SASTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener2)
|
bobVerificationService.addListener(bobListener2)
|
||||||
|
|
||||||
@@ -157,17 +149,13 @@ class SASTest : InstrumentedTest {
|
|||||||
var cancelReason: CancelCode? = null
|
var cancelReason: CancelCode? = null
|
||||||
val cancelLatch = CountDownLatch(1)
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
|
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
|
||||||
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
|
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
|
||||||
cancelLatch.countDown()
|
cancelLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobSession.getVerificationService().addListener(bobListener)
|
bobSession.getVerificationService().addListener(bobListener)
|
||||||
|
|
||||||
@@ -186,16 +174,12 @@ class SASTest : InstrumentedTest {
|
|||||||
val aliceUserID = aliceSession.myUserId
|
val aliceUserID = aliceSession.myUserId
|
||||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||||
|
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
aliceSession.getVerificationService().addListener(aliceListener)
|
aliceSession.getVerificationService().addListener(aliceListener)
|
||||||
|
|
||||||
@@ -328,7 +312,7 @@ class SASTest : InstrumentedTest {
|
|||||||
val aliceCreatedLatch = CountDownLatch(2)
|
val aliceCreatedLatch = CountDownLatch(2)
|
||||||
val aliceCancelledLatch = CountDownLatch(2)
|
val aliceCancelledLatch = CountDownLatch(2)
|
||||||
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {
|
override fun transactionCreated(tx: VerificationTransaction) {
|
||||||
createdTx.add(tx as SASDefaultVerificationTransaction)
|
createdTx.add(tx as SASDefaultVerificationTransaction)
|
||||||
aliceCreatedLatch.countDown()
|
aliceCreatedLatch.countDown()
|
||||||
@@ -339,8 +323,6 @@ class SASTest : InstrumentedTest {
|
|||||||
aliceCancelledLatch.countDown()
|
aliceCancelledLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
@@ -372,11 +354,7 @@ class SASTest : InstrumentedTest {
|
|||||||
var startReq: KeyVerificationStart? = null
|
var startReq: KeyVerificationStart? = null
|
||||||
|
|
||||||
val aliceAcceptedLatch = CountDownLatch(1)
|
val aliceAcceptedLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||||
val at = tx as SASDefaultVerificationTransaction
|
val at = tx as SASDefaultVerificationTransaction
|
||||||
@@ -388,17 +366,13 @@ class SASTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||||
val at = tx as IncomingSasVerificationTransaction
|
val at = tx as IncomingSasVerificationTransaction
|
||||||
at.performAccept()
|
at.performAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
@@ -433,9 +407,7 @@ class SASTest : InstrumentedTest {
|
|||||||
val bobVerificationService = bobSession!!.getVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
val aliceSASLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
@@ -445,15 +417,11 @@ class SASTest : InstrumentedTest {
|
|||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
val bobSASLatch = CountDownLatch(1)
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
@@ -466,8 +434,6 @@ class SASTest : InstrumentedTest {
|
|||||||
bobSASLatch.countDown()
|
bobSASLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
@@ -497,9 +463,7 @@ class SASTest : InstrumentedTest {
|
|||||||
val bobVerificationService = bobSession!!.getVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
val aliceSASLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
@@ -512,15 +476,11 @@ class SASTest : InstrumentedTest {
|
|||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
val bobSASLatch = CountDownLatch(1)
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
@@ -536,8 +496,6 @@ class SASTest : InstrumentedTest {
|
|||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
|
@@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 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.verification.qrcode
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||||
|
import im.vector.matrix.android.common.CommonTestHelper
|
||||||
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
|
import im.vector.matrix.android.common.TestConstants
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class VerificationTest : InstrumentedTest {
|
||||||
|
private val mTestHelper = CommonTestHelper(context())
|
||||||
|
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||||
|
|
||||||
|
data class ExpectedResult(
|
||||||
|
val sasIsSupported: Boolean = false,
|
||||||
|
val otherCanScanQrCode: Boolean = false,
|
||||||
|
val otherCanShowQrCode: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sas = listOf(
|
||||||
|
VerificationMethod.SAS
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasShow = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SHOW
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasScan = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SCAN
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasShowScan = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SHOW,
|
||||||
|
VerificationMethod.QR_CODE_SCAN
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_sas() = doTest(
|
||||||
|
sas,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_show() = doTest(
|
||||||
|
sas,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_sas() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_scan() = doTest(
|
||||||
|
sas,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_sas() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_scan() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_show() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_scan() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_show() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_all_all() = doTest(
|
||||||
|
sasShowScan,
|
||||||
|
sasShowScan,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO Add tests without SAS
|
||||||
|
|
||||||
|
private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
|
||||||
|
bobSupportedMethods: List<VerificationMethod>,
|
||||||
|
expectedResultForAlice: ExpectedResult,
|
||||||
|
expectedResultForBob: ExpectedResult) {
|
||||||
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
mTestHelper.doSync<Unit> { callback ->
|
||||||
|
aliceSession.getCrossSigningService()
|
||||||
|
.initializeCrossSigning(UserPasswordAuth(
|
||||||
|
user = aliceSession.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
mTestHelper.doSync<Unit> { callback ->
|
||||||
|
bobSession.getCrossSigningService()
|
||||||
|
.initializeCrossSigning(UserPasswordAuth(
|
||||||
|
user = bobSession.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.getVerificationService()
|
||||||
|
val bobVerificationService = bobSession.getVerificationService()
|
||||||
|
|
||||||
|
var aliceReadyPendingVerificationRequest: PendingVerificationRequest? = null
|
||||||
|
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
|
||||||
|
|
||||||
|
val latch = CountDownLatch(2)
|
||||||
|
val aliceListener = object : VerificationService.Listener {
|
||||||
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
|
// Step 4: Alice receive the ready request
|
||||||
|
if (pr.isReady) {
|
||||||
|
aliceReadyPendingVerificationRequest = pr
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
|
val bobListener = object : VerificationService.Listener {
|
||||||
|
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||||
|
// Step 2: Bob accepts the verification request
|
||||||
|
bobVerificationService.readyPendingVerificationInDMs(
|
||||||
|
bobSupportedMethods,
|
||||||
|
aliceSession.myUserId,
|
||||||
|
cryptoTestData.roomId,
|
||||||
|
pr.transactionId!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
|
// Step 3: Bob is ready
|
||||||
|
if (pr.isReady) {
|
||||||
|
bobReadyPendingVerificationRequest = pr
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
|
val bobUserId = bobSession.myUserId
|
||||||
|
// Step 1: Alice starts a verification request
|
||||||
|
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
|
||||||
|
mTestHelper.await(latch)
|
||||||
|
|
||||||
|
aliceReadyPendingVerificationRequest!!.let { pr ->
|
||||||
|
pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported
|
||||||
|
pr.otherCanShowQrCode() shouldBe expectedResultForAlice.otherCanShowQrCode
|
||||||
|
pr.otherCanScanQrCode() shouldBe expectedResultForAlice.otherCanScanQrCode
|
||||||
|
}
|
||||||
|
|
||||||
|
bobReadyPendingVerificationRequest!!.let { pr ->
|
||||||
|
pr.isSasSupported() shouldBe expectedResultForBob.sasIsSupported
|
||||||
|
pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode
|
||||||
|
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoTestData.close()
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
|
||||||
}
|
}
|
||||||
|
@@ -30,9 +30,9 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification
|
|||||||
*/
|
*/
|
||||||
interface VerificationService {
|
interface VerificationService {
|
||||||
|
|
||||||
fun addListener(listener: VerificationListener)
|
fun addListener(listener: Listener)
|
||||||
|
|
||||||
fun removeListener(listener: VerificationListener)
|
fun removeListener(listener: Listener)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this device as verified manually
|
* Mark this device as verified manually
|
||||||
@@ -68,11 +68,11 @@ interface VerificationService {
|
|||||||
otherDevices: List<String>?): PendingVerificationRequest
|
otherDevices: List<String>?): PendingVerificationRequest
|
||||||
|
|
||||||
fun declineVerificationRequestInDMs(otherUserId: String,
|
fun declineVerificationRequestInDMs(otherUserId: String,
|
||||||
otherDeviceId: String,
|
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
roomId: String)
|
roomId: String)
|
||||||
|
|
||||||
// Only SAS method is supported for the moment
|
// Only SAS method is supported for the moment
|
||||||
|
// TODO Parameter otherDeviceId should be removed in this case
|
||||||
fun beginKeyVerificationInDMs(method: VerificationMethod,
|
fun beginKeyVerificationInDMs(method: VerificationMethod,
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
@@ -95,15 +95,33 @@ interface VerificationService {
|
|||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
transactionId: String): Boolean
|
transactionId: String): Boolean
|
||||||
|
|
||||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
interface Listener {
|
||||||
|
/**
|
||||||
interface VerificationListener {
|
* Called when a verification request is created either by the user, or by the other user.
|
||||||
fun transactionCreated(tx: VerificationTransaction)
|
*/
|
||||||
fun transactionUpdated(tx: VerificationTransaction)
|
|
||||||
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
|
|
||||||
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
|
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a verification request is updated.
|
||||||
|
*/
|
||||||
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
|
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a transaction is created, either by the user or initiated by the other user.
|
||||||
|
*/
|
||||||
|
fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a transaction is updated. You may be interested to track the state of the VerificationTransaction.
|
||||||
|
*/
|
||||||
|
fun transactionUpdated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the the deviceId of the userId has been marked as manually verified by the SDK.
|
||||||
|
* It will be called after VerificationService.markedLocallyAsManuallyVerified() is called.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@@ -157,6 +157,11 @@ data class Event(
|
|||||||
*/
|
*/
|
||||||
fun isRedacted() = unsignedData?.redactedEvent != null
|
fun isRedacted() = unsignedData?.redactedEvent != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the event is redacted by the user himself.
|
||||||
|
*/
|
||||||
|
fun isRedactedBySameUser() = senderId == unsignedData?.redactedEvent?.senderId
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
@@ -200,7 +205,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 +215,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
|
||||||
}
|
}
|
||||||
|
@@ -19,11 +19,12 @@ package im.vector.matrix.android.api.session.events.model
|
|||||||
* Constants defining known event relation types from Matrix specifications
|
* Constants defining known event relation types from Matrix specifications
|
||||||
*/
|
*/
|
||||||
object RelationType {
|
object RelationType {
|
||||||
|
|
||||||
/** Lets you define an event which annotates an existing event.*/
|
/** Lets you define an event which annotates an existing event.*/
|
||||||
const val ANNOTATION = "m.annotation"
|
const val ANNOTATION = "m.annotation"
|
||||||
/** Lets you define an event which replaces an existing event.*/
|
/** Lets you define an event which replaces an existing event.*/
|
||||||
const val REPLACE = "m.replace"
|
const val REPLACE = "m.replace"
|
||||||
/** Lets you define an event which references an existing event.*/
|
/** Lets you define an event which references an existing event.*/
|
||||||
const val REFERENCE = "m.reference"
|
const val REFERENCE = "m.reference"
|
||||||
|
/** Lets you define an event which adds a response to an existing event.*/
|
||||||
|
const val RESPONSE = "m.response"
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.pushers
|
|||||||
|
|
||||||
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.util.Cancelable
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
interface PushersService {
|
interface PushersService {
|
||||||
@@ -28,19 +29,32 @@ interface PushersService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new HTTP pusher.
|
* Add a new HTTP pusher.
|
||||||
|
* Note that only `http` kind is supported by the SDK for now.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
|
||||||
*
|
*
|
||||||
* @param pushkey the pushkey
|
* @param pushkey This is a unique identifier for this pusher. The value you should use for
|
||||||
|
* this is the routing or destination address information for the notification,
|
||||||
|
* for example, the APNS token for APNS or the Registration ID for GCM. If your
|
||||||
|
* notification client has no such concept, use any unique identifier. Max length, 512 chars.
|
||||||
|
* If the kind is "email", this is the email address to send notifications to.
|
||||||
* @param appId the application id
|
* @param appId the application id
|
||||||
* @param profileTag the profile tag
|
* This is a reverse-DNS style identifier for the application. It is recommended
|
||||||
* @param lang the language
|
* that this end with the platform, such that different platform versions get
|
||||||
* @param appDisplayName a human-readable application name
|
* different app identifiers. Max length, 64 chars.
|
||||||
* @param deviceDisplayName a human-readable device name
|
* @param profileTag This string determines which set of device specific rules this pusher executes.
|
||||||
* @param url the URL that should be used to send notifications
|
* @param lang The preferred language for receiving notifications (e.g. "en" or "en-US").
|
||||||
* @param append append the pusher
|
* @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher.
|
||||||
* @param withEventIdOnly true to limit the push content
|
* @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher.
|
||||||
|
* @param url The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify.
|
||||||
|
* @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition
|
||||||
|
* to any others with different user IDs. Otherwise, the homeserver must remove any other pushers
|
||||||
|
* with the same App ID and pushkey for different users.
|
||||||
|
* @param withEventIdOnly true to limit the push content to only id and not message content
|
||||||
|
* Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour
|
||||||
*
|
*
|
||||||
* @return A work request uuid. Can be used to listen to the status
|
* @return A work request uuid. Can be used to listen to the status
|
||||||
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
|
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
|
||||||
|
* @throws [InvalidParameterException] if a parameter is not correct
|
||||||
*/
|
*/
|
||||||
fun addHttpPusher(pushkey: String,
|
fun addHttpPusher(pushkey: String,
|
||||||
appId: String,
|
appId: String,
|
||||||
@@ -52,13 +66,18 @@ interface PushersService {
|
|||||||
append: Boolean,
|
append: Boolean,
|
||||||
withEventIdOnly: Boolean): UUID
|
withEventIdOnly: Boolean): UUID
|
||||||
|
|
||||||
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>)
|
/**
|
||||||
|
* Remove the http pusher
|
||||||
companion object {
|
*/
|
||||||
const val EVENT_ID_ONLY = "event_id_only"
|
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current pushers, as a LiveData
|
||||||
|
*/
|
||||||
fun getPushersLive(): LiveData<List<Pusher>>
|
fun getPushersLive(): LiveData<List<Pusher>>
|
||||||
|
|
||||||
fun pushers() : List<Pusher>
|
/**
|
||||||
|
* Get the current pushers
|
||||||
|
*/
|
||||||
|
fun getPushers(): List<Pusher>
|
||||||
}
|
}
|
||||||
|
@@ -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>)
|
||||||
}
|
}
|
||||||
|
@@ -19,5 +19,6 @@ data class EventAnnotationsSummary(
|
|||||||
var eventId: String,
|
var eventId: String,
|
||||||
var reactionsSummary: List<ReactionAggregatedSummary>,
|
var reactionsSummary: List<ReactionAggregatedSummary>,
|
||||||
var editSummary: EditAggregatedSummary?,
|
var editSummary: EditAggregatedSummary?,
|
||||||
|
var pollResponseSummary: PollResponseAggregatedSummary?,
|
||||||
var referencesAggregatedSummary: ReferencesAggregatedSummary? = null
|
var referencesAggregatedSummary: ReferencesAggregatedSummary? = null
|
||||||
)
|
)
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
data class PollResponseAggregatedSummary(
|
||||||
|
|
||||||
|
var aggregatedContent: PollSummaryContent? = null,
|
||||||
|
|
||||||
|
// If set the poll is closed (Clients SHOULD NOT consider responses after the close event)
|
||||||
|
var closedTime: Long? = null,
|
||||||
|
// Clients SHOULD validate that the option in the relationship is a valid option, and ignore the response if invalid
|
||||||
|
var nbOptions: Int = 0,
|
||||||
|
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
|
||||||
|
val sourceEvents: List<String>,
|
||||||
|
val localEchos: List<String>
|
||||||
|
)
|
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains an aggregated summary info of the poll response.
|
||||||
|
* Put pre-computed info that you want to access quickly without having
|
||||||
|
* to go through all references events
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PollSummaryContent(
|
||||||
|
// Index of my vote
|
||||||
|
var myVote: Int? = null,
|
||||||
|
// Array of VoteInfo, list is constructed so that there is only one vote by user
|
||||||
|
// And that optionIndex is valid
|
||||||
|
var votes: List<VoteInfo>? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun voteCount(): Int {
|
||||||
|
return votes?.size ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun voteCountForOption(optionIndex: Int) : Int {
|
||||||
|
return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class VoteInfo(
|
||||||
|
val userId: String,
|
||||||
|
val optionIndex: Int,
|
||||||
|
val voteTimestamp: Long
|
||||||
|
)
|
@@ -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
|
||||||
|
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.session.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
// Possible values for optionType
|
||||||
|
const val OPTION_TYPE_POLL = "org.matrix.poll"
|
||||||
|
const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls and bot buttons are m.room.message events with a msgtype of m.options,
|
||||||
|
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessageOptionsContent(
|
||||||
|
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_OPTIONS,
|
||||||
|
@Json(name = "type") val optionType: String? = null,
|
||||||
|
@Json(name = "body") override val body: String,
|
||||||
|
@Json(name = "label") val label: String?,
|
||||||
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
|
@Json(name = "options") val options: List<OptionItem>? = null,
|
||||||
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
|
) : MessageContent
|
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessagePollResponseContent(
|
||||||
|
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_RESPONSE,
|
||||||
|
@Json(name = "body") override val body: String,
|
||||||
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
|
) : 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"
|
||||||
@@ -26,8 +25,10 @@ object MessageType {
|
|||||||
const val MSGTYPE_VIDEO = "m.video"
|
const val MSGTYPE_VIDEO = "m.video"
|
||||||
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_OPTIONS = "org.matrix.options"
|
||||||
|
const val MSGTYPE_RESPONSE = "org.matrix.response"
|
||||||
|
const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed"
|
||||||
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,
|
||||||
|
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class OptionItem(
|
||||||
|
@Json(name = "label") val label: String?,
|
||||||
|
@Json(name = "value") val value: String?
|
||||||
|
)
|
@@ -25,5 +25,6 @@ data class ReactionInfo(
|
|||||||
@Json(name = "event_id") override val eventId: String,
|
@Json(name = "event_id") override val eventId: String,
|
||||||
val key: String,
|
val key: String,
|
||||||
// always null for reaction
|
// always null for reaction
|
||||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||||
|
@Json(name = "option") override val option: Int? = null
|
||||||
) : RelationContent
|
) : RelationContent
|
||||||
|
@@ -23,4 +23,5 @@ interface RelationContent {
|
|||||||
val type: String?
|
val type: String?
|
||||||
val eventId: String?
|
val eventId: String?
|
||||||
val inReplyTo: ReplyToContent?
|
val inReplyTo: ReplyToContent?
|
||||||
|
val option: Int?
|
||||||
}
|
}
|
||||||
|
@@ -22,5 +22,6 @@ import com.squareup.moshi.JsonClass
|
|||||||
data class RelationDefaultContent(
|
data class RelationDefaultContent(
|
||||||
@Json(name = "rel_type") override val type: String?,
|
@Json(name = "rel_type") override val type: String?,
|
||||||
@Json(name = "event_id") override val eventId: String?,
|
@Json(name = "event_id") override val eventId: String?,
|
||||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||||
|
@Json(name = "option") override val option: Int? = null
|
||||||
) : RelationContent
|
) : RelationContent
|
||||||
|
@@ -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
|
||||||
|
@@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room.send
|
|||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||||
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.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||||
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.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
@@ -62,7 +63,24 @@ interface SendService {
|
|||||||
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redacts (delete) the given event.
|
* Send a poll to the room.
|
||||||
|
* @param question the question
|
||||||
|
* @param options list of (label, value)
|
||||||
|
* @return a [Cancelable]
|
||||||
|
*/
|
||||||
|
fun sendPoll(question: String, options: List<OptionItem>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to send a poll response.
|
||||||
|
* @param pollEventId the poll currently replied to
|
||||||
|
* @param optionIndex The reply index
|
||||||
|
* @param optionValue The option value (for compatibility)
|
||||||
|
* @return a [Cancelable]
|
||||||
|
*/
|
||||||
|
fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redact (delete) the given event.
|
||||||
* @param event The event to redact
|
* @param event The event to redact
|
||||||
* @param reason Optional reason string
|
* @param reason Optional reason string
|
||||||
*/
|
*/
|
||||||
|
@@ -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,101 +151,98 @@ 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
|
||||||
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
|
abstract fun bindCryptoService(service: DefaultCryptoService): CryptoService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask
|
abstract fun bindDeleteDeviceTask(task: DefaultDeleteDeviceTask): DeleteDeviceTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
|
abstract fun bindGetDevicesTask(task: DefaultGetDevicesTask): GetDevicesTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
|
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask
|
abstract fun bindSetDeviceNameTask(task: DefaultSetDeviceNameTask): SetDeviceNameTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUploadKeysTask(uploadKeysTask: DefaultUploadKeysTask): UploadKeysTask
|
abstract fun bindUploadKeysTask(task: DefaultUploadKeysTask): UploadKeysTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUploadSigningKeysTask(uploadKeysTask: DefaultUploadSigningKeysTask): UploadSigningKeysTask
|
abstract fun bindUploadSigningKeysTask(task: DefaultUploadSigningKeysTask): UploadSigningKeysTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUploadSignaturesTask(uploadSignaturesTask: DefaultUploadSignaturesTask): UploadSignaturesTask
|
abstract fun bindUploadSignaturesTask(task: DefaultUploadSignaturesTask): UploadSignaturesTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsersTask: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
|
abstract fun bindDownloadKeysForUsersTask(task: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCreateKeysBackupVersionTask(createKeysBackupVersionTask: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
|
abstract fun bindCreateKeysBackupVersionTask(task: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteBackupTask(deleteBackupTask: DefaultDeleteBackupTask): DeleteBackupTask
|
abstract fun bindDeleteBackupTask(task: DefaultDeleteBackupTask): DeleteBackupTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteRoomSessionDataTask(deleteRoomSessionDataTask: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
|
abstract fun bindDeleteRoomSessionDataTask(task: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteRoomSessionsDataTask(deleteRoomSessionsDataTask: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
|
abstract fun bindDeleteRoomSessionsDataTask(task: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteSessionsDataTask(deleteSessionsDataTask: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
|
abstract fun bindDeleteSessionsDataTask(task: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetKeysBackupLastVersionTask(getKeysBackupLastVersionTask: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
|
abstract fun bindGetKeysBackupLastVersionTask(task: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetKeysBackupVersionTask(getKeysBackupVersionTask: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
|
abstract fun bindGetKeysBackupVersionTask(task: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetRoomSessionDataTask(getRoomSessionDataTask: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
|
abstract fun bindGetRoomSessionDataTask(task: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetRoomSessionsDataTask(getRoomSessionsDataTask: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
|
abstract fun bindGetRoomSessionsDataTask(task: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetSessionsDataTask(getSessionsDataTask: DefaultGetSessionsDataTask): GetSessionsDataTask
|
abstract fun bindGetSessionsDataTask(task: DefaultGetSessionsDataTask): GetSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindStoreRoomSessionDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
|
abstract fun bindStoreRoomSessionDataTask(task: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindStoreRoomSessionsDataTask(storeRoomSessionsDataTask: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
|
abstract fun bindStoreRoomSessionsDataTask(task: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindStoreSessionsDataTask(storeSessionsDataTask: DefaultStoreSessionsDataTask): StoreSessionsDataTask
|
abstract fun bindStoreSessionsDataTask(task: DefaultStoreSessionsDataTask): StoreSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUpdateKeysBackupVersionTask(updateKeysBackupVersionTask: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
|
abstract fun bindUpdateKeysBackupVersionTask(task: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
abstract fun bindSendToDeviceTask(task: DefaultSendToDeviceTask): SendToDeviceTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
|
abstract fun bindEncryptEventTask(task: DefaultEncryptEventTask): EncryptEventTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendVerificationMessageTask(sendDefaultSendVerificationMessageTask: DefaultSendVerificationMessageTask): SendVerificationMessageTask
|
abstract fun bindSendVerificationMessageTask(task: DefaultSendVerificationMessageTask): SendVerificationMessageTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
|
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
|
||||||
: ClaimOneTimeKeysForUsersDeviceTask
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask)
|
abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
|
||||||
: DeleteDeviceWithUserPasswordTask
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
|
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindComputeShieldTrustTask(task: 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())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherUserId == userId) {
|
||||||
|
// It's me, i should check if a newly trusted device is signing my master key
|
||||||
|
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
||||||
|
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO if my keys have changes, i should recheck all devices of all users?
|
eventBus.post(CryptoToSessionUserTrustChange(userIds))
|
||||||
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
|
||||||
devices?.forEach { device ->
|
|
||||||
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
|
||||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
|
||||||
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherUserId == userId) {
|
|
||||||
// It's me, i should check if a newly trusted device is signing my master key
|
|
||||||
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
|
||||||
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,9 +176,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var listeners = ArrayList<VerificationService.VerificationListener>()
|
private var listeners = ArrayList<VerificationService.Listener>()
|
||||||
|
|
||||||
override fun addListener(listener: VerificationService.VerificationListener) {
|
override fun addListener(listener: VerificationService.Listener) {
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
if (!listeners.contains(listener)) {
|
if (!listeners.contains(listener)) {
|
||||||
listeners.add(listener)
|
listeners.add(listener)
|
||||||
@@ -186,7 +186,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeListener(listener: VerificationService.VerificationListener) {
|
override fun removeListener(listener: VerificationService.Listener) {
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
listeners.remove(listener)
|
listeners.remove(listener)
|
||||||
}
|
}
|
||||||
@@ -1151,9 +1151,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
return verificationRequest
|
return verificationRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) {
|
override fun declineVerificationRequestInDMs(otherUserId: String, transactionId: String, roomId: String) {
|
||||||
verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||||
.cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User)
|
.cancelTransaction(transactionId, otherUserId, null, CancelCode.User)
|
||||||
|
|
||||||
getExistingVerificationRequest(otherUserId, transactionId)?.let {
|
getExistingVerificationRequest(otherUserId, transactionId)?.let {
|
||||||
updatePendingRequest(it.copy(
|
updatePendingRequest(it.copy(
|
||||||
|
@@ -15,8 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||||
@@ -46,11 +46,37 @@ data class PendingVerificationRequest(
|
|||||||
|
|
||||||
val isFinished: Boolean = isSuccessful || cancelConclusion != null
|
val isFinished: Boolean = isSuccessful || cancelConclusion != null
|
||||||
|
|
||||||
fun hasMethod(method: VerificationMethod): Boolean? {
|
/**
|
||||||
return when (method) {
|
* SAS is supported if I support it and the other party support it
|
||||||
VerificationMethod.SAS -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS)
|
*/
|
||||||
VerificationMethod.QR_CODE_SHOW -> readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW)
|
fun isSasSupported(): Boolean {
|
||||||
VerificationMethod.QR_CODE_SCAN -> readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN)
|
return requestInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other can show QR code if I can scan QR code and other can show QR code
|
||||||
|
*/
|
||||||
|
fun otherCanShowQrCode(): Boolean {
|
||||||
|
return if (isIncoming) {
|
||||||
|
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
|
||||||
|
} else {
|
||||||
|
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other can scan QR code if I can show QR code and other can scan QR code
|
||||||
|
*/
|
||||||
|
fun otherCanScanQrCode(): Boolean {
|
||||||
|
return if (isIncoming) {
|
||||||
|
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
|
||||||
|
} else {
|
||||||
|
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
|
@@ -42,7 +42,7 @@ internal interface VerificationTransport {
|
|||||||
|
|
||||||
fun cancelTransaction(transactionId: String,
|
fun cancelTransaction(transactionId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherUserDeviceId: String,
|
otherUserDeviceId: String?,
|
||||||
code: CancelCode)
|
code: CancelCode)
|
||||||
|
|
||||||
fun done(transactionId: String)
|
fun done(transactionId: String)
|
||||||
@@ -79,11 +79,13 @@ internal interface VerificationTransport {
|
|||||||
|
|
||||||
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
||||||
|
|
||||||
fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady
|
fun createReady(tid: String,
|
||||||
|
fromDevice: String,
|
||||||
|
methods: List<String>): VerificationInfoReady
|
||||||
|
|
||||||
// TODO Refactor
|
// TODO Refactor
|
||||||
fun sendVerificationReady(keyReq: VerificationInfoReady,
|
fun sendVerificationReady(keyReq: VerificationInfoReady,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String?,
|
||||||
callback: (() -> Unit)?)
|
callback: (() -> Unit)?)
|
||||||
}
|
}
|
||||||
|
@@ -208,7 +208,7 @@ internal class VerificationTransportRoomMessage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) {
|
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
||||||
val event = createEventAndLocalEcho(
|
val event = createEventAndLocalEcho(
|
||||||
type = EventType.KEY_VERIFICATION_CANCEL,
|
type = EventType.KEY_VERIFICATION_CANCEL,
|
||||||
@@ -331,13 +331,13 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String?,
|
||||||
callback: (() -> Unit)?) {
|
callback: (() -> Unit)?) {
|
||||||
// Not applicable (send event is called directly)
|
// Not applicable (send event is called directly)
|
||||||
Timber.w("## SAS ignored verification ready with methods: ${keyReq.methods}")
|
Timber.w("## SAS ignored verification ready with methods: ${keyReq.methods}")
|
||||||
|
@@ -80,7 +80,7 @@ internal class VerificationTransportToDevice(
|
|||||||
|
|
||||||
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String?,
|
||||||
callback: (() -> Unit)?) {
|
callback: (() -> Unit)?) {
|
||||||
Timber.d("## SAS sending verification ready with methods: ${keyReq.methods}")
|
Timber.d("## SAS sending verification ready with methods: ${keyReq.methods}")
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
@@ -159,7 +159,7 @@ internal class VerificationTransportToDevice(
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) {
|
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
||||||
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -55,7 +55,11 @@ internal object EventAnnotationsSummaryMapper {
|
|||||||
it.sourceEvents.toList(),
|
it.sourceEvents.toList(),
|
||||||
it.sourceLocalEcho.toList()
|
it.sourceLocalEcho.toList()
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
|
||||||
|
PollResponseAggregatedSummaryEntityMapper.map(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +97,9 @@ internal object EventAnnotationsSummaryMapper {
|
|||||||
RealmList<String>().apply { addAll(it.localEchos) }
|
RealmList<String>().apply { addAll(it.localEchos) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
|
||||||
|
PollResponseAggregatedSummaryEntityMapper.map(it)
|
||||||
|
}
|
||||||
return eventAnnotationsSummaryEntity
|
return eventAnnotationsSummaryEntity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.PollResponseAggregatedSummary
|
||||||
|
import im.vector.matrix.android.internal.database.model.PollResponseAggregatedSummaryEntity
|
||||||
|
import io.realm.RealmList
|
||||||
|
|
||||||
|
internal object PollResponseAggregatedSummaryEntityMapper {
|
||||||
|
|
||||||
|
fun map(entity: PollResponseAggregatedSummaryEntity): PollResponseAggregatedSummary {
|
||||||
|
return PollResponseAggregatedSummary(
|
||||||
|
aggregatedContent = ContentMapper.map(entity.aggregatedContent).toModel(),
|
||||||
|
closedTime = entity.closedTime,
|
||||||
|
localEchos = entity.sourceLocalEchoEvents.toList(),
|
||||||
|
sourceEvents = entity.sourceEvents.toList(),
|
||||||
|
nbOptions = entity.nbOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(model: PollResponseAggregatedSummary): PollResponseAggregatedSummaryEntity {
|
||||||
|
return PollResponseAggregatedSummaryEntity(
|
||||||
|
aggregatedContent = ContentMapper.map(model.aggregatedContent.toContent()),
|
||||||
|
nbOptions = model.nbOptions,
|
||||||
|
closedTime = model.closedTime,
|
||||||
|
sourceEvents = RealmList<String>().apply { addAll(model.sourceEvents) },
|
||||||
|
sourceLocalEchoEvents = RealmList<String>().apply { addAll(model.localEchos) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun PollResponseAggregatedSummaryEntity.asDomain(): PollResponseAggregatedSummary {
|
||||||
|
return PollResponseAggregatedSummaryEntityMapper.map(this)
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
@@ -25,7 +25,8 @@ internal open class EventAnnotationsSummaryEntity(
|
|||||||
var roomId: String? = null,
|
var roomId: String? = null,
|
||||||
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
|
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
|
||||||
var editSummary: EditAggregatedSummaryEntity? = null,
|
var editSummary: EditAggregatedSummaryEntity? = null,
|
||||||
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null
|
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
|
||||||
|
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
companion object
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep the latest state of a poll
|
||||||
|
*/
|
||||||
|
internal open class PollResponseAggregatedSummaryEntity(
|
||||||
|
// For now we persist this a JSON for greater flexibility
|
||||||
|
// #see PollSummaryContent
|
||||||
|
var aggregatedContent: String? = null,
|
||||||
|
|
||||||
|
// If set the poll is closed (Clients SHOULD NOT consider responses after the close event)
|
||||||
|
var closedTime: Long? = null,
|
||||||
|
// Clients SHOULD validate that the option in the relationship is a valid option, and ignore the response if invalid
|
||||||
|
var nbOptions: Int = 0,
|
||||||
|
|
||||||
|
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
|
||||||
|
var sourceEvents: RealmList<String> = RealmList(),
|
||||||
|
var sourceLocalEchoEvents: RealmList<String> = RealmList()
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
@@ -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
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user