forked from GitHub-Mirror/riotX-android
Compare commits
382 Commits
feature/ro
...
v0.6.0
Author | SHA1 | Date | |
---|---|---|---|
cc832633a5 | |||
eadea9016b | |||
6422d946c9 | |||
5cc3dc00e3 | |||
5a2a9f908a | |||
c1f2e9f171 | |||
620ba279d8 | |||
3fcfa33364 | |||
546da0f173 | |||
001711d5a3 | |||
8e1a964679 | |||
b25a130db1 | |||
8a9e6497e8 | |||
47e3797b7e | |||
5cbc90e06a | |||
e04bf31faa | |||
d25cf79b07 | |||
faa8e6bbb2 | |||
d3d4deb884 | |||
f6b8e0c479 | |||
2a726f54a2 | |||
1197d4021d | |||
03f8120b7d | |||
acd7a709de | |||
5651ea515b | |||
9794b3a49d | |||
b3e1c3969d | |||
f24bed17a2 | |||
a993a30203 | |||
91cc78d2ad | |||
562acc9702 | |||
dfab88ed95 | |||
36866dd24e | |||
c728834273 | |||
f5020d0f63 | |||
7da9cafcc2 | |||
6f09eea248 | |||
468bd5bcc9 | |||
3169093c50 | |||
d60d766354 | |||
0ffb5e627e | |||
b4a13f9504 | |||
ffa8b7e73a | |||
528958b3de | |||
3ffe2f7d40 | |||
bf42b73713 | |||
ed93f4a6c1 | |||
b3d649a4d9 | |||
3739e50d46 | |||
9bf484cf1e | |||
6c2faff1f0 | |||
07fca0922b | |||
282de21708 | |||
ba9d119892 | |||
4453f0ced9 | |||
77168bfd6a | |||
25e9a179d2 | |||
73ec0f5a83 | |||
993fa74252 | |||
38fc4984fe | |||
695d8cce00 | |||
07e99901e1 | |||
20f53e9a58 | |||
ced72aff4f | |||
fdaaca49c2 | |||
3485f023b0 | |||
384dd100e9 | |||
1ba8a58219 | |||
c8010561fc | |||
1f127335bc | |||
138a210a73 | |||
ca6bcde82d | |||
6bda437f5d | |||
3e6b65e174 | |||
137dcab734 | |||
b22b8fba02 | |||
3ccdf4a244 | |||
5fbd271b1c | |||
db8ea0f5e8 | |||
a47a3ead1f | |||
05b2092ffc | |||
6249a59203 | |||
618e9a4f52 | |||
f2c8d4ad02 | |||
be524472ec | |||
1b82a1a24d | |||
cf0b331c3b | |||
2a92a3dc80 | |||
012840abba | |||
a5975a099e | |||
38da4b9ee5 | |||
242e60fcaa | |||
a23be05cbf | |||
ed39b02924 | |||
fe931b5361 | |||
90d9cd0587 | |||
9cedb18921 | |||
e89ba7b87b | |||
902657c22a | |||
eec2abf164 | |||
6879cc8ca8 | |||
fd6bbbd3b5 | |||
0ff0b014a9 | |||
a89f0ddd1d | |||
fdc9e84dd5 | |||
58f878fca9 | |||
88095e4bd9 | |||
47d22a3d5e | |||
28e82cb8ea | |||
35817245cb | |||
75266f42bb | |||
95c4c9ce56 | |||
ce5570105d | |||
188a9aebfa | |||
c95223f5d2 | |||
ef0362ba9c | |||
ea242f6737 | |||
cbc08d834b | |||
0ab6b33fb6 | |||
1b394527b6 | |||
a8f1388721 | |||
166be4e289 | |||
b49ccefe63 | |||
825760d17e | |||
b5af62c3ea | |||
a51d96bf00 | |||
7e142d201d | |||
2be6058971 | |||
49d73f360e | |||
bd88d85a21 | |||
be4fc5cce6 | |||
704da1be55 | |||
5d002532d3 | |||
d4161e9a1a | |||
7966ebef03 | |||
ed5faca5d2 | |||
8ca829d538 | |||
e7819ce678 | |||
5402902bc2 | |||
bc1350aaf5 | |||
fd74e3dfb1 | |||
e0628da1cb | |||
aa4e74e986 | |||
6cc0c0672e | |||
501474b720 | |||
e11c66035c | |||
3d2d219d79 | |||
63af03bedd | |||
d3827b8673 | |||
4ca2531e47 | |||
4e8dc72439 | |||
25a4240a5a | |||
b9cfda23b6 | |||
06dcf75a32 | |||
21deb2551d | |||
70639f180c | |||
1dbb02a80d | |||
825463d9cd | |||
c313ce78cb | |||
39f58d048b | |||
3f792c7a84 | |||
347dcb469a | |||
9cd69d1e33 | |||
79fb1985aa | |||
e216cd15a8 | |||
37fde374b3 | |||
f7b471f141 | |||
93fd56a7ca | |||
5a9d88e791 | |||
eaf6a9923a | |||
d98567045c | |||
b4ce8748cb | |||
9d5433a857 | |||
6d4ee83e65 | |||
6e44cca17d | |||
0a73887c70 | |||
7fef063e15 | |||
24f391dac0 | |||
81c7f694d6 | |||
80e2fc0ca3 | |||
9f53406e99 | |||
3584658c36 | |||
12a0cbb400 | |||
20437446b4 | |||
35229882e3 | |||
a04f4421f6 | |||
af1e81f65e | |||
63f6081fa5 | |||
ee2e575211 | |||
0949d29f9c | |||
23466fb5a4 | |||
7f09e64d63 | |||
585f0ba4b7 | |||
245fbe86d9 | |||
456908c851 | |||
b79fdf6a85 | |||
d9f448c9aa | |||
7a6fc4936b | |||
d82fd10f3b | |||
4009f2c176 | |||
15c4b03340 | |||
7b5dff3dcf | |||
357123743f | |||
bb04af1e2c | |||
2f94fbd7eb | |||
f2a3bdb68e | |||
097e9714ff | |||
acae0fad3e | |||
4deb7eb865 | |||
f910cd6f97 | |||
652ac81fa1 | |||
99f4196388 | |||
c0b94f4111 | |||
1462fa0484 | |||
dafdc1d3ad | |||
394b89e76b | |||
0db8e7da43 | |||
dae8b5c196 | |||
215324a03e | |||
d3ce4c491c | |||
ed6d28bd3b | |||
c2e053b62b | |||
c450849cc3 | |||
fe884dba2d | |||
3fa4dbaa25 | |||
4a74f58516 | |||
c413321a22 | |||
d696bd2830 | |||
a2b6bd0f62 | |||
9cc922a8a2 | |||
c36d1bcd06 | |||
85499c6b33 | |||
8076eab4b5 | |||
d47c0f5ebc | |||
fd09a1224e | |||
c300c50093 | |||
77c4355aed | |||
1a92562182 | |||
9c390dcc0c | |||
95089b91b8 | |||
eb446d7b49 | |||
dc4786ecf0 | |||
e245023add | |||
90fad23493 | |||
000db4b192 | |||
2a16c36a59 | |||
ef6c1cfc63 | |||
4b4156996d | |||
087cc0e6e3 | |||
f4df27c2dc | |||
ab25980c4e | |||
77b402ce70 | |||
2763fbb496 | |||
6deba31111 | |||
ff6ce8a4b7 | |||
d0cff219aa | |||
65f0af918f | |||
ac38a6461c | |||
9a1e16a170 | |||
9e5c70dda3 | |||
0255696c88 | |||
76a9625f25 | |||
5af6bf3762 | |||
507bc2f622 | |||
125eacb20b | |||
6176520805 | |||
3aea0a50ca | |||
ab87a3caea | |||
c58328f94e | |||
03974c8bdf | |||
151ae7f4dd | |||
a34b053efe | |||
02e342849f | |||
b59017938b | |||
2c81e41288 | |||
cb44ab547c | |||
6d01a570fd | |||
4a2bf0d6c6 | |||
36af8a6a9f | |||
40a68c3e9f | |||
1a4ec34bb2 | |||
10490e3aa6 | |||
cd6624a8a6 | |||
3965218bf9 | |||
d78ff7ab08 | |||
cb274d6a33 | |||
c00dbce536 | |||
db88caf7fa | |||
c3d945d6bb | |||
df6080b1da | |||
4c128602b2 | |||
d609c49b31 | |||
001603cf9a | |||
d87ee32422 | |||
f0671b9e73 | |||
e218691bf2 | |||
9c67036c08 | |||
62657538af | |||
5438207fba | |||
fe88aaffbd | |||
21ba72e5e7 | |||
d48ae967bd | |||
0afde3b021 | |||
49ae954183 | |||
64bee91f7a | |||
4341b0d0f5 | |||
51fdccb393 | |||
7e3b300130 | |||
a98b324c89 | |||
977721881f | |||
838003b68a | |||
7d41352918 | |||
077396a832 | |||
32b79bd50e | |||
844f6d16a4 | |||
fc9ef579ca | |||
77fa5af1b8 | |||
90d25ff45e | |||
173452d38c | |||
a9f9083745 | |||
22dc2a6790 | |||
927cd7285d | |||
4d5bdecec6 | |||
0be987ac0d | |||
4bfaa00be4 | |||
8e78d8a58d | |||
e3e86c0a41 | |||
8a5fddd952 | |||
208460850e | |||
0ddef67cc9 | |||
896e582a9c | |||
477920f411 | |||
c647648e79 | |||
b654025a3b | |||
786a7d7560 | |||
b935b9311e | |||
8e12f71535 | |||
7eea2ccfb4 | |||
c32ef02a12 | |||
3651ec4870 | |||
87de7bd3e6 | |||
9494174c33 | |||
9bdea5b325 | |||
2f01ad99b3 | |||
bb3b5788ba | |||
45f7d3e9c4 | |||
0f7a56d005 | |||
63d2861bc8 | |||
6bbc784c29 | |||
c6fd625761 | |||
d8092abc4e | |||
6effb90361 | |||
42584fc55a | |||
30d9ddb3e8 | |||
020c32bb1a | |||
efd973208f | |||
30a6c98c08 | |||
1440080d04 | |||
61bb4c0427 | |||
3c25088243 | |||
fc1c0caea3 | |||
8901a5e09a | |||
25f1d21bc7 | |||
4d2ab9fa31 | |||
0289d2ee87 | |||
222201cc64 | |||
b15dea6de3 | |||
2ba83e456d | |||
1822fc4fbb | |||
e6dd1fbfec | |||
e2ea76f871 | |||
34d14eb304 | |||
3625c462f0 | |||
fe69206340 | |||
f9885fd04c | |||
316c8ec27e | |||
41465450d8 | |||
bd009caaf1 | |||
33252c3b65 | |||
4b971a9e67 | |||
698fc35704 | |||
1c69d8e425 |
@ -1,6 +1,7 @@
|
|||||||
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
||||||
# Last docker plugin version can be found here:
|
# Last docker plugin version can be found here:
|
||||||
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
||||||
|
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
|
||||||
|
|
||||||
# Build debug version of the RiotX application, from the develop branch and the features branches
|
# Build debug version of the RiotX application, from the develop branch and the features branches
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ steps:
|
|||||||
plugins:
|
plugins:
|
||||||
- docker#v3.1.0:
|
- docker#v3.1.0:
|
||||||
image: "runmymind/docker-android-sdk"
|
image: "runmymind/docker-android-sdk"
|
||||||
|
propagate-environment: true
|
||||||
|
|
||||||
- label: "Assemble FDroid Debug version"
|
- label: "Assemble FDroid Debug version"
|
||||||
agents:
|
agents:
|
||||||
@ -32,6 +34,7 @@ steps:
|
|||||||
plugins:
|
plugins:
|
||||||
- docker#v3.1.0:
|
- docker#v3.1.0:
|
||||||
image: "runmymind/docker-android-sdk"
|
image: "runmymind/docker-android-sdk"
|
||||||
|
propagate-environment: true
|
||||||
|
|
||||||
- label: "Build Google Play unsigned APK"
|
- label: "Build Google Play unsigned APK"
|
||||||
agents:
|
agents:
|
||||||
@ -46,6 +49,7 @@ steps:
|
|||||||
plugins:
|
plugins:
|
||||||
- docker#v3.1.0:
|
- docker#v3.1.0:
|
||||||
image: "runmymind/docker-android-sdk"
|
image: "runmymind/docker-android-sdk"
|
||||||
|
propagate-environment: true
|
||||||
|
|
||||||
# Code quality
|
# Code quality
|
||||||
|
|
||||||
|
127
CHANGES.md
127
CHANGES.md
@ -1,11 +1,130 @@
|
|||||||
|
Changes in RiotX 0.6.0 (2019-09-24)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Save draft of a message when exiting a room with non empty composer (#329)
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Add unread indent on room list (#485)
|
||||||
|
- Message Editing: Update notifications (#128)
|
||||||
|
- Remove any notification of a redacted event (#563)
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Fix a few accessibility issues
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fix characters erased from the Search field when the result are coming (#545)
|
||||||
|
- "No connection" banner was displayed by mistake
|
||||||
|
- Leaving community (from another client) has no effect on RiotX (#497)
|
||||||
|
- Push rules was not retrieved after a clear cache
|
||||||
|
- m.notice messages trigger push notifications (#238)
|
||||||
|
- Embiggen messages with multiple emojis also for edited messages (#458)
|
||||||
|
|
||||||
|
Build:
|
||||||
|
- Fix (again) issue with bad versionCode generated by Buildkite (#553)
|
||||||
|
|
||||||
|
Changes in RiotX 0.5.0 (2019-09-17)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Implementation of login to homeserver with SSO (#557)
|
||||||
|
- Handle M_CONSENT_NOT_GIVEN error (#64)
|
||||||
|
- Auto configure homeserver and identity server URLs of LoginActivity with a magic link
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Reduce default release build log level, and lab option to enable more logs.
|
||||||
|
- Display a no network indicator when there is no network (#559)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fix crash due to missing informationData (#535)
|
||||||
|
- Progress in initial sync dialog is decreasing for a step and should not (#532)
|
||||||
|
- Fix rendering issue of accepted third party invitation event
|
||||||
|
- All current notifications were dismissed by mistake when the app is launched from the launcher
|
||||||
|
|
||||||
|
Build:
|
||||||
|
- Fix issue with version name (#533)
|
||||||
|
- Fix issue with bad versionCode generated by Buildkite (#553)
|
||||||
|
|
||||||
|
Changes in RiotX 0.4.0 (2019-08-30)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Display read receipts in timeline (#81)
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Reactions: Reinstate the ability to react with non-unicode keys (#307)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fix text diff linebreak display (#441)
|
||||||
|
- Date change message repeats for each redaction until a normal message (#358)
|
||||||
|
- Slide-in reply icon is distorted (#423)
|
||||||
|
- Regression / e2e replies not encrypted
|
||||||
|
- Some video won't play
|
||||||
|
- Privacy: remove log of notifiable event (#519)
|
||||||
|
- Fix crash with EmojiCompat (#530)
|
||||||
|
|
||||||
|
Changes in RiotX 0.3.0 (2019-08-08)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Create Direct Room flow
|
||||||
|
- Handle `/markdown` command
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- UI for pending edits (#193)
|
||||||
|
- UX image preview screen transition (#393)
|
||||||
|
- Basic support for resending failed messages (retry/remove)
|
||||||
|
- Enable proper cancellation of suspending functions (including db transaction)
|
||||||
|
- Enhances network connectivity checks in SDK
|
||||||
|
- Add "View Edit History" item in the message bottom sheet (#401)
|
||||||
|
- Cancel sync request on pause and timeout to 0 after pause (#404)
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Show sync progress also in room detail screen (#403)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Edited message: link confusion when (edited) appears in body (#398)
|
||||||
|
- Close detail room screen when the room is left with another client (#256)
|
||||||
|
- Clear notification for a room left on another client
|
||||||
|
- Fix messages with empty `in_reply_to` not rendering (#447)
|
||||||
|
- Fix clear cache (#408) and Logout (#205)
|
||||||
|
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||||
|
|
||||||
|
Build:
|
||||||
|
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
||||||
|
|
||||||
|
|
||||||
|
Changes in RiotX 0.2.0 (2019-07-18)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Message Editing: View edit history (#121)
|
||||||
|
- Rooms filtering (#304)
|
||||||
|
- Edit in encrypted room
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Handle click on redacted events: view source and create permalink
|
||||||
|
- Improve long tap menu: reply on top, more compact (#368)
|
||||||
|
- Quick reply in timeline with swipe gesture (#167)
|
||||||
|
- Improve edit of replies
|
||||||
|
- Improve performance on Room Members and Users management (#381)
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- migrate from rxbinding 2 to rxbinding 3
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fix regression on permalink click
|
||||||
|
- Fix crash reported by the PlayStore (#341)
|
||||||
|
- Fix Chat composer separator color in dark/black theme
|
||||||
|
- Fix bad layout for room directory filter (#349)
|
||||||
|
- Fix Copying link from a message shouldn't open context menu (#364)
|
||||||
|
|
||||||
Changes in RiotX 0.1.0 (2019-07-11)
|
Changes in RiotX 0.1.0 (2019-07-11)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
First release!
|
First release!
|
||||||
|
|
||||||
|
Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-android-b17952e8f771
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=======================================================
|
=======================================================
|
||||||
@ -13,7 +132,7 @@ First release!
|
|||||||
=======================================================
|
=======================================================
|
||||||
|
|
||||||
|
|
||||||
Changes in RiotX 0.XX (2019-XX-XX)
|
Changes in RiotX 0.0.0 (2019-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
29
README.md
29
README.md
@ -1,4 +1,4 @@
|
|||||||
[](https://buildkite.com/matrix-dot-org/riotx-android)
|
[](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||||
[](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
[](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
||||||
[](https://matrix.to/#/#riotx:matrix.org)
|
[](https://matrix.to/#/#riotx:matrix.org)
|
||||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||||
@ -7,14 +7,31 @@
|
|||||||
|
|
||||||
# RiotX Android
|
# RiotX Android
|
||||||
|
|
||||||
RiotX is an Android Matrix Client currently in development. The application is not yet available on the PlayStore.
|
RiotX is an Android Matrix Client currently in beta but in active development.
|
||||||
|
|
||||||
It's based on a new Matrix SDK, written in Kotlin.
|
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
||||||
|
|
||||||
Download nightly build here: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||||
|
|
||||||
|
Nightly build: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||||
|
|
||||||
|
# New Android SDK
|
||||||
|
|
||||||
|
RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the API is stable enough.
|
||||||
|
|
||||||
|
|
||||||
|
# Roadmap
|
||||||
|
|
||||||
|
The current target is to release an application out of beta with the same level of features (and even more) as Riot.
|
||||||
|
The roadmap has 3 phases:
|
||||||
|
|
||||||
|
- [phase 0](https://github.com/vector-im/riotX-android/labels/phase0): Prototyping / Project setup
|
||||||
|
- [phase 1](https://github.com/vector-im/riotX-android/labels/phase1): Beta release to the Play Store
|
||||||
|
- [phase 2](https://github.com/vector-im/riotX-android/labels/phase2): Out of beta
|
||||||
|
|
||||||
Matrix Room: [](https://matrix.to/#/#riotx:matrix.org)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute the Matrix on Android projects!
|
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
|
||||||
|
|
||||||
|
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#riotx:matrix.org).
|
||||||
|
21
build.gradle
21
build.gradle
@ -1,3 +1,5 @@
|
|||||||
|
import javax.tools.JavaCompiler
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
@ -45,7 +47,26 @@ allprojects {
|
|||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://repo.adobe.com/nexus/content/repositories/public/'
|
||||||
|
content {
|
||||||
|
includeGroupByRegex "diff_match_patch"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).all {
|
||||||
|
options.compilerArgs += [
|
||||||
|
'-Adagger.gradle.incremental=enabled'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
extensions.findByName("kapt")?.arguments {
|
||||||
|
arg("dagger.gradle.incremental", "enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
This document aims to describe how Riot X android displays notifications to the end user. It also clarifies notifications and background settings in the app.
|
This document aims to describe how RiotX android displays notifications to the end user. It also clarifies notifications and background settings in the app.
|
||||||
|
|
||||||
# Table of Contents
|
# Table of Contents
|
||||||
1. [Prerequisites Knowledge](#prerequisites-knowledge)
|
1. [Prerequisites Knowledge](#prerequisites-knowledge)
|
||||||
@ -50,7 +50,7 @@ By default, this is 0, so the server will return immediately even if the respons
|
|||||||
|
|
||||||
**delay** is a client preference. When the server responds to a sync request, the client waits for `delay`before calling a new sync.
|
**delay** is a client preference. When the server responds to a sync request, the client waits for `delay`before calling a new sync.
|
||||||
|
|
||||||
When the Riot X Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0.
|
When the RiotX Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0.
|
||||||
|
|
||||||
## How does a mobile app receives push notification
|
## How does a mobile app receives push notification
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ This need some disambiguation, because it is the source of common confusion:
|
|||||||
In order to send a push to a mobile, App developers need to have a server that will use the FCM APIs, and these APIs requires authentication!
|
In order to send a push to a mobile, App developers need to have a server that will use the FCM APIs, and these APIs requires authentication!
|
||||||
This server is called a **Push Gateway** in the matrix world
|
This server is called a **Push Gateway** in the matrix world
|
||||||
|
|
||||||
That means that Riot X Android, a matrix client created by New Vector, is using a **Push Gateway** with the needed credentials (FCM API secret Key) in order to send push to the New Vector client.
|
That means that RiotX Android, a matrix client created by New Vector, is using a **Push Gateway** with the needed credentials (FCM API secret Key) in order to send push to the New Vector client.
|
||||||
|
|
||||||
If you create your own matrix client, you will also need to deploy an instance of a **Push Gateway** with the credentials needed to use FCM for your app.
|
If you create your own matrix client, you will also need to deploy an instance of a **Push Gateway** with the credentials needed to use FCM for your app.
|
||||||
|
|
||||||
@ -223,7 +223,7 @@ Upon reception of the FCM push, RiotX will perform a sync call to the Home Serve
|
|||||||
* The sync generates additional notifications (e.g an encrypted message where the user is mentioned detected locally)
|
* The sync generates additional notifications (e.g an encrypted message where the user is mentioned detected locally)
|
||||||
* The sync takes too long and the process is killed before completion, or network is not reliable and the sync fails.
|
* The sync takes too long and the process is killed before completion, or network is not reliable and the sync fails.
|
||||||
|
|
||||||
Riot X implements several strategies in these cases (TODO document)
|
RiotX implements several strategies in these cases (TODO document)
|
||||||
|
|
||||||
## FCM Fallback mode
|
## FCM Fallback mode
|
||||||
|
|
||||||
|
@ -33,11 +33,12 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
// Paging
|
||||||
|
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
@ -20,6 +20,8 @@ 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
|
||||||
|
|
||||||
private class LiveDataObservable<T>(
|
private class LiveDataObservable<T>(
|
||||||
private val liveData: LiveData<T>,
|
private val liveData: LiveData<T>,
|
||||||
@ -57,5 +59,5 @@ private class LiveDataObservable<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||||
return LiveDataObservable(this)
|
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||||
}
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import io.reactivex.CompletableEmitter
|
||||||
|
import io.reactivex.SingleEmitter
|
||||||
|
|
||||||
|
internal class MatrixCallbackCompletable<T>(private val completableEmitter: CompletableEmitter) : MatrixCallback<T> {
|
||||||
|
|
||||||
|
override fun onSuccess(data: T) {
|
||||||
|
completableEmitter.onComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
completableEmitter.tryOnError(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Cancelable.toCompletable(completableEmitter: CompletableEmitter) {
|
||||||
|
completableEmitter.setCancellable {
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import io.reactivex.SingleEmitter
|
||||||
|
|
||||||
|
internal class MatrixCallbackSingle<T>(private val singleEmitter: SingleEmitter<T>) : MatrixCallback<T> {
|
||||||
|
|
||||||
|
override fun onSuccess(data: T) {
|
||||||
|
singleEmitter.onSuccess(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
singleEmitter.tryOnError(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Cancelable.toSingle(singleEmitter: SingleEmitter<T>) {
|
||||||
|
singleEmitter.setCancellable {
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
}
|
@ -18,27 +18,45 @@ package im.vector.matrix.rx
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
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 io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.Single
|
||||||
|
|
||||||
class RxRoom(private val room: Room) {
|
class RxRoom(private val room: Room) {
|
||||||
|
|
||||||
fun liveRoomSummary(): Observable<RoomSummary> {
|
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||||
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
|
return room.liveRoomSummary().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||||
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation())
|
return room.getRoomMemberIdsLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||||
return room.getEventSummaryLive(eventId).asObservable().observeOn(Schedulers.computation())
|
return room.getEventSummaryLive(eventId).asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
||||||
return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation())
|
return room.liveTimeLineEvent(eventId).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadRoomMembersIfNeeded(): Single<Unit> = Single.create {
|
||||||
|
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun joinRoom(viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||||
|
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||||
|
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveDrafts(): Observable<List<UserDraft>> {
|
||||||
|
return room.getDraftsLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,30 +16,55 @@
|
|||||||
|
|
||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import androidx.paging.PagedList
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.Single
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
||||||
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
|
return session.liveRoomSummaries().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
||||||
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation())
|
return session.liveGroupSummaries().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
return session.syncState().asObservable().observeOn(Schedulers.computation())
|
return session.syncState().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePushers(): Observable<List<Pusher>> {
|
fun livePushers(): Observable<List<Pusher>> {
|
||||||
return session.livePushers().asObservable().observeOn(Schedulers.computation())
|
return session.livePushers().asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveUsers(): Observable<List<User>> {
|
||||||
|
return session.liveUsers().asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||||
|
return session.livePagedUsers(filter).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create {
|
||||||
|
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchUsersDirectory(search: String,
|
||||||
|
limit: Int,
|
||||||
|
excludedUserIds: Set<String>): Single<List<User>> = Single.create {
|
||||||
|
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun joinRoom(roomId: String, viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||||
|
session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,6 @@ dependencies {
|
|||||||
def markwon_version = '3.0.0'
|
def markwon_version = '3.0.0'
|
||||||
def daggerVersion = '2.23.1'
|
def daggerVersion = '2.23.1'
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
|
||||||
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"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
@ -110,7 +109,7 @@ dependencies {
|
|||||||
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
|
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
|
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
|
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
|
||||||
implementation 'com.novoda:merlin:1.1.6'
|
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"
|
||||||
|
|
||||||
@ -126,9 +125,6 @@ dependencies {
|
|||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
|
||||||
implementation "io.arrow-kt:arrow-effects:$arrow_version"
|
|
||||||
implementation "io.arrow-kt:arrow-effects-instances:$arrow_version"
|
|
||||||
implementation "io.arrow-kt:arrow-integration-retrofit-adapter:$arrow_version"
|
|
||||||
|
|
||||||
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
|
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
|
||||||
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'
|
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'
|
||||||
@ -143,6 +139,9 @@ dependencies {
|
|||||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
|
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
|
||||||
|
|
||||||
|
// Bus
|
||||||
|
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||||
|
|
||||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
|
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
|
||||||
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
|
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
|
||||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0'
|
androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0'
|
||||||
|
Binary file not shown.
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android;
|
package im.vector.matrix.android;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
|||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import java.util.*
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal class CryptoStoreHelper {
|
internal class CryptoStoreHelper {
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ internal class CryptoStoreHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createCredential() = Credentials(
|
fun createCredential() = Credentials(
|
||||||
userId = "userId_" + Random().nextInt(),
|
userId = "userId_" + Random.nextInt(),
|
||||||
homeServer = "http://matrix.org",
|
homeServer = "http://matrix.org",
|
||||||
accessToken = "access_token",
|
accessToken = "access_token",
|
||||||
refreshToken = null,
|
refreshToken = null,
|
||||||
|
@ -19,11 +19,7 @@ package im.vector.matrix.android.session.room.timeline
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.internal.database.helper.add
|
import im.vector.matrix.android.internal.database.helper.*
|
||||||
import im.vector.matrix.android.internal.database.helper.addAll
|
|
||||||
import im.vector.matrix.android.internal.database.helper.isUnlinked
|
|
||||||
import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
|
||||||
import im.vector.matrix.android.internal.database.helper.merge
|
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.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
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.session.room.timeline
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
||||||
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.internal.session.room.timeline.TokenChunkEventPersistor
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
@ -24,7 +23,7 @@ import kotlin.random.Random
|
|||||||
|
|
||||||
internal class FakeGetContextOfEventTask constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
|
internal class FakeGetContextOfEventTask constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
|
||||||
|
|
||||||
override suspend fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
|
override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
|
||||||
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||||
val tokenChunkEvent = FakeTokenChunkEvent(
|
val tokenChunkEvent = FakeTokenChunkEvent(
|
||||||
Random.nextLong(System.currentTimeMillis()).toString(),
|
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.session.room.timeline
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -24,7 +23,7 @@ import kotlin.random.Random
|
|||||||
|
|
||||||
internal class FakePaginationTask @Inject constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
internal class FakePaginationTask @Inject constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
||||||
|
|
||||||
override suspend fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
|
override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
|
||||||
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||||
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||||
|
@ -28,7 +28,7 @@ object MatrixPatterns {
|
|||||||
// regex pattern to find matrix user ids in a string.
|
// regex pattern to find matrix user ids in a string.
|
||||||
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
|
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
|
||||||
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
|
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
|
||||||
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// regex pattern to find room ids in a string.
|
// regex pattern to find room ids in a string.
|
||||||
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
||||||
@ -137,4 +137,23 @@ object MatrixPatterns {
|
|||||||
fun isGroupId(str: String?): Boolean {
|
fun isGroupId(str: String?): Boolean {
|
||||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract server name from a matrix id
|
||||||
|
*
|
||||||
|
* @param matrixId
|
||||||
|
* @return null if not found or if matrixId is null
|
||||||
|
*/
|
||||||
|
fun extractServerNameFromId(matrixId: String?): String? {
|
||||||
|
if (matrixId == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val index = matrixId.indexOf(":")
|
||||||
|
|
||||||
|
return if (index == -1) {
|
||||||
|
null
|
||||||
|
} else matrixId.substring(index + 1)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,23 @@
|
|||||||
package im.vector.matrix.android.api.auth
|
package im.vector.matrix.android.api.auth
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to authenticate to a matrix server.
|
* This interface defines methods to authenticate to a matrix server.
|
||||||
*/
|
*/
|
||||||
interface Authenticator {
|
interface Authenticator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the supported login flows for this homeserver
|
||||||
|
*/
|
||||||
|
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResponse>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param homeServerConnectionConfig this param is used to configure the Homeserver
|
* @param homeServerConnectionConfig this param is used to configure the Homeserver
|
||||||
* @param login the login field
|
* @param login the login field
|
||||||
@ -56,4 +63,9 @@ interface Authenticator {
|
|||||||
* @return the associated session if any, or null
|
* @return the associated session if any, or null
|
||||||
*/
|
*/
|
||||||
fun getSession(sessionParams: SessionParams): Session?
|
fun getSession(sessionParams: SessionParams): Session?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a session after a SSO successful login
|
||||||
|
*/
|
||||||
|
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
|
||||||
}
|
}
|
@ -31,7 +31,7 @@ import okhttp3.TlsVersion
|
|||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class HomeServerConnectionConfig(
|
data class HomeServerConnectionConfig(
|
||||||
val homeServerUri: Uri,
|
val homeServerUri: Uri,
|
||||||
val identityServerUri: Uri,
|
val identityServerUri: Uri? = null,
|
||||||
val antiVirusServerUri: Uri? = null,
|
val antiVirusServerUri: Uri? = null,
|
||||||
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(),
|
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(),
|
||||||
val shouldPin: Boolean = false,
|
val shouldPin: Boolean = false,
|
||||||
@ -48,7 +48,7 @@ data class HomeServerConnectionConfig(
|
|||||||
class Builder {
|
class Builder {
|
||||||
|
|
||||||
private lateinit var homeServerUri: Uri
|
private lateinit var homeServerUri: Uri
|
||||||
private lateinit var identityServerUri: Uri
|
private var identityServerUri: Uri? = null
|
||||||
private var antiVirusServerUri: Uri? = null
|
private var antiVirusServerUri: Uri? = null
|
||||||
private val allowedFingerprints: MutableList<Fingerprint> = ArrayList()
|
private val allowedFingerprints: MutableList<Fingerprint> = ArrayList()
|
||||||
private var shouldPin: Boolean = false
|
private var shouldPin: Boolean = false
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.matrix.android.api.comparators
|
package im.vector.matrix.android.api.comparators
|
||||||
|
|
||||||
import im.vector.matrix.android.api.interfaces.DatedObject
|
import im.vector.matrix.android.api.interfaces.DatedObject
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
object DatedObjectComparators {
|
object DatedObjectComparators {
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.extensions
|
|||||||
import im.vector.matrix.android.api.comparators.DatedObjectComparators
|
import im.vector.matrix.android.api.comparators.DatedObjectComparators
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import java.util.*
|
import java.util.Collections
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* MXDeviceInfo
|
* MXDeviceInfo
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.failure
|
||||||
|
|
||||||
|
// This data class will be sent to the bus
|
||||||
|
data class ConsentNotGivenError(
|
||||||
|
val consentUri: String
|
||||||
|
)
|
@ -31,6 +31,7 @@ import java.io.IOException
|
|||||||
*/
|
*/
|
||||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||||
|
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
|
||||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||||
|
@ -26,8 +26,13 @@ import com.squareup.moshi.JsonClass
|
|||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MatrixError(
|
data class MatrixError(
|
||||||
@Json(name = "errcode") val code: String,
|
@Json(name = "errcode") val code: String,
|
||||||
@Json(name = "error") val message: String
|
@Json(name = "error") val message: String,
|
||||||
) {
|
|
||||||
|
@Json(name = "consent_uri") val consentUri: String? = null,
|
||||||
|
// RESOURCE_LIMIT_EXCEEDED data
|
||||||
|
@Json(name = "limit_type") val limitType: String? = null,
|
||||||
|
@Json(name = "admin_contact") val adminUri: String? = null) {
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val FORBIDDEN = "M_FORBIDDEN"
|
const val FORBIDDEN = "M_FORBIDDEN"
|
||||||
@ -55,5 +60,8 @@ data class MatrixError(
|
|||||||
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||||
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||||
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||||
|
|
||||||
|
// Possible value for "limit_type"
|
||||||
|
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -42,7 +42,7 @@ object MatrixLinkify {
|
|||||||
hasMatch = true
|
hasMatch = true
|
||||||
val startPos = match.range.first
|
val startPos = match.range.first
|
||||||
if (startPos == 0 || text[startPos - 1] != '/') {
|
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||||
val endPos = match.range.last
|
val endPos = match.range.last + 1
|
||||||
val url = text.substring(match.range)
|
val url = text.substring(match.range)
|
||||||
val span = MatrixPermalinkSpan(url, callback)
|
val span = MatrixPermalinkSpan(url, callback)
|
||||||
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.permalinks
|
|||||||
|
|
||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan.Callback
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back.
|
* This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back.
|
||||||
|
@ -19,77 +19,92 @@ import im.vector.matrix.android.api.pushrules.rest.PushRule
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
||||||
class Action(val type: Type) {
|
sealed class Action {
|
||||||
|
object Notify : Action()
|
||||||
|
object DoNotNotify : Action()
|
||||||
|
data class Sound(val sound: String) : Action()
|
||||||
|
data class Highlight(val highlight: Boolean) : Action()
|
||||||
|
}
|
||||||
|
|
||||||
enum class Type(val value: String) {
|
|
||||||
NOTIFY("notify"),
|
|
||||||
DONT_NOTIFY("dont_notify"),
|
|
||||||
COALESCE("coalesce"),
|
|
||||||
SET_TWEAK("set_tweak");
|
|
||||||
|
|
||||||
companion object {
|
private const val ACTION_NOTIFY = "notify"
|
||||||
|
private const val ACTION_DONT_NOTIFY = "dont_notify"
|
||||||
|
private const val ACTION_COALESCE = "coalesce"
|
||||||
|
|
||||||
fun safeValueOf(value: String): Type? {
|
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
|
||||||
try {
|
private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
|
||||||
return valueOf(value)
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tweak_action: String? = null
|
private const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
|
||||||
var stringValue: String? = null
|
private const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
|
||||||
var boolValue: Boolean? = null
|
|
||||||
|
|
||||||
companion object {
|
private const val ACTION_OBJECT_VALUE_KEY = "value"
|
||||||
fun mapFrom(pushRule: PushRule): List<Action>? {
|
private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
|
||||||
val actions = ArrayList<Action>()
|
|
||||||
pushRule.actions.forEach { actionStrOrObj ->
|
/**
|
||||||
if (actionStrOrObj is String) {
|
* Ref: https://matrix.org/docs/spec/client_server/latest#actions
|
||||||
|
*
|
||||||
|
* Convert
|
||||||
|
* <pre>
|
||||||
|
* "actions": [
|
||||||
|
* "notify",
|
||||||
|
* {
|
||||||
|
* "set_tweak": "sound",
|
||||||
|
* "value": "default"
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "set_tweak": "highlight"
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* To
|
||||||
|
* [
|
||||||
|
* Action.Notify,
|
||||||
|
* Action.Sound("default"),
|
||||||
|
* Action.Highlight(true)
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
fun PushRule.getActions(): List<Action> {
|
||||||
|
val result = ArrayList<Action>()
|
||||||
|
|
||||||
|
actions.forEach { actionStrOrObj ->
|
||||||
when (actionStrOrObj) {
|
when (actionStrOrObj) {
|
||||||
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
|
ACTION_NOTIFY -> Action.Notify
|
||||||
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
|
ACTION_DONT_NOTIFY -> Action.DoNotNotify
|
||||||
|
is Map<*, *> -> {
|
||||||
|
when (actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]) {
|
||||||
|
ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
|
||||||
|
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
|
||||||
|
Action.Sound(stringValue)
|
||||||
|
}
|
||||||
|
// When the value is not there, default sound (not specified by the spec)
|
||||||
|
?: Action.Sound(ACTION_OBJECT_VALUE_VALUE_DEFAULT)
|
||||||
|
|
||||||
|
}
|
||||||
|
ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
|
||||||
|
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
|
||||||
|
Action.Highlight(boolValue)
|
||||||
|
}
|
||||||
|
// When the value is not there, default is true, says the spec
|
||||||
|
?: Action.Highlight(true)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
Timber.w("Unsupported set_tweak value ${actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Timber.w("Unsupported action type $actionStrOrObj")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}?.let {
|
}?.let {
|
||||||
actions.add(it)
|
result.add(it)
|
||||||
}
|
|
||||||
} else if (actionStrOrObj is Map<*, *>) {
|
|
||||||
val tweakAction = actionStrOrObj["set_tweak"] as? String
|
|
||||||
when (tweakAction) {
|
|
||||||
"sound" -> {
|
|
||||||
(actionStrOrObj["value"] as? String)?.let { stringValue ->
|
|
||||||
Action(Action.Type.SET_TWEAK).also {
|
|
||||||
it.tweak_action = "sound"
|
|
||||||
it.stringValue = stringValue
|
|
||||||
actions.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"highlight" -> {
|
|
||||||
(actionStrOrObj["value"] as? Boolean)?.let { boolValue ->
|
|
||||||
Action(Action.Type.SET_TWEAK).also {
|
|
||||||
it.tweak_action = "highlight"
|
|
||||||
it.boolValue = boolValue
|
|
||||||
actions.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if (actions.isEmpty()) null else actions
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,20 +18,21 @@ package im.vector.matrix.android.api.pushrules
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
interface PushRuleService {
|
interface PushRuleService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the push rules from the server
|
* Fetch the push rules from the server
|
||||||
*/
|
*/
|
||||||
fun fetchPushRules(scope: String = "global")
|
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
|
||||||
|
|
||||||
//TODO get push rule set
|
//TODO get push rule set
|
||||||
fun getPushRules(scope: String = "global"): List<PushRule>
|
fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
|
||||||
|
|
||||||
//TODO update rule
|
//TODO update rule
|
||||||
|
|
||||||
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>)
|
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun addPushRuleListener(listener: PushRuleListener)
|
fun addPushRuleListener(listener: PushRuleListener)
|
||||||
|
|
||||||
@ -41,6 +42,8 @@ interface PushRuleService {
|
|||||||
|
|
||||||
interface PushRuleListener {
|
interface PushRuleListener {
|
||||||
fun onMatchRule(event: Event, actions: List<Action>)
|
fun onMatchRule(event: Event, actions: List<Action>)
|
||||||
|
fun onRoomLeft(roomId: String)
|
||||||
|
fun onEventRedacted(redactedEventId: String)
|
||||||
fun batchFinish()
|
fun batchFinish()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,18 +18,17 @@ 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.api.session.room.RoomService
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$")
|
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
||||||
|
|
||||||
class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) {
|
class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
|
||||||
|
|
||||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||||
return conditionResolver.resolveRoomMemberCountCondition(this)
|
return conditionResolver.resolveRoomMemberCountCondition(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun technicalDescription(): String {
|
override fun technicalDescription(): String {
|
||||||
return "Room member count is $`is`"
|
return "Room member count is $iz"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
||||||
@ -56,12 +55,9 @@ class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_co
|
|||||||
*/
|
*/
|
||||||
private fun parseIsField(): Pair<String?, Int>? {
|
private fun parseIsField(): Pair<String?, Int>? {
|
||||||
try {
|
try {
|
||||||
val match = regex.matcher(`is`)
|
val match = regex.find(iz) ?: return null
|
||||||
if (match.find()) {
|
val (prefix, count) = match.destructured
|
||||||
val prefix = match.group(1)
|
return prefix to count.toInt()
|
||||||
val count = match.group(2).toInt()
|
|
||||||
return prefix to count
|
|
||||||
}
|
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Timber.d(t)
|
Timber.d(t)
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ object RuleIds {
|
|||||||
|
|
||||||
// Default Underride Rules
|
// Default Underride Rules
|
||||||
const val RULE_ID_CALL = ".m.rule.call"
|
const val RULE_ID_CALL = ".m.rule.call"
|
||||||
const val RULE_ID_one_to_one_encrypted_room = ".m.rule.encrypted_room_one_to_one"
|
const val RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM = ".m.rule.encrypted_room_one_to_one"
|
||||||
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
|
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
|
||||||
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
|
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
|
||||||
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"
|
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"
|
||||||
|
@ -13,9 +13,8 @@
|
|||||||
* 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.pushrules
|
||||||
|
|
||||||
package im.vector.riotx.features.home.room
|
object RuleScope {
|
||||||
|
const val GLOBAL = "global"
|
||||||
import im.vector.riotx.core.utils.RxStore
|
}
|
||||||
|
|
||||||
class VisibleRoomStore : RxStore<String>()
|
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.pushrules
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
|
*/
|
||||||
|
enum class RuleSetKey(val value: String) {
|
||||||
|
CONTENT("content"),
|
||||||
|
OVERRIDE("override"),
|
||||||
|
ROOM("room"),
|
||||||
|
SENDER("sender"),
|
||||||
|
UNDERRIDE("underride")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules-scope-kind-ruleid
|
||||||
|
*/
|
||||||
|
typealias RuleKind = RuleSetKey
|
@ -71,7 +71,7 @@ data class PushCondition(
|
|||||||
this.key?.let { SenderNotificationPermissionCondition(it) }
|
this.key?.let { SenderNotificationPermissionCondition(it) }
|
||||||
}
|
}
|
||||||
Condition.Kind.UNRECOGNIZE -> {
|
Condition.Kind.UNRECOGNIZE -> {
|
||||||
Timber.e("Unknwon kind $kind")
|
Timber.e("Unknown kind $kind")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ import androidx.lifecycle.LiveData
|
|||||||
|
|
||||||
interface InitialSyncProgressService {
|
interface InitialSyncProgressService {
|
||||||
|
|
||||||
fun getLiveStatus() : LiveData<Status?>
|
fun getInitialSyncProgressStatus() : LiveData<Status?>
|
||||||
|
|
||||||
data class Status(
|
data class Status(
|
||||||
@StringRes val statusText: Int?,
|
@StringRes val statusText: Int,
|
||||||
val percentProgress: Int = 0
|
val percentProgress: Int = 0
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.group.GroupService
|
|||||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
|
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
|
||||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||||
import im.vector.matrix.android.api.session.sync.FilterService
|
import im.vector.matrix.android.api.session.sync.FilterService
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
@ -50,13 +51,17 @@ interface Session :
|
|||||||
FileService,
|
FileService,
|
||||||
PushRuleService,
|
PushRuleService,
|
||||||
PushersService,
|
PushersService,
|
||||||
InitialSyncProgressService {
|
InitialSyncProgressService,
|
||||||
|
SecureStorageService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The params associated to the session
|
* The params associated to the session
|
||||||
*/
|
*/
|
||||||
val sessionParams: SessionParams
|
val sessionParams: SessionParams
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful shortcut to get access to the userId
|
||||||
|
*/
|
||||||
val myUserId: String
|
val myUserId: String
|
||||||
get() = sessionParams.credentials.userId
|
get() = sessionParams.credentials.userId
|
||||||
|
|
||||||
@ -84,7 +89,7 @@ interface Session :
|
|||||||
/**
|
/**
|
||||||
* This method start the sync thread.
|
* This method start the sync thread.
|
||||||
*/
|
*/
|
||||||
fun startSync()
|
fun startSync(fromForeground: Boolean)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method stop the sync thread.
|
* This method stop the sync thread.
|
||||||
|
@ -26,14 +26,12 @@ import im.vector.matrix.android.api.session.events.model.Content
|
|||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
interface CryptoService {
|
interface CryptoService {
|
||||||
|
|
||||||
@ -111,8 +109,6 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>)
|
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>)
|
||||||
|
|
||||||
fun clearCryptoCache(callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||||
|
|
||||||
fun removeSessionListener(listener: NewSessionListener)
|
fun removeSessionListener(listener: NewSessionListener)
|
||||||
|
@ -20,6 +20,9 @@ import android.text.TextUtils
|
|||||||
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.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
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
|
||||||
@ -79,9 +82,15 @@ data class Event(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
@Transient
|
||||||
var mxDecryptionResult: OlmDecryptionResult? = null
|
var mxDecryptionResult: OlmDecryptionResult? = null
|
||||||
|
|
||||||
|
@Transient
|
||||||
var mCryptoError: MXCryptoError.ErrorType? = null
|
var mCryptoError: MXCryptoError.ErrorType? = null
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
var sendState: SendState = SendState.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if event is a state event.
|
* Check if event is a state event.
|
||||||
@ -95,42 +104,6 @@ data class Event(
|
|||||||
// Crypto
|
// Crypto
|
||||||
//==============================================================================================================
|
//==============================================================================================================
|
||||||
|
|
||||||
// /**
|
|
||||||
// * For encrypted events, the plaintext payload for the event.
|
|
||||||
// * This is a small MXEvent instance with typically value for `type` and 'content' fields.
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// var mClearEvent: Event? = null
|
|
||||||
// private set
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Curve25519 key which we believe belongs to the sender of the event.
|
|
||||||
// * See `senderKey` property.
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// private var mSenderCurve25519Key: String? = null
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own.
|
|
||||||
// * See `claimedEd25519Key` property.
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// private var mClaimedEd25519Key: String? = null
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key.
|
|
||||||
// * See `forwardingCurve25519KeyChain` property.
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// private var mForwardingCurve25519KeyChain: List<String> = ArrayList()
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Decryption error
|
|
||||||
// */
|
|
||||||
// @Transient
|
|
||||||
// var mCryptoError: MXCryptoError? = null
|
|
||||||
// private set
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if this event is encrypted.
|
* @return true if this event is encrypted.
|
||||||
*/
|
*/
|
||||||
@ -138,51 +111,11 @@ data class Event(
|
|||||||
return TextUtils.equals(type, EventType.ENCRYPTED)
|
return TextUtils.equals(type, EventType.ENCRYPTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the clear data on this event.
|
|
||||||
* This is used after decrypting an event; it should not be used by applications.
|
|
||||||
*
|
|
||||||
* @param decryptionResult the decryption result, including the plaintext and some key info.
|
|
||||||
*/
|
|
||||||
// internal fun setClearData(decryptionResult: MXEventDecryptionResult?) {
|
|
||||||
// mClearEvent = null
|
|
||||||
// if (decryptionResult != null) {
|
|
||||||
// if (decryptionResult.clearEvent != null) {
|
|
||||||
// val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
|
||||||
// mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
|
|
||||||
//
|
|
||||||
// if (mClearEvent != null) {
|
|
||||||
// mSenderCurve25519Key = decryptionResult.senderCurve25519Key
|
|
||||||
// mClaimedEd25519Key = decryptionResult.claimedEd25519Key
|
|
||||||
// mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
|
||||||
//
|
|
||||||
// // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
|
|
||||||
// // in the clear event
|
|
||||||
// try {
|
|
||||||
// content?.get("m.relates_to")?.let { clearRelates ->
|
|
||||||
// mClearEvent = mClearEvent?.copy(
|
|
||||||
// content = HashMap(mClearEvent!!.content).apply {
|
|
||||||
// this["m.relates_to"] = clearRelates
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// mCryptoError = null
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The curve25519 key that sent this event.
|
* @return The curve25519 key that sent this event.
|
||||||
*/
|
*/
|
||||||
fun getSenderKey(): String? {
|
fun getSenderKey(): String? {
|
||||||
return mxDecryptionResult?.senderKey
|
return mxDecryptionResult?.senderKey
|
||||||
// return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -190,23 +123,13 @@ data class Event(
|
|||||||
*/
|
*/
|
||||||
fun getKeysClaimed(): Map<String, String> {
|
fun getKeysClaimed(): Map<String, String> {
|
||||||
return mxDecryptionResult?.keysClaimed ?: HashMap()
|
return mxDecryptionResult?.keysClaimed ?: HashMap()
|
||||||
// val res = HashMap<String, String>()
|
|
||||||
//
|
|
||||||
// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key
|
|
||||||
//
|
|
||||||
// if (null != claimedEd25519Key) {
|
|
||||||
// res["ed25519"] = claimedEd25519Key
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return res
|
|
||||||
}
|
}
|
||||||
//
|
|
||||||
/**
|
/**
|
||||||
* @return the event type
|
* @return the event type
|
||||||
*/
|
*/
|
||||||
fun getClearType(): String {
|
fun getClearType(): String {
|
||||||
return mxDecryptionResult?.payload?.get("type")?.toString()
|
return mxDecryptionResult?.payload?.get("type")?.toString() ?: type
|
||||||
?: type//get("type")?.toString() ?: type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,30 +139,8 @@ data class Event(
|
|||||||
return mxDecryptionResult?.payload?.get("content") as? Content ?: content
|
return mxDecryptionResult?.payload?.get("content") as? Content ?: content
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @return the linked crypto error
|
|
||||||
// */
|
|
||||||
// fun getCryptoError(): MXCryptoError? {
|
|
||||||
// return mCryptoError
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Update the linked crypto error
|
|
||||||
// *
|
|
||||||
// * @param error the new crypto error.
|
|
||||||
// */
|
|
||||||
// fun setCryptoError(error: MXCryptoError?) {
|
|
||||||
// mCryptoError = error
|
|
||||||
// if (null != error) {
|
|
||||||
// mClearEvent = null
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
fun toContentStringWithIndent(): String {
|
fun toContentStringWithIndent(): String {
|
||||||
val contentMap = this.toContent()?.toMutableMap() ?: HashMap()
|
val contentMap = toContent()?.toMutableMap() ?: HashMap()
|
||||||
contentMap.remove("mxDecryptionResult")
|
|
||||||
contentMap.remove("mCryptoError")
|
|
||||||
return JSONObject(contentMap).toString(4)
|
return JSONObject(contentMap).toString(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,6 +173,7 @@ data class Event(
|
|||||||
if (redacts != other.redacts) return false
|
if (redacts != other.redacts) return false
|
||||||
if (mxDecryptionResult != other.mxDecryptionResult) return false
|
if (mxDecryptionResult != other.mxDecryptionResult) return false
|
||||||
if (mCryptoError != other.mCryptoError) return false
|
if (mCryptoError != other.mCryptoError) return false
|
||||||
|
if (sendState != other.sendState) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -289,6 +191,27 @@ data class Event(
|
|||||||
result = 31 * result + (redacts?.hashCode() ?: 0)
|
result = 31 * result + (redacts?.hashCode() ?: 0)
|
||||||
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
|
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
|
||||||
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
|
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + sendState.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Event.isTextMessage(): Boolean {
|
||||||
|
return getClearType() == EventType.MESSAGE
|
||||||
|
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||||
|
MessageType.MSGTYPE_TEXT,
|
||||||
|
MessageType.MSGTYPE_EMOTE,
|
||||||
|
MessageType.MSGTYPE_NOTICE -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Event.isImageMessage(): Boolean {
|
||||||
|
return getClearType() == EventType.MESSAGE
|
||||||
|
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||||
|
MessageType.MSGTYPE_IMAGE -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ 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 {
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ object RelationType {
|
|||||||
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"
|
||||||
/** ets 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"
|
||||||
|
|
||||||
}
|
}
|
@ -16,12 +16,15 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.group.model
|
package im.vector.matrix.android.api.session.group.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds some data of a group.
|
* This class holds some data of a group.
|
||||||
* It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService]
|
* It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService]
|
||||||
*/
|
*/
|
||||||
data class GroupSummary(
|
data class GroupSummary(
|
||||||
val groupId: String,
|
val groupId: String,
|
||||||
|
val membership: Membership,
|
||||||
val displayName: String = "",
|
val displayName: String = "",
|
||||||
val shortDescription: String = "",
|
val shortDescription: String = "",
|
||||||
val avatarUrl: String = "",
|
val avatarUrl: String = "",
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
package im.vector.matrix.android.api.session.pushers
|
package im.vector.matrix.android.api.session.pushers
|
||||||
|
|
||||||
data class Pusher(
|
data class Pusher(
|
||||||
|
|
||||||
val userId: String,
|
|
||||||
|
|
||||||
val pushKey: String,
|
val pushKey: String,
|
||||||
val kind: String,
|
val kind: String,
|
||||||
val appId: String,
|
val appId: String,
|
||||||
|
@ -17,7 +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 java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
interface PushersService {
|
interface PushersService {
|
||||||
|
@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.members.MembershipService
|
|||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
|
import im.vector.matrix.android.api.session.room.send.DraftService
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
import im.vector.matrix.android.api.session.room.send.SendService
|
||||||
import im.vector.matrix.android.api.session.room.state.StateService
|
import im.vector.matrix.android.api.session.room.state.StateService
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||||
@ -32,6 +33,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
|||||||
interface Room :
|
interface Room :
|
||||||
TimelineService,
|
TimelineService,
|
||||||
SendService,
|
SendService,
|
||||||
|
DraftService,
|
||||||
ReadService,
|
ReadService,
|
||||||
MembershipService,
|
MembershipService,
|
||||||
StateService,
|
StateService,
|
||||||
|
@ -30,20 +30,17 @@ interface RoomDirectoryService {
|
|||||||
/**
|
/**
|
||||||
* Get rooms from directory
|
* Get rooms from directory
|
||||||
*/
|
*/
|
||||||
fun getPublicRooms(server: String?,
|
fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
||||||
publicRoomsParams: PublicRoomsParams,
|
|
||||||
callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join a room by id
|
* Join a room by id
|
||||||
*/
|
*/
|
||||||
fun joinRoom(roomId: String,
|
fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the overall metadata about protocols supported by the homeserver.
|
* Fetches the overall metadata about protocols supported by the homeserver.
|
||||||
* Includes both the available protocols and all fields required for queries against each protocol.
|
* Includes both the available protocols and all fields required for queries against each protocol.
|
||||||
*/
|
*/
|
||||||
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>)
|
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable
|
||||||
|
|
||||||
}
|
}
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get rooms. It's implemented at the session level.
|
* This interface defines methods to get rooms. It's implemented at the session level.
|
||||||
@ -27,10 +28,18 @@ import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
|||||||
interface RoomService {
|
interface RoomService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a room
|
* Create a room asynchronously
|
||||||
*/
|
*/
|
||||||
fun createRoom(createRoomParams: CreateRoomParams,
|
fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable
|
||||||
callback: MatrixCallback<String>)
|
|
||||||
|
/**
|
||||||
|
* Join a room by id
|
||||||
|
* @param roomId the roomId of the room to join
|
||||||
|
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
|
||||||
|
*/
|
||||||
|
fun joinRoom(roomId: String,
|
||||||
|
viaServers: List<String> = emptyList(),
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a room from a roomId
|
* Get a room from a roomId
|
||||||
|
@ -13,14 +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.api.pushrules
|
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.failure
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
|
||||||
|
sealed class CreateRoomFailure : Failure.FeatureFailure() {
|
||||||
|
|
||||||
|
object CreatedWithTimeout: CreateRoomFailure()
|
||||||
|
|
||||||
enum class RulesetKey(val value: String) {
|
|
||||||
CONTENT("content"),
|
|
||||||
OVERRIDE("override"),
|
|
||||||
ROOM("room"),
|
|
||||||
SENDER("sender"),
|
|
||||||
UNDERRIDE("underride"),
|
|
||||||
UNKNOWN("")
|
|
||||||
}
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.failure
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
|
||||||
|
sealed class JoinRoomFailure : Failure.FeatureFailure() {
|
||||||
|
|
||||||
|
object JoinedWithTimeout : JoinRoomFailure()
|
||||||
|
|
||||||
|
}
|
@ -30,7 +30,7 @@ interface MembershipService {
|
|||||||
* This methods load all room members if it was done yet.
|
* This methods load all room members if it was done yet.
|
||||||
* @return a [Cancelable]
|
* @return a [Cancelable]
|
||||||
*/
|
*/
|
||||||
fun loadRoomMembersIfNeeded(): Cancelable
|
fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the roomMember with userId or null.
|
* Return the roomMember with userId or null.
|
||||||
@ -52,16 +52,17 @@ interface MembershipService {
|
|||||||
/**
|
/**
|
||||||
* Invite a user in the room
|
* Invite a user in the room
|
||||||
*/
|
*/
|
||||||
fun invite(userId: String, callback: MatrixCallback<Unit>)
|
fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join the room, or accept an invitation.
|
* Join the room, or accept an invitation.
|
||||||
*/
|
*/
|
||||||
fun join(callback: MatrixCallback<Unit>)
|
|
||||||
|
fun join(viaServers: List<String> = emptyList(), callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leave the room, or reject an invitation.
|
* Leave the room, or reject an invitation.
|
||||||
*/
|
*/
|
||||||
fun leave(callback: MatrixCallback<Unit>)
|
fun leave(callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
}
|
}
|
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
|
||||||
data class ReadReceipt(
|
data class ReadReceipt(
|
||||||
val userId: String,
|
val user: User,
|
||||||
val eventId: String,
|
|
||||||
val originServerTs: Long
|
val originServerTs: Long
|
||||||
)
|
)
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
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.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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,10 +30,17 @@ data class RoomSummary(
|
|||||||
val topic: String = "",
|
val topic: String = "",
|
||||||
val avatarUrl: String = "",
|
val avatarUrl: String = "",
|
||||||
val isDirect: Boolean = false,
|
val isDirect: Boolean = false,
|
||||||
val latestEvent: TimelineEvent? = null,
|
val latestPreviewableEvent: TimelineEvent? = null,
|
||||||
val otherMemberIds: List<String> = emptyList(),
|
val otherMemberIds: List<String> = emptyList(),
|
||||||
val notificationCount: Int = 0,
|
val notificationCount: Int = 0,
|
||||||
val highlightCount: Int = 0,
|
val highlightCount: Int = 0,
|
||||||
|
val hasUnreadMessages: Boolean = false,
|
||||||
val tags: List<RoomTag> = emptyList(),
|
val tags: List<RoomTag> = emptyList(),
|
||||||
val membership: Membership = Membership.NONE
|
val membership: Membership = Membership.NONE,
|
||||||
)
|
val versioningState: VersioningState = VersioningState.NONE,
|
||||||
|
val userDrafts: List<UserDraft> = emptyList()
|
||||||
|
) {
|
||||||
|
|
||||||
|
val isVersioned: Boolean
|
||||||
|
get() = versioningState != VersioningState.NONE
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
enum class VersioningState {
|
||||||
|
NONE,
|
||||||
|
UPGRADED_ROOM_NOT_JOINED,
|
||||||
|
UPGRADED_ROOM_JOINED
|
||||||
|
}
|
@ -20,7 +20,6 @@ import android.util.Patterns
|
|||||||
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.MatrixPatterns.isUserId
|
import im.vector.matrix.android.api.MatrixPatterns.isUserId
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
@ -29,7 +28,6 @@ import im.vector.matrix.android.api.session.room.model.PowerLevels
|
|||||||
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
|
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter to create a room, with facilities functions to configure it
|
* Parameter to create a room, with facilities functions to configure it
|
||||||
@ -133,7 +131,7 @@ class CreateRoomParams {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (null == initialStates) {
|
if (null == initialStates) {
|
||||||
initialStates = Arrays.asList<Event>(algoEvent)
|
initialStates = mutableListOf(algoEvent)
|
||||||
} else {
|
} else {
|
||||||
initialStates!!.add(algoEvent)
|
initialStates!!.add(algoEvent)
|
||||||
}
|
}
|
||||||
@ -166,7 +164,7 @@ class CreateRoomParams {
|
|||||||
content = contentMap.toContent())
|
content = contentMap.toContent())
|
||||||
|
|
||||||
if (null == initialStates) {
|
if (null == initialStates) {
|
||||||
initialStates = Arrays.asList<Event>(historyVisibilityEvent)
|
initialStates = mutableListOf(historyVisibilityEvent)
|
||||||
} else {
|
} else {
|
||||||
initialStates!!.add(historyVisibilityEvent)
|
initialStates!!.add(historyVisibilityEvent)
|
||||||
}
|
}
|
||||||
@ -220,14 +218,13 @@ class CreateRoomParams {
|
|||||||
* @param ids the participant ids to add.
|
* @param ids the participant ids to add.
|
||||||
*/
|
*/
|
||||||
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
|
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
|
||||||
credentials: Credentials,
|
userId: String,
|
||||||
ids: List<String>) {
|
ids: List<String>) {
|
||||||
for (id in ids) {
|
for (id in ids) {
|
||||||
if (Patterns.EMAIL_ADDRESS.matcher(id).matches()) {
|
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
|
||||||
if (null == invite3pids) {
|
if (null == invite3pids) {
|
||||||
invite3pids = ArrayList()
|
invite3pids = ArrayList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
|
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
|
||||||
medium = ThreePidMedium.EMAIL,
|
medium = ThreePidMedium.EMAIL,
|
||||||
address = id)
|
address = id)
|
||||||
@ -235,7 +232,7 @@ class CreateRoomParams {
|
|||||||
invite3pids!!.add(pid)
|
invite3pids!!.add(pid)
|
||||||
} else if (isUserId(id)) {
|
} else if (isUserId(id)) {
|
||||||
// do not invite oneself
|
// do not invite oneself
|
||||||
if (credentials.userId != id) {
|
if (userId != id) {
|
||||||
if (null == invitedUserIds) {
|
if (null == invitedUserIds) {
|
||||||
invitedUserIds = ArrayList()
|
invitedUserIds = ArrayList()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.session.room.model.create
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A link to an old room in case of room versioning
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Predecessor(
|
||||||
|
@Json(name = "room_id") val roomId: String? = null,
|
||||||
|
@Json(name = "event_id") val eventId: String? = null
|
||||||
|
)
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.model.create
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content of a m.room.create type event
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RoomCreateContent(
|
||||||
|
@Json(name = "creator") val creator: String? = null,
|
||||||
|
@Json(name = "room_version") val roomVersion: String? = null,
|
||||||
|
@Json(name = "predecessor") val predecessor: Predecessor? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -26,3 +26,8 @@ interface MessageContent {
|
|||||||
val relatesTo: RelationDefaultContent?
|
val relatesTo: RelationDefaultContent?
|
||||||
val newContent: Content?
|
val newContent: Content?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun MessageContent?.isReply(): Boolean {
|
||||||
|
return this?.relatesTo?.inReplyTo?.eventId != null
|
||||||
|
}
|
||||||
|
@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model.relation
|
package im.vector.matrix.android.api.session.room.model.relation
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
|
|
||||||
interface RelationContent {
|
interface RelationContent {
|
||||||
|
/** See [RelationType] for known possible values */
|
||||||
val type: String?
|
val type: String?
|
||||||
val eventId: String?
|
val eventId: String?
|
||||||
val inReplyTo: ReplyToContent?
|
val inReplyTo: ReplyToContent?
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
package im.vector.matrix.android.api.session.room.model.relation
|
package im.vector.matrix.android.api.session.room.model.relation
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.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
|
||||||
@ -79,6 +81,25 @@ interface RelationService {
|
|||||||
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit a reply. This is a special case because replies contains fallback text as a prefix.
|
||||||
|
* This method will take the new body (stripped from fallbacks) and re-add them before sending.
|
||||||
|
* @param replyToEdit The event to edit
|
||||||
|
* @param originalTimelineEvent the message that this reply (being edited) is relating to
|
||||||
|
* @param newBodyText The edited body (stripped from in reply to content)
|
||||||
|
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
|
||||||
|
*/
|
||||||
|
fun editReply(replyToEdit: TimelineEvent,
|
||||||
|
originalTimelineEvent: TimelineEvent,
|
||||||
|
newBodyText: String,
|
||||||
|
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's the edit history of the given event
|
||||||
|
*/
|
||||||
|
fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reply to an event in the timeline (must be in same room)
|
* Reply to an event in the timeline (must be in same room)
|
||||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
||||||
@ -91,4 +112,6 @@ interface RelationService {
|
|||||||
autoMarkdown: Boolean = false): Cancelable?
|
autoMarkdown: Boolean = false): Cancelable?
|
||||||
|
|
||||||
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -21,5 +21,5 @@ import com.squareup.moshi.JsonClass
|
|||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ReplyToContent(
|
data class ReplyToContent(
|
||||||
@Json(name = "event_id") val eventId: String
|
@Json(name = "event_id") val eventId: String? = null
|
||||||
)
|
)
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.session.room.model.tombstone
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to contains Tombstone information
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RoomTombstoneContent(
|
||||||
|
@Json(name = "body") val body: String? = null,
|
||||||
|
@Json(name = "replacement_room") val replacementRoom: String?
|
||||||
|
)
|
@ -16,7 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.read
|
package im.vector.matrix.android.api.session.room.read
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
||||||
@ -39,4 +41,6 @@ interface ReadService {
|
|||||||
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun isEventRead(eventId: String): Boolean
|
fun isEventRead(eventId: String): Boolean
|
||||||
|
|
||||||
|
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
||||||
}
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.send
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
|
||||||
|
interface DraftService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save or update a draft to the room
|
||||||
|
*/
|
||||||
|
fun saveDraft(draft: UserDraft)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the last draft, basically just after sending the message
|
||||||
|
*/
|
||||||
|
fun deleteDraft()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current drafts if any, as a live data
|
||||||
|
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
|
||||||
|
*/
|
||||||
|
fun getDraftsLive(): LiveData<List<UserDraft>>
|
||||||
|
|
||||||
|
}
|
@ -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.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
|
|
||||||
@ -65,4 +66,31 @@ interface SendService {
|
|||||||
*/
|
*/
|
||||||
fun redactEvent(event: Event, reason: String?): Cancelable
|
fun redactEvent(event: Event, reason: String?): Cancelable
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule this message to be resent
|
||||||
|
* @param localEcho the unsent local echo
|
||||||
|
*/
|
||||||
|
fun resendTextMessage(localEcho: TimelineEvent): Cancelable?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule this message to be resent
|
||||||
|
* @param localEcho the unsent local echo
|
||||||
|
*/
|
||||||
|
fun resendMediaMessage(localEcho: TimelineEvent): Cancelable?
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove this failed message from the timeline
|
||||||
|
* @param localEcho the unsent local echo
|
||||||
|
*/
|
||||||
|
fun deleteFailedEcho(localEcho: TimelineEvent)
|
||||||
|
|
||||||
|
fun clearSendingQueue()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resend all failed messages one by one (and keep order)
|
||||||
|
*/
|
||||||
|
fun resendAllFailedMessages()
|
||||||
|
|
||||||
}
|
}
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.send
|
package im.vector.matrix.android.api.session.room.send
|
||||||
|
|
||||||
|
|
||||||
enum class SendState {
|
enum class SendState {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
// the event has not been sent
|
// the event has not been sent
|
||||||
@ -33,12 +34,19 @@ enum class SendState {
|
|||||||
// the event failed to be sent because some unknown devices have been found while encrypting it
|
// the event failed to be sent because some unknown devices have been found while encrypting it
|
||||||
FAILED_UNKNOWN_DEVICES;
|
FAILED_UNKNOWN_DEVICES;
|
||||||
|
|
||||||
fun isSent(): Boolean {
|
internal companion object {
|
||||||
return this == SENT || this == SYNCED
|
val HAS_FAILED_STATES = listOf(UNDELIVERED, FAILED_UNKNOWN_DEVICES)
|
||||||
|
val IS_SENT_STATES = listOf(SENT, SYNCED)
|
||||||
|
val IS_SENDING_STATES = listOf(UNSENT, ENCRYPTING, SENDING)
|
||||||
|
val PENDING_STATES = IS_SENDING_STATES + HAS_FAILED_STATES
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasFailed(): Boolean {
|
fun isSent() = IS_SENT_STATES.contains(this)
|
||||||
return this == UNDELIVERED || this == FAILED_UNKNOWN_DEVICES
|
|
||||||
}
|
fun hasFailed() = HAS_FAILED_STATES.contains(this)
|
||||||
|
|
||||||
|
fun isSending() = IS_SENDING_STATES.contains(this)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.send
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a user draft:
|
||||||
|
* REGULAR: draft of a classical message
|
||||||
|
* QUOTE: draft of a message which quotes another message
|
||||||
|
* EDIT: draft of an edition of a message
|
||||||
|
* REPLY: draft of a reply of another message
|
||||||
|
*/
|
||||||
|
sealed class UserDraft(open val text: String) {
|
||||||
|
data class REGULAR(override val text: String) : UserDraft(text)
|
||||||
|
data class QUOTE(val linkedEventId: String, override val text: String) : UserDraft(text)
|
||||||
|
data class EDIT(val linkedEventId: String, override val text: String) : UserDraft(text)
|
||||||
|
data class REPLY(val linkedEventId: String, override val text: String) : UserDraft(text)
|
||||||
|
|
||||||
|
fun isValid(): Boolean {
|
||||||
|
return when (this) {
|
||||||
|
is REGULAR -> text.isNotBlank()
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.matrix.android.api.session.room.state
|
package im.vector.matrix.android.api.session.room.state
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
interface StateService {
|
interface StateService {
|
||||||
|
|
||||||
@ -25,4 +26,6 @@ interface StateService {
|
|||||||
*/
|
*/
|
||||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
fun getStateEvent(eventType: String): Event?
|
||||||
|
|
||||||
}
|
}
|
@ -56,6 +56,9 @@ interface Timeline {
|
|||||||
*/
|
*/
|
||||||
fun paginate(direction: Direction, count: Int)
|
fun paginate(direction: Direction, count: Int)
|
||||||
|
|
||||||
|
fun pendingEventCount() : Int
|
||||||
|
|
||||||
|
fun failedToDeliverEventCount() : Int
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
/**
|
/**
|
||||||
|
@ -18,10 +18,14 @@ package im.vector.matrix.android.api.session.room.timeline
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||||
|
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline.
|
* This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline.
|
||||||
@ -35,8 +39,8 @@ data class TimelineEvent(
|
|||||||
val senderName: String?,
|
val senderName: String?,
|
||||||
val isUniqueDisplayName: Boolean,
|
val isUniqueDisplayName: Boolean,
|
||||||
val senderAvatar: String?,
|
val senderAvatar: String?,
|
||||||
val sendState: SendState,
|
val annotations: EventAnnotationsSummary? = null,
|
||||||
val annotations: EventAnnotationsSummary? = null
|
val readReceipts: List<ReadReceipt> = emptyList()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val metadata = HashMap<String, Any>()
|
val metadata = HashMap<String, Any>()
|
||||||
@ -83,8 +87,47 @@ data class TimelineEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the event has been edited
|
||||||
|
*/
|
||||||
|
fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the eventId which was edited by this event if any
|
||||||
|
*/
|
||||||
|
fun TimelineEvent.getEditedEventId(): String? {
|
||||||
|
return root.getClearContent().toModel<MessageContent>()?.relatesTo?.takeIf { it.type == RelationType.REPLACE }?.eventId
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get last MessageContent, after a possible edition
|
* Get last MessageContent, after a possible edition
|
||||||
*/
|
*/
|
||||||
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
|
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
?: root.getClearContent().toModel()
|
?: root.getClearContent().toModel()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last Message body, after a possible edition
|
||||||
|
*/
|
||||||
|
fun TimelineEvent.getLastMessageBody(): String? {
|
||||||
|
val lastMessageContent = getLastMessageContent()
|
||||||
|
|
||||||
|
if (lastMessageContent != null) {
|
||||||
|
return lastMessageContent.newContent?.toModel<MessageContent>()?.body ?: lastMessageContent.body
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun TimelineEvent.getTextEditableContent(): String? {
|
||||||
|
val originalContent = root.getClearContent().toModel<MessageContent>() ?: return null
|
||||||
|
val isReply = originalContent.isReply() || root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId != null
|
||||||
|
val lastContent = getLastMessageContent()
|
||||||
|
return if (isReply) {
|
||||||
|
return extractUsefulTextFromReply(lastContent?.body ?: "")
|
||||||
|
} else {
|
||||||
|
lastContent?.body ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,12 +25,12 @@ interface TimelineService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
|
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
|
||||||
* You can filter the type you want to grab with the allowedTypes param.
|
* You can also configure some settings with the [settings] param.
|
||||||
* @param eventId the optional initial eventId.
|
* @param eventId the optional initial eventId.
|
||||||
* @param allowedTypes the optional filter types
|
* @param settings settings to configure the timeline.
|
||||||
* @return the instantiated timeline
|
* @return the instantiated timeline
|
||||||
*/
|
*/
|
||||||
fun createTimeline(eventId: String?, allowedTypes: List<String>? = null): Timeline
|
fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline
|
||||||
|
|
||||||
|
|
||||||
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.timeline
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class holding setting values for a [Timeline] instance.
|
||||||
|
*/
|
||||||
|
data class TimelineSettings(
|
||||||
|
/**
|
||||||
|
* The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet.
|
||||||
|
*/
|
||||||
|
val initialSize: Int,
|
||||||
|
/**
|
||||||
|
* A flag to filter edit events
|
||||||
|
*/
|
||||||
|
val filterEdits: Boolean = false,
|
||||||
|
/**
|
||||||
|
* A flag to filter by types. It should be used with [allowedTypes] field
|
||||||
|
*/
|
||||||
|
val filterTypes: Boolean = false,
|
||||||
|
/**
|
||||||
|
* If [filterTypes] is true, the list of types allowed by the list.
|
||||||
|
*/
|
||||||
|
val allowedTypes: List<String> = emptyList(),
|
||||||
|
/**
|
||||||
|
* If true, will build read receipts for each event.
|
||||||
|
*/
|
||||||
|
val buildReadReceipts: Boolean = true
|
||||||
|
|
||||||
|
)
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.securestorage
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
interface SecureStorageService {
|
||||||
|
|
||||||
|
fun securelyStoreObject(any: Any, keyAlias: String, outputStream: OutputStream)
|
||||||
|
|
||||||
|
fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T?
|
||||||
|
|
||||||
|
}
|
@ -18,8 +18,9 @@ package im.vector.matrix.android.api.session.sync
|
|||||||
|
|
||||||
sealed class SyncState {
|
sealed class SyncState {
|
||||||
object IDLE : SyncState()
|
object IDLE : SyncState()
|
||||||
data class RUNNING(val catchingUp: Boolean) : SyncState()
|
data class RUNNING(val afterPause: Boolean) : SyncState()
|
||||||
object PAUSED : SyncState()
|
object PAUSED : SyncState()
|
||||||
object KILLING : SyncState()
|
object KILLING : SyncState()
|
||||||
object KILLED : SyncState()
|
object KILLED : SyncState()
|
||||||
|
object NO_NETWORK : SyncState()
|
||||||
}
|
}
|
@ -17,7 +17,10 @@
|
|||||||
package im.vector.matrix.android.api.session.user
|
package im.vector.matrix.android.api.session.user
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get users. It's implemented at the session level.
|
* This interface defines methods to get users. It's implemented at the session level.
|
||||||
@ -31,11 +34,34 @@ interface UserService {
|
|||||||
*/
|
*/
|
||||||
fun getUser(userId: String): User?
|
fun getUser(userId: String): User?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search list of users on server directory.
|
||||||
|
* @param search the searched term
|
||||||
|
* @param limit the max number of users to return
|
||||||
|
* @param excludedUserIds the user ids to filter from the search
|
||||||
|
* @param callback the async callback
|
||||||
|
* @return Cancelable
|
||||||
|
*/
|
||||||
|
fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set<String>, callback: MatrixCallback<List<User>>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe a live user from a userId
|
* Observe a live user from a userId
|
||||||
* @param userId the userId to look for.
|
* @param userId the userId to look for.
|
||||||
* @return a Livedata of user with userId
|
* @return a Livedata of user with userId
|
||||||
*/
|
*/
|
||||||
fun observeUser(userId: String): LiveData<User?>
|
fun liveUser(userId: String): LiveData<User?>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe a live list of users sorted alphabetically
|
||||||
|
* @return a Livedata of users
|
||||||
|
*/
|
||||||
|
fun liveUsers(): LiveData<List<User>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
||||||
|
* @param filter the filter. It will look into userId and displayName.
|
||||||
|
* @return a Livedata of users
|
||||||
|
*/
|
||||||
|
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>
|
||||||
|
|
||||||
}
|
}
|
@ -19,5 +19,6 @@ package im.vector.matrix.android.api.util
|
|||||||
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() {
|
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() {
|
||||||
override fun cancel() {
|
override fun cancel() {
|
||||||
forEach { it.cancel() }
|
forEach { it.cancel() }
|
||||||
|
clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.util
|
||||||
|
|
||||||
|
|
||||||
|
object ContentUtils {
|
||||||
|
fun extractUsefulTextFromReply(repliedBody: String): String {
|
||||||
|
val lines = repliedBody.lines()
|
||||||
|
var wellFormed = repliedBody.startsWith(">")
|
||||||
|
var endOfPreviousFound = false
|
||||||
|
val usefullines = ArrayList<String>()
|
||||||
|
lines.forEach {
|
||||||
|
if (it == "") {
|
||||||
|
endOfPreviousFound = true
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
if (!endOfPreviousFound) {
|
||||||
|
wellFormed = wellFormed && it.startsWith(">")
|
||||||
|
} else {
|
||||||
|
usefullines.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
|
||||||
|
if (repliedBody.startsWith("<mx-reply>")) {
|
||||||
|
val closingTagIndex = repliedBody.lastIndexOf("</mx-reply>")
|
||||||
|
if (closingTagIndex != -1)
|
||||||
|
return repliedBody.substring(closingTagIndex + "</mx-reply>".length).trim()
|
||||||
|
}
|
||||||
|
return repliedBody
|
||||||
|
}
|
||||||
|
}
|
@ -50,16 +50,10 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
||||||
val userId = sessionParams.credentials.userId
|
return sessionComponents.getOrPut(sessionParams.credentials.userId) {
|
||||||
if (sessionComponents.containsKey(userId)) {
|
DaggerSessionComponent
|
||||||
return sessionComponents[userId]!!
|
|
||||||
}
|
|
||||||
return DaggerSessionComponent
|
|
||||||
.factory()
|
.factory()
|
||||||
.create(matrixComponent, sessionParams)
|
.create(matrixComponent, sessionParams)
|
||||||
.also {
|
|
||||||
sessionComponents[sessionParams.credentials.userId] = it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -17,10 +17,12 @@
|
|||||||
package im.vector.matrix.android.internal.auth
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Headers
|
import retrofit2.http.Headers
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
|
||||||
@ -29,6 +31,13 @@ import retrofit2.http.POST
|
|||||||
*/
|
*/
|
||||||
internal interface AuthAPI {
|
internal interface AuthAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the supported login flow
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||||
|
fun getLoginFlows(): Call<LoginFlowResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass params to the server for the current login phase.
|
* Pass params to the server for the current login phase.
|
||||||
* Set all the timeouts to 1 minute
|
* Set all the timeouts to 1 minute
|
||||||
|
@ -23,7 +23,7 @@ import dagger.Provides
|
|||||||
import im.vector.matrix.android.api.auth.Authenticator
|
import im.vector.matrix.android.api.auth.Authenticator
|
||||||
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
||||||
import im.vector.matrix.android.internal.database.configureEncryption
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -33,16 +33,21 @@ internal abstract class AuthModule {
|
|||||||
|
|
||||||
@Module
|
@Module
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val DB_ALIAS = "matrix-sdk-auth"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@AuthDatabase
|
@AuthDatabase
|
||||||
fun providesRealmConfiguration(context: Context): RealmConfiguration {
|
fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||||
val old = File(context.filesDir, "matrix-sdk-auth")
|
val old = File(context.filesDir, "matrix-sdk-auth")
|
||||||
if (old.exists()) {
|
if (old.exists()) {
|
||||||
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
|
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.configureEncryption("matrix-sdk-auth", context)
|
.apply {
|
||||||
|
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||||
|
}
|
||||||
.name("matrix-sdk-auth.realm")
|
.name("matrix-sdk-auth.realm")
|
||||||
.modules(AuthRealmModule())
|
.modules(AuthRealmModule())
|
||||||
.deleteRealmIfMigrationNeeded()
|
.deleteRealmIfMigrationNeeded()
|
||||||
|
@ -25,6 +25,7 @@ import im.vector.matrix.android.api.auth.data.SessionParams
|
|||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
@ -62,17 +63,35 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
|||||||
return sessionManager.getOrCreateSession(sessionParams)
|
return sessionManager.getOrCreateSession(sessionParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResponse>): Cancelable {
|
||||||
|
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
val result = runCatching {
|
||||||
|
getLoginFlowInternal(homeServerConnectionConfig)
|
||||||
|
}
|
||||||
|
result.foldToCallback(callback)
|
||||||
|
}
|
||||||
|
return CancelableCoroutine(job)
|
||||||
|
}
|
||||||
|
|
||||||
override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
|
override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
login: String,
|
login: String,
|
||||||
password: String,
|
password: String,
|
||||||
callback: MatrixCallback<Session>): Cancelable {
|
callback: MatrixCallback<Session>): Cancelable {
|
||||||
|
|
||||||
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password)
|
val sessionOrFailure = runCatching {
|
||||||
|
authenticate(homeServerConnectionConfig, login, password)
|
||||||
|
}
|
||||||
sessionOrFailure.foldToCallback(callback)
|
sessionOrFailure.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
return CancelableCoroutine(job)
|
return CancelableCoroutine(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig) = withContext(coroutineDispatchers.io) {
|
||||||
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
|
executeRequest<LoginFlowResponse> {
|
||||||
|
apiCall = authAPI.getLoginFlows()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
|
private suspend fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
@ -85,16 +104,18 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
|||||||
} else {
|
} else {
|
||||||
PasswordLoginParams.userIdentifier(login, password, "Mobile")
|
PasswordLoginParams.userIdentifier(login, password, "Mobile")
|
||||||
}
|
}
|
||||||
executeRequest<Credentials> {
|
val credentials = executeRequest<Credentials> {
|
||||||
apiCall = authAPI.login(loginParams)
|
apiCall = authAPI.login(loginParams)
|
||||||
}.map {
|
}
|
||||||
val sessionParams = SessionParams(it, homeServerConnectionConfig)
|
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
|
||||||
sessionParamsStore.save(sessionParams)
|
sessionParamsStore.save(sessionParams)
|
||||||
sessionParams
|
sessionManager.getOrCreateSession(sessionParams)
|
||||||
}.map {
|
|
||||||
sessionManager.getOrCreateSession(it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
|
||||||
|
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
|
||||||
|
sessionParamsStore.save(sessionParams)
|
||||||
|
return sessionManager.getOrCreateSession(sessionParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||||
|
@ -27,7 +27,7 @@ internal interface SessionParamsStore {
|
|||||||
|
|
||||||
fun getAll(): List<SessionParams>
|
fun getAll(): List<SessionParams>
|
||||||
|
|
||||||
fun save(sessionParams: SessionParams): Try<SessionParams>
|
fun save(sessionParams: SessionParams): Try<Unit>
|
||||||
|
|
||||||
fun delete(userId: String): Try<Unit>
|
fun delete(userId: String): Try<Unit>
|
||||||
|
|
||||||
|
@ -30,4 +30,12 @@ data class InteractiveAuthenticationFlow(
|
|||||||
|
|
||||||
@Json(name = "stages")
|
@Json(name = "stages")
|
||||||
val stages: List<String>? = null
|
val stages: List<String>? = null
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Possible values for type
|
||||||
|
const val TYPE_LOGIN_SSO = "m.login.sso"
|
||||||
|
const val TYPE_LOGIN_TOKEN = "m.login.token"
|
||||||
|
const val TYPE_LOGIN_PASSWORD = "m.login.password"
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ import com.squareup.moshi.Json
|
|||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class LoginFlowResponse(
|
data class LoginFlowResponse(
|
||||||
@Json(name = "flows")
|
@Json(name = "flows")
|
||||||
val flows: List<InteractiveAuthenticationFlow>
|
val flows: List<InteractiveAuthenticationFlow>
|
||||||
)
|
)
|
@ -62,7 +62,7 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
|||||||
return sessionParams
|
return sessionParams
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun save(sessionParams: SessionParams): Try<SessionParams> {
|
override fun save(sessionParams: SessionParams): Try<Unit> {
|
||||||
return Try {
|
return Try {
|
||||||
val entity = mapper.map(sessionParams)
|
val entity = mapper.map(sessionParams)
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
@ -72,7 +72,6 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
|||||||
}
|
}
|
||||||
realm.close()
|
realm.close()
|
||||||
}
|
}
|
||||||
sessionParams
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ import com.squareup.moshi.Moshi
|
|||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.internal.di.MatrixScope
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
@ -30,12 +29,13 @@ import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
|||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.*
|
import im.vector.matrix.android.internal.crypto.tasks.*
|
||||||
import im.vector.matrix.android.internal.database.configureEncryption
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||||
|
import im.vector.matrix.android.internal.di.UserCacheDirectory
|
||||||
|
import im.vector.matrix.android.internal.di.UserMd5
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
||||||
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
|
||||||
import im.vector.matrix.android.internal.util.md5
|
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -45,17 +45,20 @@ internal abstract class CryptoModule {
|
|||||||
|
|
||||||
@Module
|
@Module
|
||||||
companion object {
|
companion object {
|
||||||
|
internal const val DB_ALIAS_PREFIX = "crypto_module_"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@CryptoDatabase
|
@CryptoDatabase
|
||||||
@SessionScope
|
@SessionScope
|
||||||
fun providesRealmConfiguration(context: Context, credentials: Credentials): RealmConfiguration {
|
fun providesRealmConfiguration(@UserCacheDirectory directory: File,
|
||||||
val userIDHash = credentials.userId.md5()
|
@UserMd5 userMd5: String,
|
||||||
|
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.directory(File(context.filesDir, userIDHash))
|
.directory(directory)
|
||||||
.configureEncryption("crypto_module_$userIDHash", context)
|
.apply {
|
||||||
|
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
|
||||||
|
}
|
||||||
.name("crypto_store.realm")
|
.name("crypto_store.realm")
|
||||||
.modules(RealmCryptoStoreModule())
|
.modules(RealmCryptoStoreModule())
|
||||||
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
|
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
|
||||||
@ -105,7 +108,7 @@ internal abstract class CryptoModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCryptoService(cryptoManager: CryptoManager): CryptoService
|
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask
|
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask
|
||||||
|
@ -21,7 +21,6 @@ package im.vector.matrix.android.internal.crypto
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.TextUtils
|
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
@ -63,27 +62,23 @@ import im.vector.matrix.android.internal.crypto.tasks.*
|
|||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
||||||
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.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
|
||||||
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
|
||||||
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||||
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
||||||
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
|
||||||
import im.vector.matrix.android.internal.task.toConfigurableTask
|
|
||||||
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.fetchCopied
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `CryptoService` class instance manages the end-to-end crypto for a session.
|
* A `CryptoService` class instance manages the end-to-end crypto for a session.
|
||||||
@ -96,7 +91,7 @@ import kotlin.coroutines.EmptyCoroutineContext
|
|||||||
* Specially, it tracks all room membership changes events in order to do keys updates.
|
* Specially, it tracks all room membership changes events in order to do keys updates.
|
||||||
*/
|
*/
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class CryptoManager @Inject constructor(
|
internal class DefaultCryptoService @Inject constructor(
|
||||||
// Olm Manager
|
// Olm Manager
|
||||||
private val olmManager: OlmManager,
|
private val olmManager: OlmManager,
|
||||||
// The credentials,
|
// The credentials,
|
||||||
@ -138,7 +133,6 @@ internal class CryptoManager @Inject constructor(
|
|||||||
private val setDeviceNameTask: SetDeviceNameTask,
|
private val setDeviceNameTask: SetDeviceNameTask,
|
||||||
private val uploadKeysTask: UploadKeysTask,
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
@CryptoDatabase private val clearCryptoDataTask: ClearCacheTask,
|
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor
|
||||||
@ -169,22 +163,25 @@ internal class CryptoManager @Inject constructor(
|
|||||||
|
|
||||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
||||||
setDeviceNameTask
|
setDeviceNameTask
|
||||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName))
|
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
||||||
.dispatchTo(callback)
|
this.callback = callback
|
||||||
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||||
deleteDeviceTask
|
deleteDeviceTask
|
||||||
.configureWith(DeleteDeviceTask.Params(deviceId))
|
.configureWith(DeleteDeviceTask.Params(deviceId)) {
|
||||||
.dispatchTo(callback)
|
this.callback = callback
|
||||||
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
|
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
|
||||||
deleteDeviceWithUserPasswordTask
|
deleteDeviceWithUserPasswordTask
|
||||||
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password))
|
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) {
|
||||||
.dispatchTo(callback)
|
this.callback = callback
|
||||||
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +195,9 @@ internal class CryptoManager @Inject constructor(
|
|||||||
|
|
||||||
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
||||||
getDevicesTask
|
getDevicesTask
|
||||||
.toConfigurableTask()
|
.configureWith {
|
||||||
.dispatchTo(callback)
|
this.callback = callback
|
||||||
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,7 +246,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
isStarting.set(true)
|
isStarting.set(true)
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
internalStart(isInitialSync)
|
internalStart(isInitialSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,16 +254,9 @@ internal class CryptoManager @Inject constructor(
|
|||||||
private suspend fun internalStart(isInitialSync: Boolean) {
|
private suspend fun internalStart(isInitialSync: Boolean) {
|
||||||
// Open the store
|
// Open the store
|
||||||
cryptoStore.open()
|
cryptoStore.open()
|
||||||
|
runCatching {
|
||||||
uploadDeviceKeys()
|
uploadDeviceKeys()
|
||||||
.flatMap { oneTimeKeysUploader.maybeUploadOneTimeKeys() }
|
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||||
.fold(
|
|
||||||
{
|
|
||||||
Timber.e("Start failed: $it")
|
|
||||||
delay(1000)
|
|
||||||
isStarting.set(false)
|
|
||||||
internalStart(isInitialSync)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
outgoingRoomKeyRequestManager.start()
|
outgoingRoomKeyRequestManager.start()
|
||||||
keysBackup.checkAndStartKeysBackup()
|
keysBackup.checkAndStartKeysBackup()
|
||||||
if (isInitialSync) {
|
if (isInitialSync) {
|
||||||
@ -275,8 +266,16 @@ internal class CryptoManager @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
||||||
}
|
}
|
||||||
|
}.fold(
|
||||||
|
{
|
||||||
isStarting.set(false)
|
isStarting.set(false)
|
||||||
isStarted.set(true)
|
isStarted.set(true)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Timber.e("Start failed: $it")
|
||||||
|
delay(1000)
|
||||||
|
isStarting.set(false)
|
||||||
|
internalStart(isInitialSync)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -284,7 +283,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Close the crypto
|
* Close the crypto
|
||||||
*/
|
*/
|
||||||
fun close() {
|
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
||||||
olmDevice.release()
|
olmDevice.release()
|
||||||
cryptoStore.close()
|
cryptoStore.close()
|
||||||
outgoingRoomKeyRequestManager.stop()
|
outgoingRoomKeyRequestManager.stop()
|
||||||
@ -315,7 +314,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param syncResponse the syncResponse
|
* @param syncResponse the syncResponse
|
||||||
*/
|
*/
|
||||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
if (syncResponse.deviceLists != null) {
|
if (syncResponse.deviceLists != null) {
|
||||||
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
||||||
}
|
}
|
||||||
@ -340,7 +339,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
||||||
*/
|
*/
|
||||||
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo? {
|
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo? {
|
||||||
return if (!TextUtils.equals(algorithm, MXCRYPTO_ALGORITHM_MEGOLM) && !TextUtils.equals(algorithm, MXCRYPTO_ALGORITHM_OLM)) {
|
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
|
||||||
// We only deal in olm keys
|
// We only deal in olm keys
|
||||||
null
|
null
|
||||||
} else cryptoStore.deviceWithIdentityKey(senderKey)
|
} else cryptoStore.deviceWithIdentityKey(senderKey)
|
||||||
@ -353,8 +352,8 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param deviceId the device id
|
* @param deviceId the device id
|
||||||
*/
|
*/
|
||||||
override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? {
|
override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? {
|
||||||
return if (!TextUtils.isEmpty(userId) && !TextUtils.isEmpty(deviceId)) {
|
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
||||||
cryptoStore.getUserDevice(deviceId!!, userId)
|
cryptoStore.getUserDevice(deviceId, userId)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -439,7 +438,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
// (for now at least. Maybe we should alert the user somehow?)
|
// (for now at least. Maybe we should alert the user somehow?)
|
||||||
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
|
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(existingAlgorithm) && !TextUtils.equals(existingAlgorithm, algorithm)) {
|
if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) {
|
||||||
Timber.e("## setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
|
Timber.e("## setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -535,7 +534,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
eventType: String,
|
eventType: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
if (!isStarted()) {
|
if (!isStarted()) {
|
||||||
Timber.v("## encryptEventContent() : wait after e2e init")
|
Timber.v("## encryptEventContent() : wait after e2e init")
|
||||||
internalStart(false)
|
internalStart(false)
|
||||||
@ -558,13 +557,16 @@ internal class CryptoManager @Inject constructor(
|
|||||||
if (safeAlgorithm != null) {
|
if (safeAlgorithm != null) {
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
Timber.v("## encryptEventContent() starts")
|
Timber.v("## encryptEventContent() starts")
|
||||||
|
runCatching {
|
||||||
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
||||||
|
}
|
||||||
.fold(
|
.fold(
|
||||||
{ callback.onFailure(it) },
|
|
||||||
{
|
{
|
||||||
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||||
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
|
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
|
||||||
}
|
},
|
||||||
|
{ callback.onFailure(it) }
|
||||||
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val algorithm = getEncryptionAlgorithm(roomId)
|
val algorithm = getEncryptionAlgorithm(roomId)
|
||||||
@ -586,10 +588,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
internalDecryptEvent(event, timeline).fold(
|
internalDecryptEvent(event, timeline)
|
||||||
{ throw it },
|
|
||||||
{ it }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,10 +600,12 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param callback the callback to return data or null
|
* @param callback the callback to return data or null
|
||||||
*/
|
*/
|
||||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
||||||
GlobalScope.launch(EmptyCoroutineContext) {
|
GlobalScope.launch {
|
||||||
val result = withContext(coroutineDispatchers.crypto) {
|
val result = runCatching {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
internalDecryptEvent(event, timeline)
|
internalDecryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
result.foldToCallback(callback)
|
result.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -614,22 +615,22 @@ internal class CryptoManager @Inject constructor(
|
|||||||
*
|
*
|
||||||
* @param event the raw event.
|
* @param event the raw event.
|
||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
* @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try]
|
* @return the MXEventDecryptionResult data, or null in case of error
|
||||||
*/
|
*/
|
||||||
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
val eventContent = event.content
|
val eventContent = event.content
|
||||||
return if (eventContent == null) {
|
if (eventContent == null) {
|
||||||
Timber.e("## decryptEvent : empty event content")
|
Timber.e("## decryptEvent : empty event content")
|
||||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
||||||
} else {
|
} else {
|
||||||
val algorithm = eventContent["algorithm"]?.toString()
|
val algorithm = eventContent["algorithm"]?.toString()
|
||||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
||||||
if (alg == null) {
|
if (alg == null) {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
||||||
Timber.e("## decryptEvent() : $reason")
|
Timber.e("## decryptEvent() : $reason")
|
||||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
||||||
} else {
|
} else {
|
||||||
alg.decryptEvent(event, timeline)
|
return alg.decryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -649,7 +650,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param event the event
|
* @param event the event
|
||||||
*/
|
*/
|
||||||
fun onToDeviceEvent(event: Event) {
|
fun onToDeviceEvent(event: Event) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
||||||
onRoomKeyEvent(event)
|
onRoomKeyEvent(event)
|
||||||
@ -671,7 +672,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
private fun onRoomKeyEvent(event: Event) {
|
private fun onRoomKeyEvent(event: Event) {
|
||||||
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
||||||
if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.algorithm)) {
|
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
|
||||||
Timber.e("## onRoomKeyEvent() : missing fields")
|
Timber.e("## onRoomKeyEvent() : missing fields")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -689,13 +690,14 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param event the encryption event.
|
* @param event the encryption event.
|
||||||
*/
|
*/
|
||||||
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
val params = LoadRoomMembersTask.Params(roomId)
|
val params = LoadRoomMembersTask.Params(roomId)
|
||||||
loadRoomMembersTask
|
try {
|
||||||
.execute(params)
|
loadRoomMembersTask.execute(params)
|
||||||
.map { _ ->
|
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.e(throwable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -738,7 +740,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
val membership = roomMember?.membership
|
val membership = roomMember?.membership
|
||||||
if (membership == Membership.JOIN) {
|
if (membership == Membership.JOIN) {
|
||||||
// make sure we are tracking the deviceList for this user.
|
// make sure we are tracking the deviceList for this user.
|
||||||
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
|
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||||
} else if (membership == Membership.INVITE
|
} else if (membership == Membership.INVITE
|
||||||
&& shouldEncryptForInvitedMembers(roomId)
|
&& shouldEncryptForInvitedMembers(roomId)
|
||||||
&& cryptoConfig.enableEncryptionForInvitedMembers) {
|
&& cryptoConfig.enableEncryptionForInvitedMembers) {
|
||||||
@ -747,7 +749,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
// 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.
|
||||||
// They therefore will not send device updates if a user logs in whilst
|
// They therefore will not send device updates if a user logs in whilst
|
||||||
// their state is invite.
|
// their state is invite.
|
||||||
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
|
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -763,7 +765,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Upload my user's device keys.
|
* Upload my user's device keys.
|
||||||
*/
|
*/
|
||||||
private suspend fun uploadDeviceKeys(): Try<KeysUploadResponse> {
|
private suspend fun uploadDeviceKeys(): KeysUploadResponse {
|
||||||
// Prepare the device keys data to send
|
// Prepare the device keys data to send
|
||||||
// Sign it
|
// Sign it
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
||||||
@ -782,7 +784,11 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param callback the exported keys
|
* @param callback the exported keys
|
||||||
*/
|
*/
|
||||||
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
|
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
|
||||||
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT, callback)
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
runCatching {
|
||||||
|
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
|
||||||
|
}.fold(callback::onSuccess, callback::onFailure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -792,31 +798,17 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
||||||
* @param callback the exported keys
|
* @param callback the exported keys
|
||||||
*/
|
*/
|
||||||
private fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback<ByteArray>) {
|
private suspend fun exportRoomKeys(password: String, anIterationCount: Int): ByteArray {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
return withContext(coroutineDispatchers.crypto) {
|
||||||
withContext(coroutineDispatchers.crypto) {
|
val iterationCount = max(0, anIterationCount)
|
||||||
Try {
|
|
||||||
val iterationCount = Math.max(0, anIterationCount)
|
|
||||||
|
|
||||||
val exportedSessions = ArrayList<MegolmSessionData>()
|
val exportedSessions = cryptoStore.getInboundGroupSessions().mapNotNull { it.exportKeys() }
|
||||||
|
|
||||||
val inboundGroupSessions = cryptoStore.getInboundGroupSessions()
|
|
||||||
|
|
||||||
for (session in inboundGroupSessions) {
|
|
||||||
val megolmSessionData = session.exportKeys()
|
|
||||||
|
|
||||||
if (null != megolmSessionData) {
|
|
||||||
exportedSessions.add(megolmSessionData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val adapter = MoshiProvider.providesMoshi()
|
val adapter = MoshiProvider.providesMoshi()
|
||||||
.adapter(List::class.java)
|
.adapter(List::class.java)
|
||||||
|
|
||||||
MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
||||||
}
|
}
|
||||||
}.foldToCallback(callback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -879,11 +871,9 @@ internal class CryptoManager @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
|
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
|
||||||
// force the refresh to ensure that the devices list is up-to-date
|
// force the refresh to ensure that the devices list is up-to-date
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
deviceListManager
|
runCatching { deviceListManager.downloadKeys(userIds, true) }
|
||||||
.downloadKeys(userIds, true)
|
|
||||||
.fold(
|
.fold(
|
||||||
{ callback.onFailure(it) },
|
|
||||||
{
|
{
|
||||||
val unknownDevices = getUnknownDevices(it)
|
val unknownDevices = getUnknownDevices(it)
|
||||||
if (unknownDevices.map.isEmpty()) {
|
if (unknownDevices.map.isEmpty()) {
|
||||||
@ -892,7 +882,8 @@ internal class CryptoManager @Inject constructor(
|
|||||||
// trigger an an unknown devices exception
|
// trigger an an unknown devices exception
|
||||||
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
|
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{ callback.onFailure(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -944,7 +935,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList()
|
val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList()
|
||||||
|
|
||||||
if (add) {
|
if (add) {
|
||||||
if (!roomIds.contains(roomId)) {
|
if (roomId !in roomIds) {
|
||||||
roomIds.add(roomId)
|
roomIds.add(roomId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1033,8 +1024,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
||||||
val userIds = devicesInRoom.userIds
|
val userIds = devicesInRoom.userIds
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
|
devicesInRoom.getUserDeviceIds(userId)?.forEach { deviceId ->
|
||||||
deviceIds?.forEach { deviceId ->
|
|
||||||
devicesInRoom.getObject(userId, deviceId)
|
devicesInRoom.getObject(userId, deviceId)
|
||||||
?.takeIf { it.isUnknown }
|
?.takeIf { it.isUnknown }
|
||||||
?.let {
|
?.let {
|
||||||
@ -1047,20 +1037,13 @@ internal class CryptoManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
deviceListManager
|
runCatching {
|
||||||
.downloadKeys(userIds, forceDownload)
|
deviceListManager.downloadKeys(userIds, forceDownload)
|
||||||
.foldToCallback(callback)
|
}.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
|
|
||||||
clearCryptoDataTask
|
|
||||||
.toConfigurableTask()
|
|
||||||
.dispatchTo(callback)
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addNewSessionListener(newSessionListener: NewSessionListener) {
|
override fun addNewSessionListener(newSessionListener: NewSessionListener) {
|
||||||
roomDecryptorProvider.addNewSessionListener(newSessionListener)
|
roomDecryptorProvider.addNewSessionListener(newSessionListener)
|
||||||
}
|
}
|
||||||
@ -1073,6 +1056,6 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"
|
return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,14 +18,12 @@
|
|||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
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.extensions.onError
|
|
||||||
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 timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -237,7 +235,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||||||
* @param forceDownload Always download the keys even if cached.
|
* @param forceDownload Always download the keys even if cached.
|
||||||
* @param callback the asynchronous callback
|
* @param callback the asynchronous callback
|
||||||
*/
|
*/
|
||||||
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): Try<MXUsersDevicesMap<MXDeviceInfo>> {
|
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<MXDeviceInfo> {
|
||||||
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
|
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
|
||||||
// Map from userId -> deviceId -> MXDeviceInfo
|
// Map from userId -> deviceId -> MXDeviceInfo
|
||||||
val stored = MXUsersDevicesMap<MXDeviceInfo>()
|
val stored = MXUsersDevicesMap<MXDeviceInfo>()
|
||||||
@ -268,15 +266,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||||||
}
|
}
|
||||||
return if (downloadUsers.isEmpty()) {
|
return if (downloadUsers.isEmpty()) {
|
||||||
Timber.v("## downloadKeys() : no new user device")
|
Timber.v("## downloadKeys() : no new user device")
|
||||||
Try.just(stored)
|
stored
|
||||||
} else {
|
} else {
|
||||||
Timber.v("## downloadKeys() : starts")
|
Timber.v("## downloadKeys() : starts")
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
doKeyDownloadForUsers(downloadUsers)
|
val result = doKeyDownloadForUsers(downloadUsers)
|
||||||
.map {
|
|
||||||
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
|
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
|
||||||
|
result.also {
|
||||||
it.addEntriesFromMap(stored)
|
it.addEntriesFromMap(stored)
|
||||||
it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,17 +283,22 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||||||
*
|
*
|
||||||
* @param downloadUsers the user ids list
|
* @param downloadUsers the user ids list
|
||||||
*/
|
*/
|
||||||
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> {
|
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<MXDeviceInfo> {
|
||||||
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
|
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
|
||||||
// get the user ids which did not already trigger a keys download
|
// get the user ids which did not already trigger a keys download
|
||||||
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
|
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
|
||||||
if (filteredUsers.isEmpty()) {
|
if (filteredUsers.isEmpty()) {
|
||||||
// trigger nothing
|
// trigger nothing
|
||||||
return Try.just(MXUsersDevicesMap())
|
return MXUsersDevicesMap()
|
||||||
}
|
}
|
||||||
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
|
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
|
||||||
return downloadKeysForUsersTask.execute(params)
|
val response = try {
|
||||||
.map { response ->
|
downloadKeysForUsersTask.execute(params)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.e(throwable, "##doKeyDownloadForUsers(): error")
|
||||||
|
onKeysDownloadFailed(filteredUsers)
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
|
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
|
||||||
for (userId in filteredUsers) {
|
for (userId in filteredUsers) {
|
||||||
val devices = response.deviceKeys?.get(userId)
|
val devices = response.deviceKeys?.get(userId)
|
||||||
@ -334,12 +336,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||||||
cryptoStore.storeUserDevices(userId, mutableDevices)
|
cryptoStore.storeUserDevices(userId, mutableDevices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onKeysDownloadSucceed(filteredUsers, response.failures)
|
return onKeysDownloadSucceed(filteredUsers, response.failures)
|
||||||
}
|
|
||||||
.onError {
|
|
||||||
Timber.e(it, "##doKeyDownloadForUsers(): error")
|
|
||||||
onKeysDownloadFailed(filteredUsers)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -465,13 +462,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||||||
}
|
}
|
||||||
|
|
||||||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||||
|
runCatching {
|
||||||
doKeyDownloadForUsers(users)
|
doKeyDownloadForUsers(users)
|
||||||
.fold(
|
}.fold(
|
||||||
{
|
|
||||||
Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Timber.v("## refreshOutdatedDeviceLists() : done")
|
Timber.v("## refreshOutdatedDeviceLists() : done")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
@ -80,8 +79,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
//
|
//
|
||||||
// The first level keys are timeline ids.
|
// The first level keys are timeline ids.
|
||||||
// The second level keys are strings of form "<senderKey>|<session_id>|<message_index>"
|
// The second level keys are strings of form "<senderKey>|<session_id>|<message_index>"
|
||||||
// Values are true.
|
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableSet<String>> = HashMap()
|
||||||
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Retrieve the account from the store
|
// Retrieve the account from the store
|
||||||
@ -506,11 +504,8 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
keysClaimed: Map<String, String>,
|
keysClaimed: Map<String, String>,
|
||||||
exportFormat: Boolean): Boolean {
|
exportFormat: Boolean): Boolean {
|
||||||
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
|
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
|
||||||
|
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
|
||||||
getInboundGroupSession(sessionId, senderKey, roomId).fold(
|
.fold(
|
||||||
{
|
|
||||||
// Nothing to do in case of error
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// If we already have this session, consider updating it
|
// If we already have this session, consider updating it
|
||||||
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||||
@ -523,6 +518,9 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
session.olmInboundGroupSession?.releaseSession()
|
session.olmInboundGroupSession?.releaseSession()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nothing to do in case of error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -595,12 +593,8 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
getInboundGroupSession(sessionId, senderKey, roomId)
|
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
|
||||||
.fold(
|
.fold(
|
||||||
{
|
|
||||||
// Session does not already exist, add it
|
|
||||||
sessions.add(session)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// If we already have this session, consider updating it
|
// If we already have this session, consider updating it
|
||||||
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||||
@ -613,7 +607,12 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
sessions.add(session)
|
sessions.add(session)
|
||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Session does not already exist, add it
|
||||||
|
sessions.add(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,9 +647,8 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
roomId: String,
|
roomId: String,
|
||||||
timeline: String?,
|
timeline: String?,
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
senderKey: String): Try<OlmDecryptionResult> {
|
senderKey: String): OlmDecryptionResult {
|
||||||
return getInboundGroupSession(sessionId, senderKey, roomId)
|
val session = getInboundGroupSession(sessionId, senderKey, roomId)
|
||||||
.flatMap { session ->
|
|
||||||
// Check that the room id matches the original one for the session. This stops
|
// Check that the room id matches the original one for the session. This stops
|
||||||
// the HS pretending a message was targeting a different room.
|
// the HS pretending a message was targeting a different room.
|
||||||
if (roomId == session.roomId) {
|
if (roomId == session.roomId) {
|
||||||
@ -659,23 +657,21 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
|
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
|
||||||
} catch (e: OlmException) {
|
} catch (e: OlmException) {
|
||||||
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
|
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
|
||||||
return@flatMap Try.Failure(MXCryptoError.OlmError(e))
|
throw MXCryptoError.OlmError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != timeline) {
|
if (null != timeline) {
|
||||||
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
|
val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() }
|
||||||
inboundGroupSessionMessageIndexes[timeline] = HashMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
||||||
|
|
||||||
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
|
if (timelineSet.contains(messageIndexKey)) {
|
||||||
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
|
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
|
||||||
Timber.e("## decryptGroupMessage() : $reason")
|
Timber.e("## decryptGroupMessage() : $reason")
|
||||||
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
|
timelineSet.add(messageIndexKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.storeInboundGroupSessions(listOf(session))
|
store.storeInboundGroupSessions(listOf(session))
|
||||||
@ -685,23 +681,20 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
adapter.fromJson(payloadString)
|
adapter.fromJson(payloadString)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e("## decryptGroupMessage() : fails to parse the payload")
|
Timber.e("## decryptGroupMessage() : fails to parse the payload")
|
||||||
return@flatMap Try.Failure(
|
throw
|
||||||
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
|
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
|
||||||
}
|
}
|
||||||
|
|
||||||
return@flatMap Try.just(
|
return OlmDecryptionResult(
|
||||||
OlmDecryptionResult(
|
|
||||||
payload,
|
payload,
|
||||||
session.keysClaimed,
|
session.keysClaimed,
|
||||||
senderKey,
|
senderKey,
|
||||||
session.forwardingCurve25519KeyChain
|
session.forwardingCurve25519KeyChain
|
||||||
)
|
)
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
||||||
Timber.e("## decryptGroupMessage() : $reason")
|
Timber.e("## decryptGroupMessage() : $reason")
|
||||||
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -766,26 +759,26 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||||
* @return the inbound group session.
|
* @return the inbound group session.
|
||||||
*/
|
*/
|
||||||
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> {
|
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper {
|
||||||
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
|
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
|
||||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
|
||||||
}
|
}
|
||||||
|
|
||||||
val session = store.getInboundGroupSession(sessionId, senderKey)
|
val session = store.getInboundGroupSession(sessionId, senderKey)
|
||||||
|
|
||||||
return if (null != session) {
|
if (session != null) {
|
||||||
// Check that the room id matches the original one for the session. This stops
|
// Check that the room id matches the original one for the session. This stops
|
||||||
// the HS pretending a message was targeting a different room.
|
// the HS pretending a message was targeting a different room.
|
||||||
if (!TextUtils.equals(roomId, session.roomId)) {
|
if (!TextUtils.equals(roomId, session.roomId)) {
|
||||||
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
||||||
Timber.e("## getInboundGroupSession() : $errorDescription")
|
Timber.e("## getInboundGroupSession() : $errorDescription")
|
||||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)
|
||||||
} else {
|
} else {
|
||||||
Try.just(session)
|
return session
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
|
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
|
||||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -798,6 +791,6 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
* @return true if the unbound session keys are known.
|
* @return true if the unbound session keys are known.
|
||||||
*/
|
*/
|
||||||
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
|
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
|
||||||
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess()
|
return runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }.isSuccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class ObjectSigner @Inject constructor(private val credentials: Credentials,
|
internal class ObjectSigner @Inject constructor(private val credentials: Credentials,
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import arrow.core.Try
|
|
||||||
import arrow.instances.`try`.applicativeError.handleError
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
import im.vector.matrix.android.internal.crypto.model.MXKey
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||||
@ -59,13 +57,13 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Check if the OTK must be uploaded.
|
* Check if the OTK must be uploaded.
|
||||||
*/
|
*/
|
||||||
suspend fun maybeUploadOneTimeKeys(): Try<Unit> {
|
suspend fun maybeUploadOneTimeKeys() {
|
||||||
if (oneTimeKeyCheckInProgress) {
|
if (oneTimeKeyCheckInProgress) {
|
||||||
return Try.just(Unit)
|
return
|
||||||
}
|
}
|
||||||
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
|
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
|
||||||
// we've done a key upload recently.
|
// we've done a key upload recently.
|
||||||
return Try.just(Unit)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lastOneTimeKeyCheck = System.currentTimeMillis()
|
lastOneTimeKeyCheck = System.currentTimeMillis()
|
||||||
@ -81,13 +79,12 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
// discard the oldest private keys first. This will eventually clean
|
// discard the oldest private keys first. This will eventually clean
|
||||||
// out stale private keys that won't receive a message.
|
// out stale private keys that won't receive a message.
|
||||||
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
|
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
|
||||||
val result = if (oneTimeKeyCount != null) {
|
if (oneTimeKeyCount != null) {
|
||||||
uploadOTK(oneTimeKeyCount!!, keyLimit)
|
uploadOTK(oneTimeKeyCount!!, keyLimit)
|
||||||
} else {
|
} else {
|
||||||
// ask the server how many keys we have
|
// ask the server how many keys we have
|
||||||
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
|
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
|
||||||
uploadKeysTask.execute(uploadKeysParams)
|
val response = uploadKeysTask.execute(uploadKeysParams)
|
||||||
.flatMap {
|
|
||||||
// We need to keep a pool of one time public keys on the server so that
|
// We need to keep a pool of one time public keys on the server so that
|
||||||
// other devices can start conversations with us. But we can only store
|
// other devices can start conversations with us. But we can only store
|
||||||
// a finite number of private keys in the olm Account object.
|
// a finite number of private keys in the olm Account object.
|
||||||
@ -101,22 +98,13 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
// these factors.
|
// these factors.
|
||||||
// TODO Why we do not set oneTimeKeyCount here?
|
// TODO Why we do not set oneTimeKeyCount here?
|
||||||
// TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also)
|
// TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also)
|
||||||
val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
val keyCount = response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
||||||
uploadOTK(keyCount, keyLimit)
|
uploadOTK(keyCount, keyLimit)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return result
|
|
||||||
.map {
|
|
||||||
Timber.v("## uploadKeys() : success")
|
Timber.v("## uploadKeys() : success")
|
||||||
oneTimeKeyCount = null
|
oneTimeKeyCount = null
|
||||||
oneTimeKeyCheckInProgress = false
|
oneTimeKeyCheckInProgress = false
|
||||||
}
|
}
|
||||||
.handleError {
|
|
||||||
Timber.e(it, "## uploadKeys() : failed")
|
|
||||||
oneTimeKeyCount = null
|
|
||||||
oneTimeKeyCheckInProgress = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload some the OTKs.
|
* Upload some the OTKs.
|
||||||
@ -124,29 +112,26 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
* @param keyCount the key count
|
* @param keyCount the key count
|
||||||
* @param keyLimit the limit
|
* @param keyLimit the limit
|
||||||
*/
|
*/
|
||||||
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Try<Unit> {
|
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int) {
|
||||||
if (keyLimit <= keyCount) {
|
if (keyLimit <= keyCount) {
|
||||||
// If we don't need to generate any more keys then we are done.
|
// If we don't need to generate any more keys then we are done.
|
||||||
return Try.just(Unit)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
|
val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
|
||||||
olmDevice.generateOneTimeKeys(keysThisLoop)
|
olmDevice.generateOneTimeKeys(keysThisLoop)
|
||||||
return uploadOneTimeKeys()
|
val response = uploadOneTimeKeys()
|
||||||
.flatMap {
|
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||||
if (it.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
|
uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
|
||||||
uploadOTK(it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
|
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
|
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
|
||||||
Try.raise(Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519"))
|
throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload my user's one time keys.
|
* Upload my user's one time keys.
|
||||||
*/
|
*/
|
||||||
private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> {
|
private suspend fun uploadOneTimeKeys(): KeysUploadResponse {
|
||||||
val oneTimeKeys = olmDevice.getOneTimeKeys()
|
val oneTimeKeys = olmDevice.getOneTimeKeys()
|
||||||
val oneTimeJson = HashMap<String, Any>()
|
val oneTimeJson = HashMap<String, Any>()
|
||||||
|
|
||||||
@ -169,13 +154,10 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
// For now, we set the device id explicitly, as we may not be using the
|
// For now, we set the device id explicitly, as we may not be using the
|
||||||
// same one as used in login.
|
// same one as used in login.
|
||||||
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
|
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
|
||||||
return uploadKeysTask
|
val response = uploadKeysTask.execute(uploadParams)
|
||||||
.execute(uploadParams)
|
|
||||||
.map {
|
|
||||||
lastPublishedOneTimeKeys = oneTimeKeys
|
lastPublishedOneTimeKeys = oneTimeKeys
|
||||||
olmDevice.markKeysAsPublished()
|
olmDevice.markKeysAsPublished()
|
||||||
it
|
return response
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -299,10 +299,12 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||||||
// TODO Change this two hard coded key to something better
|
// TODO Change this two hard coded key to something better
|
||||||
contentMap.setObject(recipient["userId"], recipient["deviceId"], message)
|
contentMap.setObject(recipient["userId"], recipient["deviceId"], message)
|
||||||
}
|
}
|
||||||
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId))
|
sendToDeviceTask
|
||||||
.dispatchTo(callback)
|
.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId)) {
|
||||||
.executeOn(TaskThread.CALLER)
|
this.callback = callback
|
||||||
.callbackOn(TaskThread.CALLER)
|
this.callbackThread = TaskThread.CALLER
|
||||||
|
this.executionThread = TaskThread.CALLER
|
||||||
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.matrix.android.internal.crypto.actions
|
package im.vector.matrix.android.internal.crypto.actions
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
import im.vector.matrix.android.internal.crypto.model.MXKey
|
||||||
@ -32,7 +31,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
|||||||
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
|
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
|
||||||
|
|
||||||
|
|
||||||
suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): Try<MXUsersDevicesMap<MXOlmSessionResult>> {
|
suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
|
||||||
val devicesWithoutSession = ArrayList<MXDeviceInfo>()
|
val devicesWithoutSession = ArrayList<MXDeviceInfo>()
|
||||||
|
|
||||||
val results = MXUsersDevicesMap<MXOlmSessionResult>()
|
val results = MXUsersDevicesMap<MXOlmSessionResult>()
|
||||||
@ -58,7 +57,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (devicesWithoutSession.size == 0) {
|
if (devicesWithoutSession.size == 0) {
|
||||||
return Try.just(results)
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the request for claiming one-time keys
|
// Prepare the request for claiming one-time keys
|
||||||
@ -79,15 +78,13 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
|||||||
Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
|
Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
|
||||||
|
|
||||||
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
|
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
|
||||||
return oneTimeKeysForUsersDeviceTask
|
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams)
|
||||||
.execute(claimParams)
|
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
|
||||||
.map {
|
|
||||||
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $it")
|
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
val deviceInfos = devicesByUser[userId]
|
val deviceInfos = devicesByUser[userId]
|
||||||
for (deviceInfo in deviceInfos!!) {
|
for (deviceInfo in deviceInfos!!) {
|
||||||
var oneTimeKey: MXKey? = null
|
var oneTimeKey: MXKey? = null
|
||||||
val deviceIds = it.getUserDeviceIds(userId)
|
val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
|
||||||
if (null != deviceIds) {
|
if (null != deviceIds) {
|
||||||
for (deviceId in deviceIds) {
|
for (deviceId in deviceIds) {
|
||||||
val olmSessionResult = results.getObject(userId, deviceId)
|
val olmSessionResult = results.getObject(userId, deviceId)
|
||||||
@ -95,7 +92,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
|||||||
// We already have a result for this device
|
// We already have a result for this device
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val key = it.getObject(userId, deviceId)
|
val key = oneTimeKeys.getObject(userId, deviceId)
|
||||||
if (key?.type == oneTimeKeyAlgorithm) {
|
if (key?.type == oneTimeKeyAlgorithm) {
|
||||||
oneTimeKey = key
|
oneTimeKey = key
|
||||||
}
|
}
|
||||||
@ -110,8 +107,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results
|
return results
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {
|
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {
|
||||||
|
@ -17,14 +17,11 @@
|
|||||||
package im.vector.matrix.android.internal.crypto.actions
|
package im.vector.matrix.android.internal.crypto.actions
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
|
||||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
|
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -37,7 +34,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
|
|||||||
* Try to make sure we have established olm sessions for the given users.
|
* Try to make sure we have established olm sessions for the given users.
|
||||||
* @param users a list of user ids.
|
* @param users a list of user ids.
|
||||||
*/
|
*/
|
||||||
suspend fun handle(users: List<String>) : Try<MXUsersDevicesMap<MXOlmSessionResult>> {
|
suspend fun handle(users: List<String>): MXUsersDevicesMap<MXOlmSessionResult> {
|
||||||
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
|
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
|
||||||
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()
|
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import im.vector.matrix.android.internal.crypto.RoomDecryptorProvider
|
|||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -16,15 +16,14 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.actions
|
package im.vector.matrix.android.internal.crypto.actions
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore,
|
internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore,
|
||||||
private val credentials: Credentials,
|
@UserId private val userId: String,
|
||||||
private val keysBackup: KeysBackup) {
|
private val keysBackup: KeysBackup) {
|
||||||
|
|
||||||
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
|
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
|
||||||
@ -40,7 +39,7 @@ internal class SetDeviceVerificationAction @Inject constructor(private val crypt
|
|||||||
device.verified = verificationStatus
|
device.verified = verificationStatus
|
||||||
cryptoStore.storeUserDevice(userId, device)
|
cryptoStore.storeUserDevice(userId, device)
|
||||||
|
|
||||||
if (userId == credentials.userId) {
|
if (userId == this.userId) {
|
||||||
// If one of the user's own devices is being marked as verified / unverified,
|
// If one of the user's own devices is being marked as verified / unverified,
|
||||||
// check the key backup status, since whether or not we use this depends on
|
// check the key backup status, since whether or not we use this depends on
|
||||||
// whether it has a signature from a verified device
|
// whether it has a signature from a verified device
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.algorithms
|
package im.vector.matrix.android.internal.crypto.algorithms
|
||||||
|
|
||||||
import arrow.core.Try
|
|
||||||
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.crypto.IncomingRoomKeyRequest
|
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
@ -35,7 +34,7 @@ internal interface IMXDecrypting {
|
|||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
* @return the decryption information, or an error
|
* @return the decryption information, or an error
|
||||||
*/
|
*/
|
||||||
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult>
|
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a key event.
|
* Handle a key event.
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.algorithms
|
package im.vector.matrix.android.internal.crypto.algorithms
|
||||||
|
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,7 +30,7 @@ internal interface IMXEncrypting {
|
|||||||
* @param eventContent the content of the event.
|
* @param eventContent the content of the event.
|
||||||
* @param eventType the type of the event.
|
* @param eventType the type of the event.
|
||||||
* @param userIds the room members the event will be sent to.
|
* @param userIds the room members the event will be sent to.
|
||||||
* @return the encrypted content wrapped by [Try]
|
* @return the encrypted content
|
||||||
*/
|
*/
|
||||||
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content>
|
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,6 @@
|
|||||||
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
|
||||||
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.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
@ -29,7 +27,6 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
|
|||||||
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
|
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
||||||
@ -38,13 +35,11 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
|||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
|
|
||||||
internal class MXMegolmDecryption(private val credentials: Credentials,
|
internal class MXMegolmDecryption(private val userId: String,
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
|
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
|
||||||
@ -63,30 +58,46 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
*/
|
*/
|
||||||
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
||||||
|
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
return decryptEvent(event, timeline, true)
|
return decryptEvent(event, timeline, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> {
|
private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
|
||||||
if (event.roomId.isNullOrBlank()) {
|
if (event.roomId.isNullOrBlank()) {
|
||||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
}
|
}
|
||||||
|
|
||||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||||
?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
|
||||||
if (encryptedEventContent.senderKey.isNullOrBlank()
|
if (encryptedEventContent.senderKey.isNullOrBlank()
|
||||||
|| encryptedEventContent.sessionId.isNullOrBlank()
|
|| encryptedEventContent.sessionId.isNullOrBlank()
|
||||||
|| encryptedEventContent.ciphertext.isNullOrBlank()) {
|
|| encryptedEventContent.ciphertext.isNullOrBlank()) {
|
||||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
}
|
}
|
||||||
|
|
||||||
return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
|
return runCatching {
|
||||||
|
olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
|
||||||
event.roomId,
|
event.roomId,
|
||||||
timeline,
|
timeline,
|
||||||
encryptedEventContent.sessionId,
|
encryptedEventContent.sessionId,
|
||||||
encryptedEventContent.senderKey)
|
encryptedEventContent.senderKey)
|
||||||
|
}
|
||||||
.fold(
|
.fold(
|
||||||
|
{ olmDecryptionResult ->
|
||||||
|
// the decryption succeeds
|
||||||
|
if (olmDecryptionResult.payload != null) {
|
||||||
|
MXEventDecryptionResult(
|
||||||
|
clearEvent = olmDecryptionResult.payload,
|
||||||
|
senderCurve25519Key = olmDecryptionResult.senderKey,
|
||||||
|
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
||||||
|
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
|
||||||
|
?: emptyList()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
}
|
||||||
|
},
|
||||||
{ throwable ->
|
{ throwable ->
|
||||||
if (throwable is MXCryptoError.OlmError) {
|
if (throwable is MXCryptoError.OlmError) {
|
||||||
// TODO Check the value of .message
|
// TODO Check the value of .message
|
||||||
@ -100,10 +111,10 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
||||||
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
|
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
|
||||||
|
|
||||||
Try.Failure(MXCryptoError.Base(
|
throw MXCryptoError.Base(
|
||||||
MXCryptoError.ErrorType.OLM,
|
MXCryptoError.ErrorType.OLM,
|
||||||
reason,
|
reason,
|
||||||
detailedReason))
|
detailedReason)
|
||||||
}
|
}
|
||||||
if (throwable is MXCryptoError.Base) {
|
if (throwable is MXCryptoError.Base) {
|
||||||
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||||
@ -113,23 +124,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw throwable
|
||||||
Try.Failure(throwable)
|
|
||||||
},
|
|
||||||
{ olmDecryptionResult ->
|
|
||||||
// the decryption succeeds
|
|
||||||
if (olmDecryptionResult.payload != null) {
|
|
||||||
Try.just(
|
|
||||||
MXEventDecryptionResult(
|
|
||||||
clearEvent = olmDecryptionResult.payload,
|
|
||||||
senderCurve25519Key = olmDecryptionResult.senderKey,
|
|
||||||
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
|
||||||
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain ?: emptyList()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -150,11 +145,11 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
|
|
||||||
val selfMap = HashMap<String, String>()
|
val selfMap = HashMap<String, String>()
|
||||||
// TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
|
// TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
|
||||||
selfMap["userId"] = credentials.userId
|
selfMap["userId"] = userId
|
||||||
selfMap["deviceId"] = "*"
|
selfMap["deviceId"] = "*"
|
||||||
recipients.add(selfMap)
|
recipients.add(selfMap)
|
||||||
|
|
||||||
if (!TextUtils.equals(sender, credentials.userId)) {
|
if (!TextUtils.equals(sender, userId)) {
|
||||||
val senderMap = HashMap<String, String>()
|
val senderMap = HashMap<String, String>()
|
||||||
senderMap["userId"] = sender
|
senderMap["userId"] = sender
|
||||||
senderMap["deviceId"] = encryptedEventContent.deviceId!!
|
senderMap["deviceId"] = encryptedEventContent.deviceId!!
|
||||||
@ -312,45 +307,41 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
val userId = request.userId ?: return
|
val userId = request.userId ?: return
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
deviceListManager
|
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
||||||
.downloadKeys(listOf(userId), false)
|
.mapCatching {
|
||||||
.flatMap {
|
|
||||||
val deviceId = request.deviceId
|
val deviceId = request.deviceId
|
||||||
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
|
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
|
||||||
if (deviceInfo == null) {
|
if (deviceInfo == null) {
|
||||||
throw RuntimeException()
|
throw RuntimeException()
|
||||||
} else {
|
} else {
|
||||||
val devicesByUser = HashMap<String, List<MXDeviceInfo>>()
|
val devicesByUser = mapOf(userId to listOf(deviceInfo))
|
||||||
devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo))
|
val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||||
ensureOlmSessionsForDevicesAction
|
|
||||||
.handle(devicesByUser)
|
|
||||||
.flatMap {
|
|
||||||
val body = request.requestBody
|
val body = request.requestBody
|
||||||
val olmSessionResult = it.getObject(userId, deviceId)
|
val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
|
||||||
if (olmSessionResult?.sessionId == null) {
|
if (olmSessionResult?.sessionId == null) {
|
||||||
// no session with this device, probably because there
|
// no session with this device, probably because there
|
||||||
// were no one-time keys.
|
// were no one-time keys.
|
||||||
Try.just(Unit)
|
return@mapCatching
|
||||||
}
|
}
|
||||||
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
|
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
|
||||||
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
|
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
|
||||||
|
|
||||||
val payloadJson = HashMap<String, Any>()
|
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
|
||||||
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
|
runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) }
|
||||||
|
|
||||||
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
|
|
||||||
.fold(
|
.fold(
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
|
payloadJson["content"] = it.exportKeys()
|
||||||
|
?: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
payloadJson["content"] = it.exportKeys() ?: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
|
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||||
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
||||||
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
|
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
|
||||||
@ -360,6 +351,5 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
|
||||||
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
|
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
|
||||||
@ -24,11 +23,11 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
|
|||||||
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class MXMegolmDecryptionFactory @Inject constructor(private val credentials: Credentials,
|
internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val userId: String,
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
|
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
|
||||||
@ -40,7 +39,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(private val credent
|
|||||||
|
|
||||||
fun create(): MXMegolmDecryption {
|
fun create(): MXMegolmDecryption {
|
||||||
return MXMegolmDecryption(
|
return MXMegolmDecryption(
|
||||||
credentials,
|
userId,
|
||||||
olmDevice,
|
olmDevice,
|
||||||
deviceListManager,
|
deviceListManager,
|
||||||
outgoingRoomKeyRequestManager,
|
outgoingRoomKeyRequestManager,
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
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.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
@ -69,12 +68,10 @@ internal class MXMegolmEncryption(
|
|||||||
|
|
||||||
override suspend fun encryptEventContent(eventContent: Content,
|
override suspend fun encryptEventContent(eventContent: Content,
|
||||||
eventType: String,
|
eventType: String,
|
||||||
userIds: List<String>): Try<Content> {
|
userIds: List<String>): Content {
|
||||||
return getDevicesInRoom(userIds)
|
val devices = getDevicesInRoom(userIds)
|
||||||
.flatMap { ensureOutboundSession(it) }
|
val outboundSession = ensureOutboundSession(devices)
|
||||||
.flatMap {
|
return encryptContent(outboundSession, eventType, eventContent)
|
||||||
encryptContent(it, eventType, eventContent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,7 +98,7 @@ internal class MXMegolmEncryption(
|
|||||||
*
|
*
|
||||||
* @param devicesInRoom the devices list
|
* @param devicesInRoom the devices list
|
||||||
*/
|
*/
|
||||||
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Try<MXOutboundSessionInfo> {
|
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXOutboundSessionInfo {
|
||||||
var session = outboundSession
|
var session = outboundSession
|
||||||
if (session == null
|
if (session == null
|
||||||
// Need to make a brand new session?
|
// Need to make a brand new session?
|
||||||
@ -126,7 +123,8 @@ internal class MXMegolmEncryption(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return shareKey(safeSession, shareMap).map { safeSession!! }
|
shareKey(safeSession, shareMap)
|
||||||
|
return safeSession
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,11 +134,11 @@ internal class MXMegolmEncryption(
|
|||||||
* @param devicesByUsers the devices map
|
* @param devicesByUsers the devices map
|
||||||
*/
|
*/
|
||||||
private suspend fun shareKey(session: MXOutboundSessionInfo,
|
private suspend fun shareKey(session: MXOutboundSessionInfo,
|
||||||
devicesByUsers: Map<String, List<MXDeviceInfo>>): Try<Unit> {
|
devicesByUsers: Map<String, List<MXDeviceInfo>>) {
|
||||||
// nothing to send, the task is done
|
// nothing to send, the task is done
|
||||||
if (devicesByUsers.isEmpty()) {
|
if (devicesByUsers.isEmpty()) {
|
||||||
Timber.v("## shareKey() : nothing more to do")
|
Timber.v("## shareKey() : nothing more to do")
|
||||||
return Try.just(Unit)
|
return
|
||||||
}
|
}
|
||||||
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
|
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
|
||||||
val subMap = HashMap<String, List<MXDeviceInfo>>()
|
val subMap = HashMap<String, List<MXDeviceInfo>>()
|
||||||
@ -157,12 +155,10 @@ internal class MXMegolmEncryption(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.v("## shareKey() ; userId $userIds")
|
Timber.v("## shareKey() ; userId $userIds")
|
||||||
return shareUserDevicesKey(session, subMap)
|
shareUserDevicesKey(session, subMap)
|
||||||
.flatMap {
|
|
||||||
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
|
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
|
||||||
shareKey(session, remainingDevices)
|
shareKey(session, remainingDevices)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share the device keys of a an user
|
* Share the device keys of a an user
|
||||||
@ -172,7 +168,7 @@ internal class MXMegolmEncryption(
|
|||||||
* @param callback the asynchronous callback
|
* @param callback the asynchronous callback
|
||||||
*/
|
*/
|
||||||
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
|
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
|
||||||
devicesByUser: Map<String, List<MXDeviceInfo>>): Try<Unit> {
|
devicesByUser: Map<String, List<MXDeviceInfo>>) {
|
||||||
val sessionKey = olmDevice.getSessionKey(session.sessionId)
|
val sessionKey = olmDevice.getSessionKey(session.sessionId)
|
||||||
val chainIndex = olmDevice.getMessageIndex(session.sessionId)
|
val chainIndex = olmDevice.getMessageIndex(session.sessionId)
|
||||||
|
|
||||||
@ -190,17 +186,16 @@ internal class MXMegolmEncryption(
|
|||||||
var t0 = System.currentTimeMillis()
|
var t0 = System.currentTimeMillis()
|
||||||
Timber.v("## shareUserDevicesKey() : starts")
|
Timber.v("## shareUserDevicesKey() : starts")
|
||||||
|
|
||||||
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||||
.flatMap {
|
|
||||||
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
|
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
|
||||||
+ (System.currentTimeMillis() - t0) + " ms")
|
+ (System.currentTimeMillis() - t0) + " ms")
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
var haveTargets = false
|
var haveTargets = false
|
||||||
val userIds = it.userIds
|
val userIds = results.userIds
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
val devicesToShareWith = devicesByUser[userId]
|
val devicesToShareWith = devicesByUser[userId]
|
||||||
for ((deviceID) in devicesToShareWith!!) {
|
for ((deviceID) in devicesToShareWith!!) {
|
||||||
val sessionResult = it.getObject(userId, deviceID)
|
val sessionResult = results.getObject(userId, deviceID)
|
||||||
if (sessionResult?.sessionId == null) {
|
if (sessionResult?.sessionId == null) {
|
||||||
// no session with this device, probably because there
|
// no session with this device, probably because there
|
||||||
// were no one-time keys.
|
// were no one-time keys.
|
||||||
@ -226,7 +221,6 @@ internal class MXMegolmEncryption(
|
|||||||
Timber.v("## shareUserDevicesKey() : has target")
|
Timber.v("## shareUserDevicesKey() : has target")
|
||||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
|
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
|
||||||
sendToDeviceTask.execute(sendToDeviceParams)
|
sendToDeviceTask.execute(sendToDeviceParams)
|
||||||
.map {
|
|
||||||
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
|
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
|
||||||
+ (System.currentTimeMillis() - t0) + " ms")
|
+ (System.currentTimeMillis() - t0) + " ms")
|
||||||
|
|
||||||
@ -241,20 +235,15 @@ internal class MXMegolmEncryption(
|
|||||||
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
|
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unit
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Timber.v("## shareUserDevicesKey() : no need to sharekey")
|
Timber.v("## shareUserDevicesKey() : no need to sharekey")
|
||||||
Try.just(Unit)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* process the pending encryptions
|
* process the pending encryptions
|
||||||
*/
|
*/
|
||||||
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> {
|
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Content {
|
||||||
return Try<Content> {
|
|
||||||
// Everything is in place, encrypt all pending events
|
// Everything is in place, encrypt all pending events
|
||||||
val payloadJson = HashMap<String, Any>()
|
val payloadJson = HashMap<String, Any>()
|
||||||
payloadJson["room_id"] = roomId
|
payloadJson["room_id"] = roomId
|
||||||
@ -276,8 +265,7 @@ internal class MXMegolmEncryption(
|
|||||||
// m.new_device message if they don't have our session key.
|
// m.new_device message if they don't have our session key.
|
||||||
map["device_id"] = credentials.deviceId!!
|
map["device_id"] = credentials.deviceId!!
|
||||||
session.useCount++
|
session.useCount++
|
||||||
map
|
return map
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -287,24 +275,22 @@ internal class MXMegolmEncryption(
|
|||||||
* @param userIds the user ids whose devices must be checked.
|
* @param userIds the user ids whose devices must be checked.
|
||||||
* @param callback the asynchronous callback
|
* @param callback the asynchronous callback
|
||||||
*/
|
*/
|
||||||
private suspend fun getDevicesInRoom(userIds: List<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> {
|
private suspend fun getDevicesInRoom(userIds: List<String>): MXUsersDevicesMap<MXDeviceInfo> {
|
||||||
// We are happy to use a cached version here: we assume that if we already
|
// We are happy to use a cached version here: we assume that if we already
|
||||||
// have a list of the user's devices, then we already share an e2e room
|
// have a list of the user's devices, then we already share an e2e room
|
||||||
// with them, which means that they will have announced any new devices via
|
// with them, which means that they will have announced any new devices via
|
||||||
// an m.new_device.
|
// an m.new_device.
|
||||||
return deviceListManager
|
val keys = deviceListManager.downloadKeys(userIds, false)
|
||||||
.downloadKeys(userIds, false)
|
|
||||||
.flatMap {
|
|
||||||
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|
||||||
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|
||||||
|
|
||||||
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
|
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
|
||||||
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
||||||
|
|
||||||
for (userId in it.userIds) {
|
for (userId in keys.userIds) {
|
||||||
val deviceIds = it.getUserDeviceIds(userId) ?: continue
|
val deviceIds = keys.getUserDeviceIds(userId) ?: continue
|
||||||
for (deviceId in deviceIds) {
|
for (deviceId in deviceIds) {
|
||||||
val deviceInfo = it.getObject(userId, deviceId) ?: continue
|
val deviceInfo = keys.getObject(userId, deviceId) ?: continue
|
||||||
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
|
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
|
||||||
// The device is not yet known by the user
|
// The device is not yet known by the user
|
||||||
unknownDevices.setObject(userId, deviceId, deviceInfo)
|
unknownDevices.setObject(userId, deviceId, deviceInfo)
|
||||||
@ -327,10 +313,9 @@ internal class MXMegolmEncryption(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (unknownDevices.isEmpty) {
|
if (unknownDevices.isEmpty) {
|
||||||
Try.just(devicesInRoom)
|
return devicesInRoom
|
||||||
} else {
|
} else {
|
||||||
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
|
throw MXCryptoError.UnknownDevice(unknownDevices)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user