1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 08:12:46 +02:00

Compare commits

...

295 Commits

Author SHA1 Message Date
Benoit Marty
ec0974f72c Merge branch 'hotfix/dimensionConverter' 2019-09-24 14:28:51 +02:00
Benoit Marty
1e963bc0dc Fix crash: MergedHeaderItem was missing dimensionConverter 2019-09-24 14:23:13 +02:00
Benoit Marty
cc832633a5 Merge branch 'release/0.6.0' 2019-09-24 10:22:42 +02:00
Benoit Marty
eadea9016b Prepare release 0.6.0 2019-09-24 10:22:36 +02:00
Benoit Marty
6422d946c9 Merge pull request #584 from vector-im/feature/hasUnread
isEventRead() returns true if the event has been sent by the user
2019-09-24 10:17:52 +02:00
Benoit Marty
5cc3dc00e3 Merge pull request #581 from vector-im/feature/talk_back
Fix a few accessibility issues
2019-09-24 10:06:28 +02:00
Benoit Marty
5a2a9f908a isEventRead() returns true if the event has been sent by the user 2019-09-24 10:04:57 +02:00
Benoit Marty
c1f2e9f171 Fix a few accessibility issues - home menu (best compromise) 2019-09-23 17:48:13 +02:00
Benoit Marty
620ba279d8 Fix a few accessibility issues 2019-09-23 16:32:54 +02:00
Benoit Marty
3fcfa33364 Merge pull request #573 from vector-im/feature/notif_invit
Clean up push rules management and fixes several issues
2019-09-23 16:23:35 +02:00
Benoit Marty
546da0f173 Merge branch 'develop' into feature/notif_invit 2019-09-23 16:23:22 +02:00
Benoit Marty
001711d5a3 Merge pull request #574 from vector-im/feature/big_emoji
Embiggen messages with multiple emojis also for edited messages
2019-09-23 16:22:53 +02:00
Benoit Marty
8e1a964679 After Ganfra's review 2019-09-23 15:08:18 +02:00
Benoit Marty
b25a130db1 Rename DimensionUtils to DimensionConverter, and inject resources instead of context. 2019-09-23 14:39:52 +02:00
Benoit Marty
8a9e6497e8 Merge pull request #578 from vector-im/feature/fix_focus_login
Fix infinite focus on HS field
2019-09-23 10:05:43 +02:00
Valere
47e3797b7e Fix infinite focus on HS field 2019-09-23 09:44:32 +02:00
Benoit Marty
5cbc90e06a Embiggen messages with multiple emojis also for edited messages (#458)
And daggerize DimensionUtils
2019-09-20 19:22:42 +02:00
Benoit Marty
e04bf31faa Fix wrong "no network" banner 2019-09-20 18:18:55 +02:00
Benoit Marty
d25cf79b07 Cleanup 2019-09-20 17:50:57 +02:00
Benoit Marty
faa8e6bbb2 m.notice messages trigger push notifications (#238) 2019-09-20 17:50:57 +02:00
Benoit Marty
d3d4deb884 Rework Action (better kotlin code) 2019-09-20 17:50:57 +02:00
Benoit Marty
f6b8e0c479 Fix issue: push rules was not retrieved after a clear cache.
We now store push rules from the sync response
2019-09-20 17:50:57 +02:00
Benoit Marty
2a726f54a2 Remove userId from PushRulesEntity and PusherEntity objects 2019-09-20 17:50:15 +02:00
Benoit Marty
1197d4021d Fix regression on PushRulesApi 2019-09-20 17:50:15 +02:00
Benoit Marty
03f8120b7d Create enum for Push rules. Also add some TODOs 2019-09-20 17:50:15 +02:00
Benoit Marty
acd7a709de Dagger: create @UserId to inject userId 2019-09-20 17:50:15 +02:00
Benoit Marty
5651ea515b Merge pull request #570 from vector-im/feature/left_group
Handle left group from sync
2019-09-20 17:44:13 +02:00
Benoit Marty
9794b3a49d Fix compilation issue of F-Droid build 2019-09-20 17:35:10 +02:00
Benoit Marty
b3e1c3969d Little changes after review 2019-09-20 17:34:50 +02:00
Benoit Marty
f24bed17a2 Add missing issue number 2019-09-19 17:56:34 +02:00
Benoit Marty
a993a30203 Handle left group from sync 2019-09-19 17:08:22 +02:00
Benoit Marty
91cc78d2ad Merge pull request #552 from vector-im/feature/draft
Save draft of a message when exiting a room with non empty composer (#329)
2019-09-19 13:11:35 +02:00
Benoit Marty
562acc9702 Save Draft only when app goes to background. 2019-09-19 13:09:08 +02:00
Benoit Marty
dfab88ed95 Display room with draft in the Catchup screen 2019-09-19 13:09:08 +02:00
Benoit Marty
36866dd24e Save draft of a message when exiting a room with non empty composer (#329) 2019-09-19 13:09:08 +02:00
Benoit Marty
c728834273 Merge pull request #566 from vector-im/feature/redact_notification
Redact notification
2019-09-19 13:02:17 +02:00
Benoit Marty
f5020d0f63 Daggerization and cleanup of NotificationUtils 2019-09-19 13:01:00 +02:00
Benoit Marty
7da9cafcc2 Remove any notification of a redacted event (#563)
Also do some cleanup and kotlinification on the code
2019-09-19 13:01:00 +02:00
Benoit Marty
6f09eea248 Merge pull request #562 from vector-im/feature/notification_edited
Message Editing: Update notifications (#128)
2019-09-19 12:59:10 +02:00
Benoit Marty
468bd5bcc9 Message Editing: Update notifications (#128) 2019-09-19 12:57:58 +02:00
Benoit Marty
3169093c50 Quick fix on the no connection banner displayed when internet is available 2019-09-19 12:55:39 +02:00
Benoit Marty
d60d766354 Merge pull request #524 from vector-im/feature/indicate_unread_rooms
Add unread indent on room list
2019-09-19 12:50:55 +02:00
Benoit Marty
0ffb5e627e Cleanup injected constructors 2019-09-19 12:43:39 +02:00
Benoit Marty
b4a13f9504 Add unread indent on room list 2019-09-19 12:43:39 +02:00
Benoit Marty
ffa8b7e73a Better fix 2019-09-18 11:24:29 +02:00
Benoit Marty
528958b3de Avoid export on env variable 2019-09-18 10:58:03 +02:00
Benoit Marty
3ffe2f7d40 Fix (again) issue with bad versionCode generated by Buildkite (#553) 2019-09-18 10:29:29 +02:00
Benoit Marty
bf42b73713 Merge pull request #555 from vector-im/feature/room_search
Cleanup on the room search screen
2019-09-17 15:28:54 +02:00
Benoit Marty
ed93f4a6c1 Cancel any request properly 2019-09-17 14:55:57 +02:00
Benoit Marty
b3d649a4d9 Fix characters erased from the Search field when the result are coming (#545) 2019-09-17 14:55:57 +02:00
Benoit Marty
3739e50d46 Better error message for timeout 2019-09-17 14:55:48 +02:00
Benoit Marty
9bf484cf1e Create a Failure to handle cancellation, and use it to ignore cancellation on room search 2019-09-17 14:55:48 +02:00
Benoit Marty
6c2faff1f0 Version++ (0.6.0) 2019-09-17 14:53:50 +02:00
Benoit Marty
07fca0922b Merge branch 'release/0.5.0' 2019-09-17 14:50:55 +02:00
Benoit Marty
282de21708 Merge branch 'release/0.5.0' into develop 2019-09-17 14:50:55 +02:00
Benoit Marty
ba9d119892 Prepare release 0.5.0 2019-09-17 14:50:43 +02:00
Benoit Marty
4453f0ced9 Merge pull request #560 from vector-im/feature/no_network
Display a "No network" banner when the device has no network
2019-09-17 14:40:42 +02:00
Benoit Marty
77168bfd6a Merge pull request #558 from vector-im/feature/login_sso
Quick implementation of SSO login - Also handling of magic link
2019-09-17 14:28:04 +02:00
Benoit Marty
25e9a179d2 SyncThread: Fix issue when network is back and the app was in background: do not restart the thread 2019-09-17 14:26:30 +02:00
Benoit Marty
73ec0f5a83 NetworkConnectivityChecker: filter onConnected callbacks (several callback if Wifi and LTE is connected)
Also do not use merlinsBeard.isConnected, which return trus even if there is no internet access (ex: with Wifi hotspot)
2019-09-17 14:22:08 +02:00
Benoit Marty
993fa74252 Cleanup after BillCarsonFr's review 2019-09-17 11:24:37 +02:00
Benoit Marty
38fc4984fe Display a no network indicator when there is no network: Create a dedicated View 2019-09-17 11:13:00 +02:00
Benoit Marty
695d8cce00 Display a no network indicator when there is no network (#559) 2019-09-17 10:59:58 +02:00
Benoit Marty
07e99901e1 SecretStoringUtils -> move to internal package 2019-09-17 10:38:37 +02:00
Benoit Marty
20f53e9a58 Signout: propose the user to retry in case of error 2019-09-17 10:33:27 +02:00
Benoit Marty
ced72aff4f Revert change done to save alias for the client 2019-09-17 10:32:09 +02:00
Benoit Marty
fdaaca49c2 Code quality (bad import) 2019-09-16 19:27:13 +02:00
Benoit Marty
3485f023b0 All current notifications were dismissed by mistake when the app is launched from the launcher 2019-09-16 19:24:52 +02:00
Benoit Marty
384dd100e9 Daggerization and Kotlinification of SecretStoringUtils 2019-09-16 19:19:14 +02:00
Benoit Marty
1ba8a58219 Cleanup SecretStoringUtils, and delete keys when user signs out 2019-09-16 18:29:06 +02:00
Benoit Marty
c8010561fc Rework on sign out task 2019-09-16 17:45:26 +02:00
Benoit Marty
1f127335bc Daggerization of RealmKeysUtils 2019-09-16 15:50:56 +02:00
Benoit Marty
138a210a73 Dagger: Screen component now exposes ActiveSessionHolder instead of Session 2019-09-16 14:43:39 +02:00
Benoit Marty
ca6bcde82d Re add the remove CurlLoggingInterceptor 2019-09-16 14:43:08 +02:00
Benoit Marty
6bda437f5d Auto configure homeserver and identity server URLs of LoginActivity with a magic link 2019-09-16 10:58:51 +02:00
Benoit Marty
3e6b65e174 Handle M_CONSENT_NOT_GIVEN error (#64) 2019-09-13 18:21:56 +02:00
Benoit Marty
137dcab734 Curl login interceptor now log the AT (on debug mode) 2019-09-13 16:20:19 +02:00
Benoit Marty
b22b8fba02 Fix the mess up with OnBackPress support on Fragment 2019-09-13 15:55:33 +02:00
Benoit Marty
3ccdf4a244 Login: some cleanup 2019-09-13 15:35:44 +02:00
Benoit Marty
5fbd271b1c Login: add SSO support 2019-09-13 15:19:45 +02:00
Benoit Marty
db8ea0f5e8 Login: check login flow - step 1 2019-09-13 11:08:54 +02:00
Benoit Marty
a47a3ead1f Login: move login code to the ViewModel 2019-09-13 10:39:22 +02:00
Benoit Marty
05b2092ffc Login: move existing code to a Fragment, MvRx style 2019-09-13 10:07:55 +02:00
Benoit Marty
6249a59203 Merge pull request #554 from vector-im/feature/build_number
Fix issue with bad versionCode generated by Buildkite (#553)
2019-09-12 17:24:46 +02:00
Benoit Marty
618e9a4f52 Fix issue with bad versionCode generated by Buildkite (#553) 2019-09-12 16:17:44 +02:00
Benoit Marty
f2c8d4ad02 Merge pull request #549 from vector-im/feature/third_party_invite
Fix rendering issue of accepted third party invitation event
2019-09-06 16:36:30 +02:00
Benoit Marty
be524472ec Merge pull request #546 from vector-im/feature/cleanup
Cleanup
2019-09-06 16:25:08 +02:00
Benoit Marty
1b82a1a24d Cleanup 2019-09-06 15:52:29 +02:00
Benoit Marty
cf0b331c3b Handle invite to the current user rendering 2019-09-06 15:48:42 +02:00
Benoit Marty
2a92a3dc80 Fix rendering issue of accepted third party invitation event 2019-09-06 14:34:52 +02:00
Benoit Marty
012840abba Progress in initial sync dialog is decreasing for a step and should not (#532) 2019-09-05 18:14:05 +02:00
Benoit Marty
a5975a099e Cleanup and document DefaultInitialSyncProgressService 2019-09-05 17:23:09 +02:00
Benoit Marty
38da4b9ee5 Cleanup and document DefaultInitialSyncProgressService 2019-09-05 17:02:03 +02:00
Benoit Marty
242e60fcaa Rename CryptoManager to DefaultCryptoService 2019-09-05 16:14:34 +02:00
Benoit Marty
a23be05cbf Better type 2019-09-05 16:04:41 +02:00
Benoit Marty
ed39b02924 Avoid using keyword for variable names 2019-09-05 16:04:41 +02:00
Benoit Marty
fe931b5361 Merge pull request #418 from Dominaezzz/kotlinify-1
Some more kotlinification
2019-09-05 16:02:30 +02:00
Benoit Marty
90d9cd0587 Merge pull request #416 from Dominaezzz/kt-remove_java_util
Remove most usages of the java.util package
2019-09-05 15:33:03 +02:00
Benoit Marty
9cedb18921 Merge pull request #538 from vector-im/feature/log_mgmt
Reduce release build log level
2019-09-05 15:24:04 +02:00
Benoit Marty
e89ba7b87b Update wording 2019-09-05 15:23:38 +02:00
Benoit Marty
902657c22a Merge pull request #537 from vector-im/feature/fix_crash
Fix crash due to missing informationData (#535)
2019-09-02 15:31:28 +02:00
Valere
eec2abf164 Reduce release build log level 2019-09-02 14:33:53 +02:00
Benoit Marty
6879cc8ca8 Fix crash due to missing informationData (#535) 2019-09-02 14:24:36 +02:00
Benoit Marty
fd6bbbd3b5 Fix issue with version name (Fixes #533) 2019-08-30 15:57:39 +02:00
Benoit Marty
0ff0b014a9 Version++ (0.5.0) 2019-08-30 15:07:04 +02:00
Benoit Marty
a89f0ddd1d Merge branch 'release/0.4.0' 2019-08-30 15:04:43 +02:00
Benoit Marty
fdc9e84dd5 Merge branch 'release/0.4.0' into develop 2019-08-30 15:04:43 +02:00
Benoit Marty
58f878fca9 Prepare version 0.4.0 2019-08-30 15:04:28 +02:00
Benoit Marty
88095e4bd9 Add entry in change file 2019-08-30 14:54:15 +02:00
Benoit Marty
47d22a3d5e Import translation from Riot and MatrixSDK 2019-08-30 11:21:43 +02:00
Valere
28e82cb8ea Merge pull request #531 from vector-im/feature/fix_crash_530
Fix / EmojiCompat not initialized
2019-08-29 17:46:51 +02:00
Valere
35817245cb refactoring, code review 2019-08-29 17:27:49 +02:00
Valere
75266f42bb Fix / EmojiCompat not initialized 2019-08-29 16:49:22 +02:00
Benoit Marty
95c4c9ce56 Merge pull request #527 from vector-im/feature/privacy
Privacy: remove log of notifiable event (#519)
2019-08-29 12:16:34 +02:00
Benoit Marty
ce5570105d Privacy: remove log of notifiable event (#519) 2019-08-29 10:36:45 +02:00
Benoit Marty
188a9aebfa Merge pull request #525 from vector-im/feature/read_receipt_cleanup
Feature/read receipt cleanup
2019-08-29 10:19:06 +02:00
Benoit Marty
c95223f5d2 Add long click support on unsupported event 2019-08-28 18:17:37 +02:00
Benoit Marty
ef0362ba9c Display Read Receipt on unsupported events 2019-08-28 17:31:31 +02:00
Benoit Marty
ea242f6737 Hide ReadReceipt View when it is not relevant 2019-08-28 17:17:37 +02:00
Valere
cbc08d834b Merge pull request #522 from vector-im/feature/fix_e2e_reply
Fix / regression on e2e reply and edit of reply
2019-08-28 10:38:22 +02:00
Valere
0ab6b33fb6 Merge branch 'develop' into feature/fix_e2e_reply 2019-08-28 10:38:12 +02:00
Valere
1b394527b6 cleaning + code review 2019-08-28 10:22:51 +02:00
Valere
a8f1388721 Merge pull request #520 from vector-im/feature/read_receipts_511
Improve read receipt design
2019-08-28 10:17:56 +02:00
Valere
166be4e289 Improve read receipt design 2019-08-28 09:56:10 +02:00
Valere
b49ccefe63 Merge pull request #521 from vector-im/feature/fix_dome_video_wont_play
Some video won't play
2019-08-28 03:43:35 -04:00
Valere
825760d17e Fix / regression on e2e reply and edit of reply 2019-08-27 17:05:04 +02:00
Valere
b5af62c3ea Some video won't play
VideoView fails to play some remote uri video on some device. For now video is downloaded locally in internal cache then played. This offers basic support before full media preview implementation
2019-08-27 16:50:02 +02:00
Valere
a51d96bf00 Merge pull request #325 from vector-im/feature/non_unicode_reaction
Accept non unicode reactions
2019-08-27 08:10:51 -04:00
Valere
7e142d201d Use EmojiCompat to build EmojiSpans from text 2019-08-27 11:06:52 +02:00
Valere
2be6058971 accept non unicode reactions 2019-08-27 10:58:21 +02:00
Valere
49d73f360e Merge pull request #494 from vector-im/feature/fix_441
Fix text diff removed linebreak
2019-08-27 04:36:03 -04:00
Valere
bd88d85a21 Merge branch 'develop' into feature/fix_441 2019-08-27 04:35:17 -04:00
Valere
be4fc5cce6 Merge pull request #493 from vector-im/feature/fix_358
Date change message repeats for each redaction until a normal message
2019-08-27 04:34:35 -04:00
Valere
704da1be55 Merge branch 'develop' into feature/fix_358 2019-08-27 04:34:24 -04:00
Valere
5d002532d3 Merge pull request #495 from vector-im/feature/fix_423
Slide-in reply icon is distorted
2019-08-27 04:22:02 -04:00
Valere
d4161e9a1a Fix text diff removed linebreak 2019-08-27 10:17:42 +02:00
Valere
7966ebef03 Date change message repeats for each redaction until a normal message 2019-08-27 10:16:11 +02:00
Valere
ed5faca5d2 Slide-in reply icon is distorted 2019-08-27 10:06:20 +02:00
Benoit Marty
8ca829d538 An error was displayed by mistake 2019-08-19 17:22:04 +02:00
ganfra
e7819ce678 Merge pull request #496 from vector-im/feature/di_clean
Dagger clean
2019-08-19 16:41:50 +02:00
ganfra
5402902bc2 Merge branch 'develop' into feature/di_clean 2019-08-19 15:04:26 +02:00
ganfra
bc1350aaf5 Merge pull request #484 from vector-im/feature/timeline_read_receipts
Feature/timeline read receipts
2019-08-19 14:29:59 +02:00
ganfra
fd74e3dfb1 Read receipts: clean code after review 2019-08-19 14:08:15 +02:00
ganfra
e0628da1cb Dagger: use AssistedInjectModule for viewModel + use AssistedFactory for room dependencies 2019-08-14 19:09:56 +02:00
Benoit Marty
aa4e74e986 Merge pull request #487 from vector-im/feature/fix_ui_issues
Feature/fix ui issues
2019-08-14 18:20:08 +02:00
Benoit Marty
6cc0c0672e Merge pull request #474 from vector-im/feature/dev_suffix
Automatic "-dev" version suffix on non master branch
2019-08-14 18:15:44 +02:00
ganfra
501474b720 Fix code quality issues 2019-08-14 14:53:40 +02:00
ganfra
e11c66035c Theme: the action menu text items should use colorAccent 2019-08-14 14:19:21 +02:00
ganfra
3d2d219d79 Room list: let the fab animation be quicker 2019-08-14 14:18:56 +02:00
ganfra
63af03bedd List: add overScroll 2019-08-14 14:18:42 +02:00
ganfra
d3827b8673 Read receipts: branch settings to show/hide them 2019-08-14 10:51:09 +02:00
Benoit Marty
4ca2531e47 develop branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode
Other branches (master, features, etc.) will have version code based on application version.
2019-08-14 10:45:17 +02:00
ganfra
4e8dc72439 Update CHANGES 2019-08-13 15:17:04 +02:00
ganfra
25a4240a5a Merge branch 'develop' into feature/timeline_read_receipts 2019-08-13 15:16:10 +02:00
ganfra
b9cfda23b6 Read receipts: just juste invisible on hidden avatars, to have a bigger touch zone 2019-08-13 15:06:00 +02:00
ganfra
06dcf75a32 Read receipts: fix not appearing RR 2019-08-13 12:06:49 +02:00
ganfra
21deb2551d Read receipts: handle read receipts set on filtered events + let BottomSheet takes a snapshot instead of being live. 2019-08-12 17:59:07 +02:00
ganfra
70639f180c Read receipts: add read receipts bottom sheet 2019-08-08 19:59:20 +02:00
ganfra
1dbb02a80d Read receipts: create custom view to use it wherever we want easily 2019-08-08 17:51:06 +02:00
ganfra
825463d9cd Change package for NotificationAreaView 2019-08-08 17:50:33 +02:00
ganfra
c313ce78cb Read receipts: sort descending by timestamp 2019-08-08 17:49:50 +02:00
ganfra
39f58d048b Read receipts: fix dummy being overrided 2019-08-08 17:49:31 +02:00
Benoit Marty
3f792c7a84 Automatic "-dev" version suffix on non master branch 2019-08-08 16:57:03 +02:00
Benoit Marty
347dcb469a Version++ 2019-08-08 16:47:13 +02:00
Benoit Marty
9cd69d1e33 Merge branch 'release/0.3.0' 2019-08-08 16:45:03 +02:00
Benoit Marty
79fb1985aa Merge branch 'release/0.3.0' into develop 2019-08-08 16:45:02 +02:00
Benoit Marty
e216cd15a8 Prepare release 0.3.0 2019-08-08 16:44:53 +02:00
Benoit Marty
37fde374b3 Merge pull request #469 from vector-im/feature/versionCode_auto
Ensure versionCode is the wanted one for GPlay and F-Droid build
2019-08-08 16:32:10 +02:00
Benoit Marty
f7b471f141 Stop using BuildConfig.VERSION_CODE, it is not the correct value 2019-08-08 16:31:45 +02:00
Benoit Marty
93fd56a7ca Ensure versionCode is the wanted one for GPlay and F-Droid build 2019-08-08 16:30:44 +02:00
Benoit Marty
5a9d88e791 Merge pull request #473 from vector-im/feature/sync_room
Feature/sync room
2019-08-08 16:15:26 +02:00
Benoit Marty
eaf6a9923a Cancel sync request on pause and timeout to 0 after pause (#404) 2019-08-08 16:04:53 +02:00
ganfra
d98567045c Read receipts: use a simpler strategy when it's initialSync 2019-08-08 15:03:36 +02:00
ganfra
b4ce8748cb First step in handling read receipts 2019-08-08 14:32:11 +02:00
Benoit Marty
9d5433a857 Show sync progress also in room detail screen (#403) 2019-08-08 14:14:10 +02:00
Benoit Marty
6d4ee83e65 Merge pull request #472 from vector-im/feature/vectorPref
Dagger for VectorPreferences and /markdown command as a bonus
2019-08-08 12:43:22 +02:00
Benoit Marty
6e44cca17d Handle /markdown command 2019-08-08 12:09:05 +02:00
Benoit Marty
0a73887c70 Daggerization of VectorPreferences 2019-08-08 11:52:50 +02:00
ganfra
7fef063e15 Merge pull request #468 from vector-im/feature/fix_realm_issues
Feature/fix realm issues
2019-08-07 18:05:06 +02:00
Benoit Marty
24f391dac0 Merge pull request #467 from vector-im/feature/playstore_crash
Feature/playstore crash
2019-08-07 17:10:49 +02:00
Benoit Marty
81c7f694d6 Import Strings form Riot 2019-08-07 16:10:50 +02:00
Benoit Marty
80e2fc0ca3 Merge pull request #466 from vector-im/feature/edit_history_item
Add "View Edit History" item in the message bottom sheet (#401)
2019-08-07 15:08:26 +02:00
Benoit Marty
9f53406e99 Fix crash (KotlinNullPointerException) observed on PlayStore 2019-08-07 13:35:44 +02:00
Benoit Marty
3584658c36 Fix crash (IllegalStateException) observed on PlayStore 2019-08-07 13:24:43 +02:00
Benoit Marty
12a0cbb400 Fix crash observed on PlayStore 2019-08-07 13:16:04 +02:00
Benoit Marty
20437446b4 Add "View Edit History" item in the message bottom sheet (#401) 2019-08-07 13:05:22 +02:00
Benoit Marty
35229882e3 Fix (edited) link can be copied to clipboard (#402) 2019-08-07 12:28:21 +02:00
Benoit Marty
a04f4421f6 Merge pull request #464 from vector-im/feature/splitApk
Split apk
2019-08-07 12:11:13 +02:00
Benoit Marty
af1e81f65e Remove unused react native lib, and ensure dependencies lib are explicitly declared 2019-08-07 11:53:59 +02:00
Benoit Marty
63f6081fa5 Split APK: generate one APK per arch, to reduce APK size of about 30% 2019-08-07 11:46:38 +02:00
Benoit Marty
ee2e575211 Display VersionCode of the app in the settings, because Android system does not display it anymore 2019-08-07 11:44:51 +02:00
ganfra
0949d29f9c Let TimelineEvent be queried by SendState 2019-08-07 10:54:54 +02:00
Benoit Marty
23466fb5a4 Merge pull request #463 from vector-im/feature/fix_theme
Fix theme not well defined at runtime after configurationChange
2019-08-07 10:40:33 +02:00
ganfra
7f09e64d63 Fix timeline forward loader showing when sending events 2019-08-07 09:59:37 +02:00
ganfra
585f0ba4b7 Add an identifier method on ChunkEntity 2019-08-06 21:32:45 +02:00
ganfra
245fbe86d9 Get enum safe with realm entities 2019-08-06 21:32:40 +02:00
Dominic Fischer
456908c851 Merge branch 'develop' into kt-remove_java_util 2019-08-06 18:27:39 +01:00
ganfra
b79fdf6a85 Fix theme not well defined at runtime after configurationChange 2019-08-06 18:55:38 +02:00
Benoit Marty
d9f448c9aa Merge pull request #459 from vector-im/feature/clenup_after_hol
Review of merged PRs
2019-08-06 18:39:37 +02:00
Benoit Marty
7a6fc4936b Start chain: create extension 2019-08-06 18:15:15 +02:00
Benoit Marty
d82fd10f3b Start chain: add missing cases 2019-08-06 18:15:15 +02:00
Benoit Marty
4009f2c176 Add comment to explain why we use a AlwaysSuccessfulWorker 2019-08-06 18:15:15 +02:00
Benoit Marty
15c4b03340 Event: do not display sendState in View Source and cleanup the class 2019-08-06 18:14:24 +02:00
Benoit Marty
7b5dff3dcf Mutualize :? part 2019-08-06 18:14:24 +02:00
Benoit Marty
357123743f Search firstIndexOf, because server url can contains port (This is what JS does, but Riot Android is also bugged) 2019-08-06 18:14:24 +02:00
Benoit Marty
bb04af1e2c Remove useless code 2019-08-06 18:14:24 +02:00
Benoit Marty
2f94fbd7eb Use existing method 2019-08-06 18:14:24 +02:00
Benoit Marty
f2a3bdb68e Kotlin style 2019-08-06 18:14:24 +02:00
Benoit Marty
097e9714ff Cleaner code 2019-08-06 18:14:24 +02:00
Benoit Marty
acae0fad3e Better private method name 2019-08-06 18:14:24 +02:00
Benoit Marty
4deb7eb865 Javadoc for NoMerger 2019-08-06 18:14:24 +02:00
Benoit Marty
f910cd6f97 More robust SDK: retry only when on failure 2019-08-06 18:14:24 +02:00
Benoit Marty
652ac81fa1 simple code 2019-08-06 18:14:24 +02:00
Benoit Marty
99f4196388 More code cleanup/review 2019-08-06 18:14:24 +02:00
Benoit Marty
c0b94f4111 Typo 2019-08-06 18:14:24 +02:00
Benoit Marty
1462fa0484 Simple code 2019-08-06 18:14:24 +02:00
Benoit Marty
dafdc1d3ad Cleaner API 2019-08-06 18:07:35 +02:00
Benoit Marty
394b89e76b Avoid duplicated code 2019-08-06 18:07:35 +02:00
Benoit Marty
0db8e7da43 Format 2019-08-06 18:07:35 +02:00
ganfra
dae8b5c196 Merge pull request #460 from vector-im/feature/fix_cancellations
Feature/fix cancellations
2019-08-06 18:06:05 +02:00
Dominic Fischer
215324a03e Some kotlinification
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-08-06 11:36:39 +01:00
ganfra
d3ce4c491c Clean code after review 2019-08-06 11:45:06 +02:00
Benoit Marty
ed6d28bd3b Merge pull request #417 from Dominaezzz/kt-opt
Some optimisations
2019-08-06 11:42:07 +02:00
Benoit Marty
c2e053b62b Merge pull request #414 from Dominaezzz/kt-leak
Fix potential resource leak
2019-08-06 11:39:51 +02:00
Benoit Marty
c450849cc3 Merge pull request #425 from Cadair/patch-1
Fix reply fallback prefix
2019-08-06 11:23:37 +02:00
ganfra
fe884dba2d Update CHANGES.md and fix code quality 2019-08-05 20:28:50 +02:00
ganfra
3fa4dbaa25 Make async transaction working with suspend method 2019-08-05 20:17:59 +02:00
ganfra
4a74f58516 Task: use a builder with DSL and introduce Constraints (only boolean connectedToNetwork at the moment) 2019-08-05 20:17:36 +02:00
ganfra
c413321a22 Remove unnecessary code and fix signout 2019-08-02 13:15:56 +02:00
ganfra
d696bd2830 Send worker: let LIMIT_EXCEEDED error to be retry 2019-08-02 11:36:32 +02:00
ganfra
a2b6bd0f62 Fix network reconnection with sync 2019-08-02 11:35:58 +02:00
ganfra
9cc922a8a2 Optimize imports 2019-08-02 11:35:27 +02:00
Valere
c36d1bcd06 Merge pull request #456 from vector-im/feature/fix_image_transition_overlap
Fix / Shared element transition overlap
2019-08-02 10:18:14 +02:00
Valere
85499c6b33 fix for background overlaps 2019-08-02 10:00:33 +02:00
Valere
8076eab4b5 Fix / Shared element transition overlap
Shared element was overlapping top system bars
2019-08-02 10:00:33 +02:00
Valere
d47c0f5ebc Fix / layout res in debug instead of main 2019-08-02 09:59:59 +02:00
ganfra
fd09a1224e Remove Try from suspending functions 2019-08-01 17:15:17 +02:00
ganfra
c300c50093 Merge pull request #449 from vector-im/feature/room_update
Feature/room upgrade
2019-07-31 15:34:38 +02:00
ganfra
77c4355aed Merge branch 'develop' into feature/room_update 2019-07-31 14:27:12 +02:00
ganfra
1a92562182 Clean code after review 2019-07-31 14:06:10 +02:00
ganfra
9c390dcc0c Merge pull request #453 from vector-im/feature/fix_code_quality
Fix code quality issues
2019-07-30 21:54:38 +02:00
ganfra
95089b91b8 UserAccountData: optimize helper and clean code. 2019-07-30 21:41:29 +02:00
ganfra
eb446d7b49 Fix code quality issues 2019-07-30 21:20:30 +02:00
ganfra
dc4786ecf0 Room upgrade: add rx flux and handle failures more precisely 2019-07-30 19:13:09 +02:00
Valere
e245023add Merge pull request #444 from vector-im/feature/fail_to_send_msg
Basic Message Failure support + Resend (text only)
2019-07-30 18:31:53 +02:00
Stuart Mumford
90fad23493 Fix reply fallback prefix
Plain text reply fallback should be prefixed with "> " not ">" (as per spec).

Signed-off-by: Stuart Mumford <stuart@cadair.com>
2019-07-30 12:09:29 -04:00
Valere
000db4b192 Basic Message Failure support + Resend (text only)
+ clean worker inputs when starting new independent task in unique queue
2019-07-30 17:53:43 +02:00
ganfra
2a16c36a59 Merge pull request #451 from vector-im/feature/fix_user_account_data_direct
User Account Data: fix sync issues with direct invites
2019-07-30 17:39:12 +02:00
ganfra
ef6c1cfc63 RoomSummaryUpdater: remove unused params 2019-07-30 17:37:16 +02:00
ganfra
4b4156996d User Account Data: fix sync issues with direct invites 2019-07-30 17:32:31 +02:00
Valere
087cc0e6e3 Merge pull request #448 from danteissaias/develop
Fix #447
2019-07-30 17:07:22 +02:00
ganfra
f4df27c2dc Merge branch 'develop' into feature/room_update 2019-07-30 15:51:56 +02:00
ganfra
ab25980c4e Merge pull request #437 from vector-im/feature/create_direct_room
Feature/create direct room
2019-07-30 15:13:30 +02:00
Dante Issaias
77b402ce70 updates CHANGES.md
Signed-off-by: Dante Issaias <dante.issaias@gmail.com>
2019-07-30 14:01:41 +01:00
Dante Issaias
2763fbb496 fix #447
Signed-off-by: Dante Issaias <dante.issaias@gmail.com>
2019-07-30 13:57:04 +01:00
ganfra
6deba31111 Direct room: finally use PagedList as we can get a lot of users in DB. 2019-07-30 14:51:14 +02:00
ganfra
ff6ce8a4b7 Create direct : remove letter headers when filtering 2019-07-29 19:13:06 +02:00
ganfra
d0cff219aa Merge pull request #446 from vector-im/feature/remove_identity_default
Remove default identity server as we don't use it.
2019-07-29 18:32:07 +02:00
ganfra
65f0af918f Remove default identity server as we don't use it. 2019-07-29 18:26:26 +02:00
ganfra
ac38a6461c Tombstone : handle joining viaserver params 2019-07-26 19:17:12 +02:00
ganfra
9a1e16a170 Tombstone : add notification area and handle links 2019-07-26 14:51:14 +02:00
ganfra
9e5c70dda3 Room update: start handling tombstone and room create events [WIP] 2019-07-25 19:34:39 +02:00
ganfra
0255696c88 Update CHANGES 2019-07-25 16:49:15 +02:00
ganfra
76a9625f25 Direct chat : finalize flow 2019-07-25 16:34:27 +02:00
ganfra
5af6bf3762 Direct room: finally handle selection with chips (not as Nad design) 2019-07-25 16:34:27 +02:00
ganfra
507bc2f622 UserEntity: fix not inserted at all 2019-07-23 21:31:58 +02:00
ganfra
125eacb20b Direct messages: try to handle selecting/deselecting users (WIP) 2019-07-23 19:53:47 +02:00
Valere
6176520805 Merge pull request #407 from vector-im/feature/pending_edits_ux
Feature/pending edits ux
2019-07-22 23:53:26 +02:00
Valere
3aea0a50ca Merge branch 'develop' into feature/pending_edits_ux 2019-07-22 23:53:16 +02:00
Valere
ab87a3caea Merge pull request #397 from vector-im/feature/animation_image_preview
Better image fullscreen preview animation
2019-07-22 23:37:15 +02:00
Valere
c58328f94e cleaning / review 2019-07-22 23:36:19 +02:00
ganfra
03974c8bdf Create Direct Room : fix loading/error state (WIP) 2019-07-22 19:01:17 +02:00
ganfra
151ae7f4dd Direct chat: handle user account data 2019-07-22 18:58:55 +02:00
Dominic Fischer
a34b053efe Some optimisations
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-07-21 23:35:38 +01:00
Dominic Fischer
02e342849f Remove most usages of the java.util package
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-07-21 23:23:56 +01:00
Dominic Fischer
b59017938b Fix potential leak
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-07-21 19:11:53 +01:00
ganfra
2c81e41288 Merge branch 'develop' into feature/create_direct_room 2019-07-19 18:18:22 +02:00
ganfra
cb44ab547c Create direct room: almost finished, still need to handle showing selected users in search field 2019-07-19 18:12:42 +02:00
Benoit Marty
6d01a570fd Clear notification for a room left on another client 2019-07-19 16:44:30 +02:00
Valere
4a2bf0d6c6 Cleaning Lint 2019-07-19 16:18:47 +02:00
Valere
36af8a6a9f Lab / show replace in timeline when show hidden event selected 2019-07-19 16:13:55 +02:00
Valere
40a68c3e9f Show pending edits by fading the event body #193
+ Fix issues with edits local echo management in aggregation
2019-07-19 16:13:35 +02:00
Benoit Marty
1a4ec34bb2 Code cleanup 2019-07-19 16:03:37 +02:00
Benoit Marty
10490e3aa6 Close detail room screen when the room is left with another client (#256) 2019-07-19 16:00:06 +02:00
Benoit Marty
cd6624a8a6 Fix issue on setting screen: bad alignment of title 2019-07-19 15:15:29 +02:00
Valere
3965218bf9 Cleaning / Review 2019-07-19 12:12:17 +02:00
Valere
d78ff7ab08 Fix / can't zoom after rotation 2019-07-19 11:58:24 +02:00
ganfra
cb274d6a33 Add some cancelable on service methods and start branching Rx 2019-07-19 11:21:16 +02:00
Valere
c00dbce536 Fix #390
(edited) string in edited message body
2019-07-19 09:58:53 +02:00
Valere
db88caf7fa Better image fullscreen preview animation 2019-07-18 18:53:46 +02:00
Benoit Marty
c3d945d6bb Version++ 2019-07-18 17:48:56 +02:00
Benoit Marty
4c128602b2 Merge branch 'release/0.2.0' into develop 2019-07-18 17:47:39 +02:00
ganfra
001603cf9a Create direct room: add filtering and enhance design a bit 2019-07-18 17:42:22 +02:00
ganfra
4341b0d0f5 Merge branch 'develop' into feature/create_direct_room 2019-07-18 09:47:25 +02:00
ganfra
838003b68a Create direct room: start creating all the required stuff 2019-07-17 18:30:14 +02:00
586 changed files with 16710 additions and 5319 deletions

View File

@@ -1,6 +1,7 @@
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
# Last docker plugin version can be found here:
# 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
@@ -18,6 +19,7 @@ steps:
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Assemble FDroid Debug version"
agents:
@@ -32,6 +34,7 @@ steps:
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Build Google Play unsigned APK"
agents:
@@ -46,6 +49,7 @@ steps:
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
# Code quality

View File

@@ -1,3 +1,105 @@
Changes in RiotX 0.6.1 (2019-09-24)
===================================================
Bugfix:
- Fix crash: MergedHeaderItem was missing dimensionConverter
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)
===================================================
@@ -36,7 +138,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
=======================================================
Changes in RiotX 0.XX (2019-XX-XX)
Changes in RiotX 0.0.0 (2019-XX-XX)
===================================================
Features:

View File

@@ -33,11 +33,12 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'

View File

@@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import io.reactivex.Observable
import io.reactivex.android.MainThreadDisposable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
private class LiveDataObservable<T>(
private val liveData: LiveData<T>,
@@ -57,5 +59,5 @@ private class LiveDataObservable<T>(
}
fun <T> LiveData<T>.asObservable(): Observable<T> {
return LiveDataObservable(this)
return LiveDataObservable(this).observeOn(Schedulers.computation())
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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.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.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import io.reactivex.Single
class RxRoom(private val room: Room) {
fun liveRoomSummary(): Observable<RoomSummary> {
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
return room.liveRoomSummary().asObservable()
}
fun liveRoomMemberIds(): Observable<List<String>> {
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation())
return room.getRoomMemberIdsLive().asObservable()
}
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> {
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()
}
}

View File

@@ -16,30 +16,55 @@
package im.vector.matrix.rx
import androidx.paging.PagedList
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.pushers.Pusher
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.user.model.User
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import io.reactivex.Single
class RxSession(private val session: Session) {
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
return session.liveRoomSummaries().asObservable()
}
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation())
return session.liveGroupSummaries().asObservable()
}
fun liveSyncState(): Observable<SyncState> {
return session.syncState().asObservable().observeOn(Schedulers.computation())
return session.syncState().asObservable()
}
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)
}
}

View File

@@ -94,7 +94,6 @@ dependencies {
def markwon_version = '3.0.0'
def daggerVersion = '2.23.1'
implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$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.okhttp3:okhttp:3.14.1'
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"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
@@ -126,9 +125,6 @@ dependencies {
// FP
implementation "io.arrow-kt:arrow-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
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.facebook.stetho:stetho-okhttp3:1.5.0'
// Bus
implementation 'org.greenrobot:eventbus:3.1.1'
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0'

View File

@@ -16,10 +16,10 @@
package im.vector.matrix.android;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;

View File

@@ -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.RealmCryptoStoreModule
import io.realm.RealmConfiguration
import java.util.*
import kotlin.random.Random
internal class CryptoStoreHelper {
@@ -35,7 +35,7 @@ internal class CryptoStoreHelper {
}
fun createCredential() = Credentials(
userId = "userId_" + Random().nextInt(),
userId = "userId_" + Random.nextInt(),
homeServer = "http://matrix.org",
accessToken = "access_token",
refreshToken = null,

View File

@@ -19,11 +19,7 @@ package im.vector.matrix.android.session.room.timeline
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.internal.database.helper.add
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.helper.*
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents

View File

@@ -16,7 +16,6 @@
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.PaginationDirection
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 {
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 tokenChunkEvent = FakeTokenChunkEvent(
Random.nextLong(System.currentTimeMillis()).toString(),

View File

@@ -16,7 +16,6 @@
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.TokenChunkEventPersistor
import javax.inject.Inject
@@ -24,7 +23,7 @@ import kotlin.random.Random
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 tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)

View File

@@ -28,7 +28,7 @@ object MatrixPatterns {
// regex pattern to find matrix user ids in a string.
// 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 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.
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
@@ -123,9 +123,9 @@ object MatrixPatterns {
*/
fun isEventId(str: String?): Boolean {
return str != null
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
}
/**
@@ -137,4 +137,23 @@ object MatrixPatterns {
fun isGroupId(str: String?): Boolean {
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)
}
}

View File

@@ -17,16 +17,23 @@
package im.vector.matrix.android.api.auth
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.SessionParams
import im.vector.matrix.android.api.session.Session
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.
*/
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 login the login field
@@ -56,4 +63,9 @@ interface Authenticator {
* @return the associated session if any, or null
*/
fun getSession(sessionParams: SessionParams): Session?
/**
* Create a session after a SSO successful login
*/
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
}

View File

@@ -31,7 +31,7 @@ import okhttp3.TlsVersion
@JsonClass(generateAdapter = true)
data class HomeServerConnectionConfig(
val homeServerUri: Uri,
val identityServerUri: Uri,
val identityServerUri: Uri? = null,
val antiVirusServerUri: Uri? = null,
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(),
val shouldPin: Boolean = false,
@@ -48,7 +48,7 @@ data class HomeServerConnectionConfig(
class Builder {
private lateinit var homeServerUri: Uri
private lateinit var identityServerUri: Uri
private var identityServerUri: Uri? = null
private var antiVirusServerUri: Uri? = null
private val allowedFingerprints: MutableList<Fingerprint> = ArrayList()
private var shouldPin: Boolean = false

View File

@@ -17,7 +17,6 @@
package im.vector.matrix.android.api.comparators
import im.vector.matrix.android.api.interfaces.DatedObject
import java.util.*
object DatedObjectComparators {

View File

@@ -19,7 +19,7 @@ package im.vector.matrix.android.api.extensions
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.rest.DeviceInfo
import java.util.*
import java.util.Collections
/* ==========================================================================================
* MXDeviceInfo

View File

@@ -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
)

View File

@@ -31,6 +31,7 @@ import java.io.IOException
*/
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
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 ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
// When server send an error, but it cannot be interpreted as a MatrixError

View File

@@ -26,8 +26,13 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class MatrixError(
@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 {
const val FORBIDDEN = "M_FORBIDDEN"
@@ -55,5 +60,8 @@ data class MatrixError(
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
// Possible value for "limit_type"
const val LIMIT_TYPE_MAU = "monthly_active_user"
}
}

View File

@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.permalinks
import android.text.style.ClickableSpan
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.

View File

@@ -19,77 +19,92 @@ import im.vector.matrix.android.api.pushrules.rest.PushRule
import timber.log.Timber
class Action(val type: Type) {
enum class Type(val value: String) {
NOTIFY("notify"),
DONT_NOTIFY("dont_notify"),
COALESCE("coalesce"),
SET_TWEAK("set_tweak");
companion object {
fun safeValueOf(value: String): Type? {
try {
return valueOf(value)
} catch (e: IllegalArgumentException) {
return null
}
}
}
}
var tweak_action: String? = null
var stringValue: String? = null
var boolValue: Boolean? = null
companion object {
fun mapFrom(pushRule: PushRule): List<Action>? {
val actions = ArrayList<Action>()
pushRule.actions.forEach { actionStrOrObj ->
if (actionStrOrObj is String) {
when (actionStrOrObj) {
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
else -> {
Timber.w("Unsupported action type ${actionStrOrObj}")
null
}
}?.let {
actions.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
}
}
sealed class Action {
object Notify : Action()
object DoNotNotify : Action()
data class Sound(val sound: String) : Action()
data class Highlight(val highlight: Boolean) : Action()
}
private const val ACTION_NOTIFY = "notify"
private const val ACTION_DONT_NOTIFY = "dont_notify"
private const val ACTION_COALESCE = "coalesce"
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
private const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
private const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
private const val ACTION_OBJECT_VALUE_KEY = "value"
private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
/**
* 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) {
ACTION_NOTIFY -> Action.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 -> {
Timber.w("Unsupported set_tweak value ${actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]}")
null
}
}
}
else -> {
Timber.w("Unsupported action type $actionStrOrObj")
null
}
}?.let {
result.add(it)
}
}
return result
}

View File

@@ -18,20 +18,21 @@ package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.MatrixCallback
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.util.Cancelable
interface PushRuleService {
/**
* Fetch the push rules from the server
*/
fun fetchPushRules(scope: String = "global")
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
//TODO get push rule set
fun getPushRules(scope: String = "global"): List<PushRule>
fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
//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)
@@ -41,6 +42,8 @@ interface PushRuleService {
interface PushRuleListener {
fun onMatchRule(event: Event, actions: List<Action>)
fun onRoomLeft(roomId: String)
fun onEventRedacted(redactedEventId: String)
fun batchFinish()
}
}

View File

@@ -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.room.RoomService
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 {
return conditionResolver.resolveRoomMemberCountCondition(this)
}
override fun technicalDescription(): String {
return "Room member count is $`is`"
return "Room member count is $iz"
}
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>? {
try {
val match = regex.matcher(`is`)
if (match.find()) {
val prefix = match.group(1)
val count = match.group(2).toInt()
return prefix to count
}
val match = regex.find(iz) ?: return null
val (prefix, count) = match.destructured
return prefix to count.toInt()
} catch (t: Throwable) {
Timber.d(t)
}

View File

@@ -37,7 +37,7 @@ object RuleIds {
// Default Underride Rules
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_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"

View File

@@ -15,12 +15,6 @@
*/
package im.vector.matrix.android.api.pushrules
enum class RulesetKey(val value: String) {
CONTENT("content"),
OVERRIDE("override"),
ROOM("room"),
SENDER("sender"),
UNDERRIDE("underride"),
UNKNOWN("")
}
object RuleScope {
const val GLOBAL = "global"
}

View File

@@ -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

View File

@@ -71,7 +71,7 @@ data class PushCondition(
this.key?.let { SenderNotificationPermissionCondition(it) }
}
Condition.Kind.UNRECOGNIZE -> {
Timber.e("Unknwon kind $kind")
Timber.e("Unknown kind $kind")
null
}
}

View File

@@ -20,10 +20,10 @@ import androidx.lifecycle.LiveData
interface InitialSyncProgressService {
fun getLiveStatus() : LiveData<Status?>
fun getInitialSyncProgressStatus() : LiveData<Status?>
data class Status(
@StringRes val statusText: Int?,
@StringRes val statusText: Int,
val percentProgress: Int = 0
)
}

View File

@@ -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.room.RoomDirectoryService
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.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState
@@ -50,7 +51,8 @@ interface Session :
FileService,
PushRuleService,
PushersService,
InitialSyncProgressService {
InitialSyncProgressService,
SecureStorageService {
/**
* The params associated to the session
@@ -87,7 +89,7 @@ interface Session :
/**
* This method start the sync thread.
*/
fun startSync(fromForeground : Boolean)
fun startSync(fromForeground: Boolean)
/**
* This method stop the sync thread.

View File

@@ -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.internal.crypto.MXEventDecryptionResult
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.MXDeviceInfo
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.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import java.io.File
interface CryptoService {
@@ -111,8 +109,6 @@ interface CryptoService {
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>)
fun clearCryptoCache(callback: MatrixCallback<Unit>)
fun addNewSessionListener(newSessionListener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener)

View File

@@ -20,6 +20,9 @@ import android.text.TextUtils
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
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.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.di.MoshiProvider
@@ -79,9 +82,15 @@ data class Event(
) {
@Transient
var mxDecryptionResult: OlmDecryptionResult? = null
@Transient
var mCryptoError: MXCryptoError.ErrorType? = null
@Transient
var sendState: SendState = SendState.UNKNOWN
/**
* Check if event is a state event.
@@ -95,42 +104,6 @@ data class Event(
// 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.
*/
@@ -138,51 +111,11 @@ data class Event(
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.
*/
fun getSenderKey(): String? {
return mxDecryptionResult?.senderKey
// return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
}
/**
@@ -190,23 +123,13 @@ data class Event(
*/
fun getKeysClaimed(): Map<String, String> {
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
*/
fun getClearType(): String {
return mxDecryptionResult?.payload?.get("type")?.toString()
?: type//get("type")?.toString() ?: type
return mxDecryptionResult?.payload?.get("type")?.toString() ?: type
}
/**
@@ -216,30 +139,8 @@ data class Event(
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 {
val contentMap = this.toContent()?.toMutableMap() ?: HashMap()
contentMap.remove("mxDecryptionResult")
contentMap.remove("mCryptoError")
val contentMap = toContent()?.toMutableMap() ?: HashMap()
return JSONObject(contentMap).toString(4)
}
@@ -272,6 +173,7 @@ data class Event(
if (redacts != other.redacts) return false
if (mxDecryptionResult != other.mxDecryptionResult) return false
if (mCryptoError != other.mCryptoError) return false
if (sendState != other.sendState) return false
return true
}
@@ -289,6 +191,27 @@ data class Event(
result = 31 * result + (redacts?.hashCode() ?: 0)
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
result = 31 * result + sendState.hashCode()
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
}
}

View File

@@ -16,12 +16,15 @@
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.
* It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService]
*/
data class GroupSummary(
val groupId: String,
val membership: Membership,
val displayName: String = "",
val shortDescription: String = "",
val avatarUrl: String = "",

View File

@@ -16,9 +16,6 @@
package im.vector.matrix.android.api.session.pushers
data class Pusher(
val userId: String,
val pushKey: String,
val kind: String,
val appId: String,

View File

@@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.pushers
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import java.util.*
import java.util.UUID
interface PushersService {

View File

@@ -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.relation.RelationService
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.state.StateService
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 :
TimelineService,
SendService,
DraftService,
ReadService,
MembershipService,
StateService,

View File

@@ -30,20 +30,17 @@ interface RoomDirectoryService {
/**
* Get rooms from directory
*/
fun getPublicRooms(server: String?,
publicRoomsParams: PublicRoomsParams,
callback: MatrixCallback<PublicRoomsResponse>): Cancelable
fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback<PublicRoomsResponse>): Cancelable
/**
* Join a room by id
*/
fun joinRoom(roomId: String,
callback: MatrixCallback<Unit>)
fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Fetches the overall metadata about protocols supported by the homeserver.
* 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
}

View File

@@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
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.create.CreateRoomParams
import im.vector.matrix.android.api.util.Cancelable
/**
* 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 {
/**
* Create a room
* Create a room asynchronously
*/
fun createRoom(createRoomParams: CreateRoomParams,
callback: MatrixCallback<String>)
fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable
/**
* 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

View File

@@ -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 CreateRoomFailure : Failure.FeatureFailure() {
object CreatedWithTimeout: CreateRoomFailure()
}

View File

@@ -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()
}

View File

@@ -30,7 +30,7 @@ interface MembershipService {
* This methods load all room members if it was done yet.
* @return a [Cancelable]
*/
fun loadRoomMembersIfNeeded(): Cancelable
fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable
/**
* Return the roomMember with userId or null.
@@ -52,16 +52,17 @@ interface MembershipService {
/**
* 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.
*/
fun join(callback: MatrixCallback<Unit>)
fun join(viaServers: List<String> = emptyList(), callback: MatrixCallback<Unit>): Cancelable
/**
* Leave the room, or reject an invitation.
*/
fun leave(callback: MatrixCallback<Unit>)
fun leave(callback: MatrixCallback<Unit>): Cancelable
}

View File

@@ -16,8 +16,9 @@
package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.session.user.model.User
data class ReadReceipt(
val userId: String,
val eventId: String,
val user: User,
val originServerTs: Long
)

View File

@@ -17,6 +17,7 @@
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.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
/**
@@ -29,10 +30,17 @@ data class RoomSummary(
val topic: String = "",
val avatarUrl: String = "",
val isDirect: Boolean = false,
val latestEvent: TimelineEvent? = null,
val latestPreviewableEvent: TimelineEvent? = null,
val otherMemberIds: List<String> = emptyList(),
val notificationCount: Int = 0,
val highlightCount: Int = 0,
val hasUnreadMessages: Boolean = false,
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
}

View File

@@ -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
}

View File

@@ -20,7 +20,6 @@ import android.util.Patterns
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
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.session.events.model.Event
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.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import java.util.*
/**
* Parameter to create a room, with facilities functions to configure it
@@ -128,12 +126,12 @@ class CreateRoomParams {
contentMap["algorithm"] = algorithm
val algoEvent = Event(type = EventType.ENCRYPTION,
stateKey = "",
content = contentMap.toContent()
stateKey = "",
content = contentMap.toContent()
)
if (null == initialStates) {
initialStates = Arrays.asList<Event>(algoEvent)
initialStates = mutableListOf(algoEvent)
} else {
initialStates!!.add(algoEvent)
}
@@ -162,11 +160,11 @@ class CreateRoomParams {
contentMap["history_visibility"] = historyVisibility
val historyVisibilityEvent = Event(type = EventType.STATE_HISTORY_VISIBILITY,
stateKey = "",
content = contentMap.toContent())
stateKey = "",
content = contentMap.toContent())
if (null == initialStates) {
initialStates = Arrays.asList<Event>(historyVisibilityEvent)
initialStates = mutableListOf(historyVisibilityEvent)
} else {
initialStates!!.add(historyVisibilityEvent)
}
@@ -202,8 +200,8 @@ class CreateRoomParams {
*/
fun isDirect(): Boolean {
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
&& isDirect == true
&& (1 == getInviteCount() || 1 == getInvite3PidCount())
&& isDirect == true
&& (1 == getInviteCount() || 1 == getInvite3PidCount())
}
/**
@@ -220,22 +218,21 @@ class CreateRoomParams {
* @param ids the participant ids to add.
*/
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
credentials: Credentials,
userId: String,
ids: List<String>) {
for (id in ids) {
if (Patterns.EMAIL_ADDRESS.matcher(id).matches()) {
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
if (null == invite3pids) {
invite3pids = ArrayList()
}
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
medium = ThreePidMedium.EMAIL,
address = id)
medium = ThreePidMedium.EMAIL,
address = id)
invite3pids!!.add(pid)
} else if (isUserId(id)) {
// do not invite oneself
if (credentials.userId != id) {
if (userId != id) {
if (null == invitedUserIds) {
invitedUserIds = ArrayList()
}

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -29,5 +29,5 @@ interface MessageContent {
fun MessageContent?.isReply(): Boolean {
return this?.relatesTo?.inReplyTo != null
}
return this?.relatesTo?.inReplyTo?.eventId != null
}

View File

@@ -21,5 +21,5 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ReplyToContent(
@Json(name = "event_id") val eventId: String
)
@Json(name = "event_id") val eventId: String? = null
)

View File

@@ -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?
)

View File

@@ -16,7 +16,9 @@
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.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.
@@ -39,4 +41,6 @@ interface ReadService {
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
fun isEventRead(eventId: String): Boolean
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
}

View File

@@ -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>>
}

View File

@@ -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.events.model.Event
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
@@ -65,4 +66,31 @@ interface SendService {
*/
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()
}

View File

@@ -16,6 +16,7 @@
package im.vector.matrix.android.api.session.room.send
enum class SendState {
UNKNOWN,
// 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
FAILED_UNKNOWN_DEVICES;
fun isSent(): Boolean {
return this == SENT || this == SYNCED
internal companion object {
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 {
return this == UNDELIVERED || this == FAILED_UNKNOWN_DEVICES
}
fun isSent() = IS_SENT_STATES.contains(this)
fun hasFailed() = HAS_FAILED_STATES.contains(this)
fun isSending() = IS_SENDING_STATES.contains(this)
}

View File

@@ -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
}
}
}

View File

@@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.room.state
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
interface StateService {
@@ -25,4 +26,6 @@ interface StateService {
*/
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
fun getStateEvent(eventType: String): Event?
}

View File

@@ -56,6 +56,9 @@ interface Timeline {
*/
fun paginate(direction: Direction, count: Int)
fun pendingEventCount() : Int
fun failedToDeliverEventCount() : Int
interface Listener {
/**

View File

@@ -18,11 +18,12 @@ 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.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.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.isReply
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
@@ -38,8 +39,8 @@ data class TimelineEvent(
val senderName: String?,
val isUniqueDisplayName: Boolean,
val senderAvatar: String?,
val sendState: SendState,
val annotations: EventAnnotationsSummary? = null
val annotations: EventAnnotationsSummary? = null,
val readReceipts: List<ReadReceipt> = emptyList()
) {
val metadata = HashMap<String, Any>()
@@ -67,8 +68,8 @@ data class TimelineEvent(
"$name (${root.senderId})"
}
}
?: root.senderId
?: ""
?: root.senderId
?: ""
}
/**
@@ -86,11 +87,38 @@ 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
*/
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? {
@@ -102,4 +130,4 @@ fun TimelineEvent.getTextEditableContent(): String? {
} else {
lastContent?.body ?: ""
}
}
}

View File

@@ -25,12 +25,12 @@ interface TimelineService {
/**
* 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 allowedTypes the optional filter types
* @param settings settings to configure the 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?

View File

@@ -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
)

View File

@@ -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?
}

View File

@@ -22,4 +22,5 @@ sealed class SyncState {
object PAUSED : SyncState()
object KILLING : SyncState()
object KILLED : SyncState()
object NO_NETWORK : SyncState()
}

View File

@@ -17,7 +17,10 @@
package im.vector.matrix.android.api.session.user
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.util.Cancelable
/**
* 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?
/**
* 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
* @param userId the userId to look for.
* @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>>
}

View File

@@ -19,5 +19,6 @@ package im.vector.matrix.android.api.util
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() {
override fun cancel() {
forEach { it.cancel() }
clear()
}
}

View File

@@ -17,10 +17,12 @@
package im.vector.matrix.android.internal.auth
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.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
@@ -29,6 +31,13 @@ import retrofit2.http.POST
*/
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.
* Set all the timeouts to 1 minute

View File

@@ -23,7 +23,7 @@ import dagger.Provides
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.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 io.realm.RealmConfiguration
import java.io.File
@@ -33,16 +33,21 @@ internal abstract class AuthModule {
@Module
companion object {
private const val DB_ALIAS = "matrix-sdk-auth"
@JvmStatic
@Provides
@AuthDatabase
fun providesRealmConfiguration(context: Context): RealmConfiguration {
fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
val old = File(context.filesDir, "matrix-sdk-auth")
if (old.exists()) {
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
}
return RealmConfiguration.Builder()
.configureEncryption("matrix-sdk-auth", context)
.apply {
realmKeysUtils.configureEncryption(this, DB_ALIAS)
}
.name("matrix-sdk-auth.realm")
.modules(AuthRealmModule())
.deleteRealmIfMigrationNeeded()

View File

@@ -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.util.Cancelable
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.ThreePidMedium
import im.vector.matrix.android.internal.di.Unauthenticated
@@ -62,17 +63,35 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
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,
login: String,
password: String,
callback: MatrixCallback<Session>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password)
val sessionOrFailure = runCatching {
authenticate(homeServerConnectionConfig, login, password)
}
sessionOrFailure.foldToCallback(callback)
}
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,
@@ -85,16 +104,18 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
} else {
PasswordLoginParams.userIdentifier(login, password, "Mobile")
}
executeRequest<Credentials> {
val credentials = executeRequest<Credentials> {
apiCall = authAPI.login(loginParams)
}.map {
val sessionParams = SessionParams(it, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
sessionParams
}.map {
sessionManager.getOrCreateSession(it)
}
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
sessionManager.getOrCreateSession(sessionParams)
}
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 {

View File

@@ -27,7 +27,7 @@ internal interface SessionParamsStore {
fun getAll(): List<SessionParams>
fun save(sessionParams: SessionParams): Try<SessionParams>
fun save(sessionParams: SessionParams): Try<Unit>
fun delete(userId: String): Try<Unit>

View File

@@ -30,4 +30,12 @@ data class InteractiveAuthenticationFlow(
@Json(name = "stages")
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"
}
}

View File

@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class LoginFlowResponse(
data class LoginFlowResponse(
@Json(name = "flows")
val flows: List<InteractiveAuthenticationFlow>
)

View File

@@ -62,7 +62,7 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
return sessionParams
}
override fun save(sessionParams: SessionParams): Try<SessionParams> {
override fun save(sessionParams: SessionParams): Try<Unit> {
return Try {
val entity = mapper.map(sessionParams)
if (entity != null) {
@@ -72,7 +72,6 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
}
realm.close()
}
sessionParams
}
}

View File

@@ -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.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.di.MatrixScope
import javax.inject.Inject
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {

View File

@@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto
import android.content.Context
import dagger.Binds
import dagger.Module
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.RealmCryptoStoreModule
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.UserCacheDirectory
import im.vector.matrix.android.internal.di.UserMd5
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.RealmClearCacheTask
import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration
import retrofit2.Retrofit
import java.io.File
@@ -45,17 +45,20 @@ internal abstract class CryptoModule {
@Module
companion object {
internal const val DB_ALIAS_PREFIX = "crypto_module_"
@JvmStatic
@Provides
@CryptoDatabase
@SessionScope
fun providesRealmConfiguration(context: Context, credentials: Credentials): RealmConfiguration {
val userIDHash = credentials.userId.md5()
fun providesRealmConfiguration(@UserCacheDirectory directory: File,
@UserMd5 userMd5: String,
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(File(context.filesDir, userIDHash))
.configureEncryption("crypto_module_$userIDHash", context)
.directory(directory)
.apply {
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
}
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
@@ -105,7 +108,7 @@ internal abstract class CryptoModule {
}
@Binds
abstract fun bindCryptoService(cryptoManager: CryptoManager): CryptoService
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
@Binds
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask

View File

@@ -62,17 +62,14 @@ import im.vector.matrix.android.internal.crypto.tasks.*
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.query.where
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback
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.RoomMembers
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.configureWith
import im.vector.matrix.android.internal.task.toConfigurableTask
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.fetchCopied
@@ -94,7 +91,7 @@ import kotlin.math.max
* Specially, it tracks all room membership changes events in order to do keys updates.
*/
@SessionScope
internal class CryptoManager @Inject constructor(
internal class DefaultCryptoService @Inject constructor(
// Olm Manager
private val olmManager: OlmManager,
// The credentials,
@@ -136,7 +133,6 @@ internal class CryptoManager @Inject constructor(
private val setDeviceNameTask: SetDeviceNameTask,
private val uploadKeysTask: UploadKeysTask,
private val loadRoomMembersTask: LoadRoomMembersTask,
@CryptoDatabase private val clearCryptoDataTask: ClearCacheTask,
private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor
@@ -167,22 +163,25 @@ internal class CryptoManager @Inject constructor(
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
setDeviceNameTask
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName))
.dispatchTo(callback)
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
deleteDeviceTask
.configureWith(DeleteDeviceTask.Params(deviceId))
.dispatchTo(callback)
.configureWith(DeleteDeviceTask.Params(deviceId)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
deleteDeviceWithUserPasswordTask
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password))
.dispatchTo(callback)
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
@@ -196,8 +195,9 @@ internal class CryptoManager @Inject constructor(
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
getDevicesTask
.toConfigurableTask()
.dispatchTo(callback)
.configureWith {
this.callback = callback
}
.executeBy(taskExecutor)
}
@@ -254,35 +254,36 @@ internal class CryptoManager @Inject constructor(
private suspend fun internalStart(isInitialSync: Boolean) {
// Open the store
cryptoStore.open()
uploadDeviceKeys()
.flatMap { oneTimeKeysUploader.maybeUploadOneTimeKeys() }
.fold(
{
Timber.e("Start failed: $it")
delay(1000)
isStarting.set(false)
internalStart(isInitialSync)
},
{
outgoingRoomKeyRequestManager.start()
keysBackup.checkAndStartKeysBackup()
if (isInitialSync) {
// refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists()
deviceListManager.refreshOutdatedDeviceLists()
} else {
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
isStarting.set(false)
isStarted.set(true)
}
)
runCatching {
uploadDeviceKeys()
oneTimeKeysUploader.maybeUploadOneTimeKeys()
outgoingRoomKeyRequestManager.start()
keysBackup.checkAndStartKeysBackup()
if (isInitialSync) {
// refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists()
deviceListManager.refreshOutdatedDeviceLists()
} else {
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
}.fold(
{
isStarting.set(false)
isStarted.set(true)
},
{
Timber.e("Start failed: $it")
delay(1000)
isStarting.set(false)
internalStart(isInitialSync)
}
)
}
/**
* Close the crypto
*/
fun close() {
fun close() = runBlocking(coroutineDispatchers.crypto) {
olmDevice.release()
cryptoStore.close()
outgoingRoomKeyRequestManager.stop()
@@ -556,13 +557,16 @@ internal class CryptoManager @Inject constructor(
if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent() starts")
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
runCatching {
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
}
.fold(
{ callback.onFailure(it) },
{
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
}
},
{ callback.onFailure(it) }
)
} else {
val algorithm = getEncryptionAlgorithm(roomId)
@@ -584,10 +588,7 @@ internal class CryptoManager @Inject constructor(
@Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return runBlocking {
internalDecryptEvent(event, timeline).fold(
{ throw it },
{ it }
)
internalDecryptEvent(event, timeline)
}
}
@@ -600,8 +601,10 @@ internal class CryptoManager @Inject constructor(
*/
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
GlobalScope.launch {
val result = withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline)
val result = runCatching {
withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline)
}
}
result.foldToCallback(callback)
}
@@ -612,22 +615,22 @@ internal class CryptoManager @Inject constructor(
*
* @param event the raw event.
* @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
return if (eventContent == null) {
if (eventContent == null) {
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 {
val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
Timber.e("## decryptEvent() : $reason")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason))
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
} else {
alg.decryptEvent(event, timeline)
return alg.decryptEvent(event, timeline)
}
}
}
@@ -689,12 +692,13 @@ internal class CryptoManager @Inject constructor(
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) {
val params = LoadRoomMembersTask.Params(roomId)
loadRoomMembersTask
.execute(params)
.map { _ ->
val userIds = getRoomUserIds(roomId)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
}
try {
loadRoomMembersTask.execute(params)
val userIds = getRoomUserIds(roomId)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
} catch (throwable: Throwable) {
Timber.e(throwable)
}
}
}
@@ -761,7 +765,7 @@ internal class CryptoManager @Inject constructor(
/**
* Upload my user's device keys.
*/
private suspend fun uploadDeviceKeys(): Try<KeysUploadResponse> {
private suspend fun uploadDeviceKeys(): KeysUploadResponse {
// Prepare the device keys data to send
// Sign it
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
@@ -868,10 +872,8 @@ internal class CryptoManager @Inject constructor(
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
// force the refresh to ensure that the devices list is up-to-date
GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager
.downloadKeys(userIds, true)
runCatching { deviceListManager.downloadKeys(userIds, true) }
.fold(
{ callback.onFailure(it) },
{
val unknownDevices = getUnknownDevices(it)
if (unknownDevices.map.isEmpty()) {
@@ -880,7 +882,8 @@ internal class CryptoManager @Inject constructor(
// trigger an an unknown devices exception
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
}
}
},
{ callback.onFailure(it) }
)
}
}
@@ -1035,19 +1038,12 @@ internal class CryptoManager @Inject constructor(
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager
.downloadKeys(userIds, forceDownload)
.foldToCallback(callback)
runCatching {
deviceListManager.downloadKeys(userIds, forceDownload)
}.foldToCallback(callback)
}
}
override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
clearCryptoDataTask
.toConfigurableTask()
.dispatchTo(callback)
.executeBy(taskExecutor)
}
override fun addNewSessionListener(newSessionListener: NewSessionListener) {
roomDecryptorProvider.addNewSessionListener(newSessionListener)
}
@@ -1060,6 +1056,6 @@ internal class CryptoManager @Inject constructor(
* ========================================================================================== */
override fun toString(): String {
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"
return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")"
}
}

View File

@@ -18,14 +18,12 @@
package im.vector.matrix.android.internal.crypto
import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixPatterns
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.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
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.sync.SyncTokenStore
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 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")
// Map from userId -> deviceId -> MXDeviceInfo
val stored = MXUsersDevicesMap<MXDeviceInfo>()
@@ -268,16 +266,15 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
return if (downloadUsers.isEmpty()) {
Timber.v("## downloadKeys() : no new user device")
Try.just(stored)
stored
} else {
Timber.v("## downloadKeys() : starts")
val t0 = System.currentTimeMillis()
doKeyDownloadForUsers(downloadUsers)
.map {
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
it.addEntriesFromMap(stored)
it
}
val result = doKeyDownloadForUsers(downloadUsers)
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
result.also {
it.addEntriesFromMap(stored)
}
}
}
@@ -286,60 +283,60 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
*
* @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")
// get the user ids which did not already trigger a keys download
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
if (filteredUsers.isEmpty()) {
// trigger nothing
return Try.just(MXUsersDevicesMap())
return MXUsersDevicesMap()
}
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
return downloadKeysForUsersTask.execute(params)
.map { response ->
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
for (userId in filteredUsers) {
val devices = response.deviceKeys?.get(userId)
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
if (devices != null) {
val mutableDevices = HashMap(devices)
val deviceIds = ArrayList(mutableDevices.keys)
for (deviceId in deviceIds) {
// Get the potential previously store device keys for this device
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
val deviceInfo = mutableDevices[deviceId]
val response = try {
downloadKeysForUsersTask.execute(params)
} catch (throwable: Throwable) {
Timber.e(throwable, "##doKeyDownloadForUsers(): error")
onKeysDownloadFailed(filteredUsers)
throw throwable
}
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
for (userId in filteredUsers) {
val devices = response.deviceKeys?.get(userId)
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
if (devices != null) {
val mutableDevices = HashMap(devices)
val deviceIds = ArrayList(mutableDevices.keys)
for (deviceId in deviceIds) {
// Get the potential previously store device keys for this device
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
val deviceInfo = mutableDevices[deviceId]
// in some race conditions (like unit tests)
// the self device must be seen as verified
if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) {
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
}
// Validate received keys
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
// New device keys are not valid. Do not store them
mutableDevices.remove(deviceId)
if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys
}
} else if (null != previouslyStoredDeviceKeys) {
// The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client.
// So, transfer its previous value
mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
}
}
// Update the store
// Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices)
}
// in some race conditions (like unit tests)
// the self device must be seen as verified
if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) {
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
}
// Validate received keys
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
// New device keys are not valid. Do not store them
mutableDevices.remove(deviceId)
if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys
}
} else if (null != previouslyStoredDeviceKeys) {
// The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client.
// So, transfer its previous value
mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
}
onKeysDownloadSucceed(filteredUsers, response.failures)
}
.onError {
Timber.e(it, "##doKeyDownloadForUsers(): error")
onKeysDownloadFailed(filteredUsers)
}
// Update the store
// Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices)
}
}
return onKeysDownloadSucceed(filteredUsers, response.failures)
}
/**
@@ -465,15 +462,16 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
doKeyDownloadForUsers(users)
.fold(
{
Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
},
{
Timber.v("## refreshOutdatedDeviceLists() : done")
}
)
runCatching {
doKeyDownloadForUsers(users)
}.fold(
{
Timber.v("## refreshOutdatedDeviceLists() : done")
},
{
Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
}
)
}
companion object {

View File

@@ -18,7 +18,6 @@
package im.vector.matrix.android.internal.crypto
import android.text.TextUtils
import arrow.core.Try
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.JsonDict
@@ -80,8 +79,7 @@ internal class MXOlmDevice @Inject constructor(
//
// The first level keys are timeline ids.
// The second level keys are strings of form "<senderKey>|<session_id>|<message_index>"
// Values are true.
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableSet<String>> = HashMap()
init {
// Retrieve the account from the store
@@ -506,25 +504,25 @@ internal class MXOlmDevice @Inject constructor(
keysClaimed: Map<String, String>,
exportFormat: Boolean): Boolean {
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
.fold(
{
// If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
getInboundGroupSession(sessionId, senderKey, roomId).fold(
{
// Nothing to do in case of error
},
{
// If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
val existingFirstKnown = it.firstKnownIndex!!
val newKnownFirstIndex = session.firstKnownIndex
val existingFirstKnown = it.firstKnownIndex!!
val newKnownFirstIndex = session.firstKnownIndex
//If our existing session is better we keep it
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
session.olmInboundGroupSession?.releaseSession()
return false
}
}
)
//If our existing session is better we keep it
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
session.olmInboundGroupSession?.releaseSession()
return false
}
},
{
// Nothing to do in case of error
}
)
// sanity check
if (null == session.olmInboundGroupSession) {
@@ -595,12 +593,8 @@ internal class MXOlmDevice @Inject constructor(
continue
}
getInboundGroupSession(sessionId, senderKey, roomId)
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
.fold(
{
// Session does not already exist, add it
sessions.add(session)
},
{
// If we already have this session, consider updating it
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
@@ -613,7 +607,12 @@ internal class MXOlmDevice @Inject constructor(
sessions.add(session)
}
Unit
},
{
// Session does not already exist, add it
sessions.add(session)
}
)
}
@@ -648,61 +647,55 @@ internal class MXOlmDevice @Inject constructor(
roomId: String,
timeline: String?,
sessionId: String,
senderKey: String): Try<OlmDecryptionResult> {
return getInboundGroupSession(sessionId, senderKey, roomId)
.flatMap { session ->
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
if (roomId == session.roomId) {
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
try {
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
} catch (e: OlmException) {
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
return@flatMap Try.Failure(MXCryptoError.OlmError(e))
}
senderKey: String): OlmDecryptionResult {
val session = getInboundGroupSession(sessionId, senderKey, roomId)
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
if (roomId == session.roomId) {
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
try {
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
} catch (e: OlmException) {
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
throw MXCryptoError.OlmError(e)
}
if (null != timeline) {
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
inboundGroupSessionMessageIndexes[timeline] = HashMap()
}
if (null != timeline) {
val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() }
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason))
}
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
}
store.storeInboundGroupSessions(listOf(session))
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
return@flatMap Try.Failure(
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
}
return@flatMap Try.just(
OlmDecryptionResult(
payload,
session.keysClaimed,
senderKey,
session.forwardingCurve25519KeyChain
)
)
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason))
}
if (timelineSet.contains(messageIndexKey)) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
}
timelineSet.add(messageIndexKey)
}
store.storeInboundGroupSessions(listOf(session))
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
throw
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
}
return OlmDecryptionResult(
payload,
session.keysClaimed,
senderKey,
session.forwardingCurve25519KeyChain
)
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $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.
* @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()) {
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)
return if (null != session) {
if (session != null) {
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
if (!TextUtils.equals(roomId, session.roomId)) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
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 {
Try.just(session)
return session
}
} else {
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.
*/
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess()
return runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }.isSuccess
}
}

View File

@@ -17,12 +17,10 @@
package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.session.SessionScope
import java.util.*
import javax.inject.Inject
internal class ObjectSigner @Inject constructor(private val credentials: Credentials,
private val olmDevice: MXOlmDevice) {
private val olmDevice: MXOlmDevice) {
/**
* Sign Object

View File

@@ -16,8 +16,6 @@
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.internal.crypto.model.MXKey
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.
*/
suspend fun maybeUploadOneTimeKeys(): Try<Unit> {
suspend fun maybeUploadOneTimeKeys() {
if (oneTimeKeyCheckInProgress) {
return Try.just(Unit)
return
}
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
// we've done a key upload recently.
return Try.just(Unit)
return
}
lastOneTimeKeyCheck = System.currentTimeMillis()
@@ -81,41 +79,31 @@ internal class OneTimeKeysUploader @Inject constructor(
// discard the oldest private keys first. This will eventually clean
// out stale private keys that won't receive a message.
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
val result = if (oneTimeKeyCount != null) {
if (oneTimeKeyCount != null) {
uploadOTK(oneTimeKeyCount!!, keyLimit)
} else {
// ask the server how many keys we have
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
uploadKeysTask.execute(uploadKeysParams)
.flatMap {
// 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
// a finite number of private keys in the olm Account object.
// To complicate things further then can be a delay between a device
// claiming a public one time key from the server and it sending us a
// message. We need to keep the corresponding private key locally until
// we receive the message.
// But that message might never arrive leaving us stuck with duff
// private keys clogging up our local storage.
// So we need some kind of engineering compromise to balance all of
// these factors.
// 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)
val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
uploadOTK(keyCount, keyLimit)
}
val response = uploadKeysTask.execute(uploadKeysParams)
// 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
// a finite number of private keys in the olm Account object.
// To complicate things further then can be a delay between a device
// claiming a public one time key from the server and it sending us a
// message. We need to keep the corresponding private key locally until
// we receive the message.
// But that message might never arrive leaving us stuck with duff
// private keys clogging up our local storage.
// So we need some kind of engineering compromise to balance all of
// these factors.
// 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)
val keyCount = response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
uploadOTK(keyCount, keyLimit)
}
return result
.map {
Timber.v("## uploadKeys() : success")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
.handleError {
Timber.e(it, "## uploadKeys() : failed")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
Timber.v("## uploadKeys() : success")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
/**
@@ -124,29 +112,26 @@ internal class OneTimeKeysUploader @Inject constructor(
* @param keyCount the key count
* @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 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)
olmDevice.generateOneTimeKeys(keysThisLoop)
return uploadOneTimeKeys()
.flatMap {
if (it.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
uploadOTK(it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
} else {
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"))
}
}
val response = uploadOneTimeKeys()
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
} else {
Timber.e("## uploadLoop() : 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.
*/
private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> {
private suspend fun uploadOneTimeKeys(): KeysUploadResponse {
val oneTimeKeys = olmDevice.getOneTimeKeys()
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
// same one as used in login.
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
return uploadKeysTask
.execute(uploadParams)
.map {
lastPublishedOneTimeKeys = oneTimeKeys
olmDevice.markKeysAsPublished()
it
}
val response = uploadKeysTask.execute(uploadParams)
lastPublishedOneTimeKeys = oneTimeKeys
olmDevice.markKeysAsPublished()
return response
}
companion object {

View File

@@ -299,10 +299,12 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
// TODO Change this two hard coded key to something better
contentMap.setObject(recipient["userId"], recipient["deviceId"], message)
}
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId))
.dispatchTo(callback)
.executeOn(TaskThread.CALLER)
.callbackOn(TaskThread.CALLER)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId)) {
this.callback = callback
this.callbackThread = TaskThread.CALLER
this.executionThread = TaskThread.CALLER
}
.executeBy(taskExecutor)
}

View File

@@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto.actions
import android.text.TextUtils
import arrow.core.Try
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.MXKey
@@ -32,7 +31,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
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 results = MXUsersDevicesMap<MXOlmSessionResult>()
@@ -58,7 +57,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
}
if (devicesWithoutSession.size == 0) {
return Try.just(results)
return results
}
// Prepare the request for claiming one-time keys
@@ -79,39 +78,36 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
return oneTimeKeysForUsersDeviceTask
.execute(claimParams)
.map {
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $it")
for (userId in userIds) {
val deviceInfos = devicesByUser[userId]
for (deviceInfo in deviceInfos!!) {
var oneTimeKey: MXKey? = null
val deviceIds = it.getUserDeviceIds(userId)
if (null != deviceIds) {
for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(userId, deviceId)
if (olmSessionResult!!.sessionId != null) {
// We already have a result for this device
continue
}
val key = it.getObject(userId, deviceId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
if (oneTimeKey == null) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId)
continue
}
// Update the result for this device in results
olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
}
}
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams)
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
for (userId in userIds) {
val deviceInfos = devicesByUser[userId]
for (deviceInfo in deviceInfos!!) {
var oneTimeKey: MXKey? = null
val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
if (null != deviceIds) {
for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(userId, deviceId)
if (olmSessionResult!!.sessionId != null) {
// We already have a result for this device
continue
}
val key = oneTimeKeys.getObject(userId, deviceId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
if (oneTimeKey == null) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId)
continue
}
// Update the result for this device in results
olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
}
results
}
}
}
return results
}
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {

View File

@@ -17,27 +17,24 @@
package im.vector.matrix.android.internal.crypto.actions
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.model.MXDeviceInfo
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.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber
import java.util.*
import javax.inject.Inject
internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val olmDevice: MXOlmDevice,
private val cryptoStore: IMXCryptoStore,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction) {
private val cryptoStore: IMXCryptoStore,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction) {
/**
* Try to make sure we have established olm sessions for the given users.
* @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")
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()

View File

@@ -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.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber
import javax.inject.Inject

View File

@@ -16,16 +16,15 @@
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.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.di.UserId
import timber.log.Timber
import javax.inject.Inject
internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore,
private val credentials: Credentials,
private val keysBackup: KeysBackup) {
@UserId private val userId: String,
private val keysBackup: KeysBackup) {
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
val device = cryptoStore.getUserDevice(deviceId, userId)
@@ -40,7 +39,7 @@ internal class SetDeviceVerificationAction @Inject constructor(private val crypt
device.verified = verificationStatus
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,
// check the key backup status, since whether or not we use this depends on
// whether it has a signature from a verified device

View File

@@ -17,7 +17,6 @@
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.internal.crypto.IncomingRoomKeyRequest
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.
* @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.

View File

@@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto.algorithms
import arrow.core.Try
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 eventType the type of the event.
* @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
}

View File

@@ -18,8 +18,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm
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.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
@@ -40,9 +38,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.collections.HashMap
internal class MXMegolmDecryption(private val credentials: Credentials,
internal class MXMegolmDecryption(private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
@@ -61,30 +58,46 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/
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)
}
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()) {
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>()
?: 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()
|| encryptedEventContent.sessionId.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,
event.roomId,
timeline,
encryptedEventContent.sessionId,
encryptedEventContent.senderKey)
return runCatching {
olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
event.roomId,
timeline,
encryptedEventContent.sessionId,
encryptedEventContent.senderKey)
}
.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 ->
if (throwable is MXCryptoError.OlmError) {
// TODO Check the value of .message
@@ -98,10 +111,10 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
Try.Failure(MXCryptoError.Base(
throw MXCryptoError.Base(
MXCryptoError.ErrorType.OLM,
reason,
detailedReason))
detailedReason)
}
if (throwable is MXCryptoError.Base) {
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
@@ -111,23 +124,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
}
}
}
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))
}
throw throwable
}
)
}
@@ -148,11 +145,11 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val selfMap = HashMap<String, String>()
// TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
selfMap["userId"] = credentials.userId
selfMap["userId"] = userId
selfMap["deviceId"] = "*"
recipients.add(selfMap)
if (!TextUtils.equals(sender, credentials.userId)) {
if (!TextUtils.equals(sender, userId)) {
val senderMap = HashMap<String, String>()
senderMap["userId"] = sender
senderMap["deviceId"] = encryptedEventContent.deviceId!!
@@ -311,51 +308,48 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
}
val userId = request.userId ?: return
GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager
.downloadKeys(listOf(userId), false)
.flatMap {
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
.mapCatching {
val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
if (deviceInfo == null) {
throw RuntimeException()
} else {
val devicesByUser = mapOf(userId to listOf(deviceInfo))
ensureOlmSessionsForDevicesAction
.handle(devicesByUser)
.flatMap {
val body = request.requestBody
val olmSessionResult = it.getObject(userId, deviceId)
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
Try.just(Unit)
}
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
val body = request.requestBody
val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
return@mapCatching
}
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) }
.fold(
{
// TODO
payloadJson["content"] = it.exportKeys()
?: ""
},
{
// TODO
}
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
.fold(
{
// TODO
},
{
// TODO
payloadJson["content"] = it.exportKeys() ?: ""
}
)
)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams)
}
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams)
}
}
}
}
}

View File

@@ -16,7 +16,6 @@
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.MXOlmDevice
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
@@ -24,23 +23,23 @@ 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.store.IMXCryptoStore
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 javax.inject.Inject
internal class MXMegolmDecryptionFactory @Inject constructor(private val credentials: Credentials,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
fun create(): MXMegolmDecryption {
return MXMegolmDecryption(
credentials,
userId,
olmDevice,
deviceListManager,
outgoingRoomKeyRequestManager,

View File

@@ -19,7 +19,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm
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.events.model.Content
@@ -69,12 +68,10 @@ internal class MXMegolmEncryption(
override suspend fun encryptEventContent(eventContent: Content,
eventType: String,
userIds: List<String>): Try<Content> {
return getDevicesInRoom(userIds)
.flatMap { ensureOutboundSession(it) }
.flatMap {
encryptContent(it, eventType, eventContent)
}
userIds: List<String>): Content {
val devices = getDevicesInRoom(userIds)
val outboundSession = ensureOutboundSession(devices)
return encryptContent(outboundSession, eventType, eventContent)
}
/**
@@ -101,7 +98,7 @@ internal class MXMegolmEncryption(
*
* @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
if (session == null
// 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
*/
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
if (devicesByUsers.isEmpty()) {
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)
val subMap = HashMap<String, List<MXDeviceInfo>>()
@@ -157,11 +155,9 @@ internal class MXMegolmEncryption(
}
}
Timber.v("## shareKey() ; userId $userIds")
return shareUserDevicesKey(session, subMap)
.flatMap {
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
shareKey(session, remainingDevices)
}
shareUserDevicesKey(session, subMap)
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
shareKey(session, remainingDevices)
}
/**
@@ -172,7 +168,7 @@ internal class MXMegolmEncryption(
* @param callback the asynchronous callback
*/
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 chainIndex = olmDevice.getMessageIndex(session.sessionId)
@@ -190,94 +186,86 @@ internal class MXMegolmEncryption(
var t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : starts")
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
.flatMap {
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
val contentMap = MXUsersDevicesMap<Any>()
var haveTargets = false
val userIds = it.userIds
for (userId in userIds) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceID) in devicesToShareWith!!) {
val sessionResult = it.getObject(userId, deviceID)
if (sessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
//
// we could send them a to_device message anyway, as a
// signal that they have missed out on the key sharing
// message because of the lack of keys, but there's not
// much point in that really; it will mostly serve to clog
// up to_device inboxes.
//
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
continue
}
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)))
haveTargets = true
}
}
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
sendToDeviceTask.execute(sendToDeviceParams)
.map {
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
// Add the devices we have shared with to session.sharedWithDevices.
// we deliberately iterate over devicesByUser (ie, the devices we
// attempted to share with) rather than the contentMap (those we did
// share with), because we don't want to try to claim a one-time-key
// for dead devices on every message.
for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) {
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
}
}
Unit
}
} else {
Timber.v("## shareUserDevicesKey() : no need to sharekey")
Try.just(Unit)
}
val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
val contentMap = MXUsersDevicesMap<Any>()
var haveTargets = false
val userIds = results.userIds
for (userId in userIds) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceID) in devicesToShareWith!!) {
val sessionResult = results.getObject(userId, deviceID)
if (sessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
//
// we could send them a to_device message anyway, as a
// signal that they have missed out on the key sharing
// message because of the lack of keys, but there's not
// much point in that really; it will mostly serve to clog
// up to_device inboxes.
//
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
continue
}
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)))
haveTargets = true
}
}
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
sendToDeviceTask.execute(sendToDeviceParams)
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
// Add the devices we have shared with to session.sharedWithDevices.
// we deliberately iterate over devicesByUser (ie, the devices we
// attempted to share with) rather than the contentMap (those we did
// share with), because we don't want to try to claim a one-time-key
// for dead devices on every message.
for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) {
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
}
}
} else {
Timber.v("## shareUserDevicesKey() : no need to sharekey")
}
}
/**
* process the pending encryptions
*/
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> {
return Try<Content> {
// Everything is in place, encrypt all pending events
val payloadJson = HashMap<String, Any>()
payloadJson["room_id"] = roomId
payloadJson["type"] = eventType
payloadJson["content"] = eventContent
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Content {
// Everything is in place, encrypt all pending events
val payloadJson = HashMap<String, Any>()
payloadJson["room_id"] = roomId
payloadJson["type"] = eventType
payloadJson["content"] = eventContent
// Get canonical Json from
// Get canonical Json from
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!!
map["session_id"] = session.sessionId
val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!!
map["session_id"] = session.sessionId
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!!
session.useCount++
map
}
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!!
session.useCount++
return map
}
/**
@@ -287,50 +275,47 @@ internal class MXMegolmEncryption(
* @param userIds the user ids whose devices must be checked.
* @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
// 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
// an m.new_device.
return deviceListManager
.downloadKeys(userIds, false)
.flatMap {
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
val keys = deviceListManager.downloadKeys(userIds, false)
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
for (userId in it.userIds) {
val deviceIds = it.getUserDeviceIds(userId) ?: continue
for (deviceId in deviceIds) {
val deviceInfo = it.getObject(userId, deviceId) ?: continue
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
// The device is not yet known by the user
unknownDevices.setObject(userId, deviceId, deviceInfo)
continue
}
if (deviceInfo.isBlocked) {
// Remove any blocked devices
continue
}
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
continue
}
if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
// Don't bother sending to ourself
continue
}
devicesInRoom.setObject(userId, deviceId, deviceInfo)
}
}
if (unknownDevices.isEmpty) {
Try.just(devicesInRoom)
} else {
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
}
for (userId in keys.userIds) {
val deviceIds = keys.getUserDeviceIds(userId) ?: continue
for (deviceId in deviceIds) {
val deviceInfo = keys.getObject(userId, deviceId) ?: continue
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
// The device is not yet known by the user
unknownDevices.setObject(userId, deviceId, deviceInfo)
continue
}
if (deviceInfo.isBlocked) {
// Remove any blocked devices
continue
}
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
continue
}
if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
// Don't bother sending to ourself
continue
}
devicesInRoom.setObject(userId, deviceId, deviceInfo)
}
}
if (unknownDevices.isEmpty) {
return devicesInRoom
} else {
throw MXCryptoError.UnknownDevice(unknownDevices)
}
}
}

View File

@@ -17,8 +17,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm
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.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel
@@ -32,38 +30,36 @@ import im.vector.matrix.android.internal.crypto.model.event.OlmPayloadContent
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.util.convertFromUTF8
import timber.log.Timber
import java.util.*
internal class MXOlmDecryption(
// The olm device interface
private val olmDevice: MXOlmDevice,
// the matrix credentials
private val credentials: Credentials)
// the matrix userId
private val userId: String)
: IMXDecrypting {
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
Timber.e("## decryptEvent() : bad event format")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)
}
val cipherText = olmEventContent.ciphertext ?: run {
Timber.e("## decryptEvent() : missing cipher text")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)
}
val senderKey = olmEventContent.senderKey ?: run {
Timber.e("## decryptEvent() : missing sender key")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON))
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)
}
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS,
MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
throw MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)
}
// The message for myUser
@@ -73,14 +69,12 @@ internal class MXOlmDecryption(
if (decryptedPayload == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
return 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)
}
val payloadString = convertFromUTF8(decryptedPayload)
if (payloadString == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
return 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)
}
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
@@ -88,73 +82,70 @@ internal class MXOlmDecryption(
if (payload == null) {
Timber.e("## decryptEvent failed : null payload")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)
}
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
Timber.e("## decryptEvent() : bad olmPayloadContent format")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT,
MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
}
if (olmPayloadContent.recipient.isNullOrBlank()) {
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
Timber.e("## decryptEvent() : $reason")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
reason))
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
}
if (olmPayloadContent.recipient != credentials.userId) {
if (olmPayloadContent.recipient != userId) {
Timber.e("## decryptEvent() : Event ${event.eventId}:" +
" Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
" Intended recipient ${olmPayloadContent.recipient} does not match our id $userId")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
}
val recipientKeys = olmPayloadContent.recipient_keys ?: run {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))
}
val ed25519 = recipientKeys["ed25519"]
if (ed25519 != olmDevice.deviceEd25519Key) {
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
MXCryptoError.BAD_RECIPIENT_KEY_REASON))
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
MXCryptoError.BAD_RECIPIENT_KEY_REASON)
}
if (olmPayloadContent.sender.isNullOrBlank()) {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))
}
if (olmPayloadContent.sender != event.senderId) {
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
throw MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))
}
if (olmPayloadContent.room_id != event.roomId) {
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))
}
val keys = olmPayloadContent.keys ?: run {
Timber.e("## decryptEvent failed : null keys")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)
}
return Try.just(MXEventDecryptionResult(
return MXEventDecryptionResult(
clearEvent = payload,
senderCurve25519Key = senderKey,
claimedEd25519Key = keys["ed25519"]
))
)
}
/**
@@ -165,33 +156,14 @@ internal class MXOlmDecryption(
* @return payload, if decrypted successfully.
*/
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
val sessionIdsSet = olmDevice.getSessionIds(theirDeviceIdentityKey)
val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet()
val sessionIds: List<String>
if (null == sessionIdsSet) {
sessionIds = ArrayList()
} else {
sessionIds = ArrayList(sessionIdsSet)
}
val messageBody = message["body"] as? String
var messageType: Int? = null
val typeAsVoid = message["type"]
if (null != typeAsVoid) {
if (typeAsVoid is Double) {
messageType = typeAsVoid.toInt()
} else if (typeAsVoid is Int) {
messageType = typeAsVoid
} else if (typeAsVoid is Long) {
messageType = typeAsVoid.toInt()
}
}
if (null == messageBody || null == messageType) {
return null
val messageBody = message["body"] as? String ?: return null
val messageType = when (val typeAsVoid = message["type"]) {
is Double -> typeAsVoid.toInt()
is Int -> typeAsVoid
is Long -> typeAsVoid.toInt()
else -> return null
}
// Try each session in turn

View File

@@ -16,17 +16,16 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.di.UserId
import javax.inject.Inject
internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice,
private val credentials: Credentials) {
@UserId private val userId: String) {
fun create(): MXOlmDecryption {
return MXOlmDecryption(
olmDevice,
credentials)
userId)
}
}

View File

@@ -19,7 +19,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm
import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.internal.crypto.DeviceListManager
@@ -40,37 +39,35 @@ internal class MXOlmEncryption(
private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction)
: IMXEncrypting {
override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content> {
override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content {
// pick the list of recipients based on the membership list.
//
// TODO: there is a race condition here! What if a new user turns up
return ensureSession(userIds)
.map {
val deviceInfos = ArrayList<MXDeviceInfo>()
for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
for (device in devices) {
val key = device.identityKey()
if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) {
// Don't bother setting up session to ourself
continue
}
if (device.isBlocked) {
// Don't bother setting up sessions with blocked users
continue
}
deviceInfos.add(device)
}
}
val messageMap = HashMap<String, Any>()
messageMap["room_id"] = roomId
messageMap["type"] = eventType
messageMap["content"] = eventContent
messageEncrypter.encryptMessage(messageMap, deviceInfos)
messageMap.toContent()!!
ensureSession(userIds)
val deviceInfos = ArrayList<MXDeviceInfo>()
for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
for (device in devices) {
val key = device.identityKey()
if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) {
// Don't bother setting up session to ourself
continue
}
if (device.isBlocked) {
// Don't bother setting up sessions with blocked users
continue
}
deviceInfos.add(device)
}
}
val messageMap = HashMap<String, Any>()
messageMap["room_id"] = roomId
messageMap["type"] = eventType
messageMap["content"] = eventContent
messageEncrypter.encryptMessage(messageMap, deviceInfos)
return messageMap.toContent()!!
}
@@ -78,13 +75,9 @@ internal class MXOlmEncryption(
* Ensure that the session
*
* @param users the user ids list
* @param callback the asynchronous callback
*/
private suspend fun ensureSession(users: List<String>): Try<Unit> {
return deviceListManager
.downloadKeys(users, false)
.flatMap { ensureOlmSessionsForUsersAction.handle(users) }
.map { Unit }
private suspend fun ensureSession(users: List<String>) {
deviceListManager.downloadKeys(users, false)
ensureOlmSessionsForUsersAction.handle(users)
}
}

View File

@@ -21,7 +21,6 @@ import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForUsersAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import javax.inject.Inject

View File

@@ -18,8 +18,6 @@ package im.vector.matrix.android.internal.crypto.api
import im.vector.matrix.android.internal.crypto.model.rest.*
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadBody
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.*

View File

@@ -26,7 +26,6 @@ import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.SecureRandom
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@@ -59,8 +58,7 @@ object MXEncryptedAttachments {
// Half of the IV is random, the lower order bits are zeroed
// such that the counter never wraps.
// See https://github.com/matrix-org/matrix-ios-kit/blob/3dc0d8e46b4deb6669ed44f72ad79be56471354c/MatrixKit/Models/Room/MXEncryptedAttachments.m#L75
val initVectorBytes = ByteArray(16)
Arrays.fill(initVectorBytes, 0.toByte())
val initVectorBytes = ByteArray(16) { 0.toByte() }
val ivRandomPart = ByteArray(8)
secureRandom.nextBytes(ivRandomPart)
@@ -115,7 +113,7 @@ object MXEncryptedAttachments {
encryptedByteArray = outStream.toByteArray()
)
Timber.v("Encrypt in " + (System.currentTimeMillis() - t0) + " ms")
Timber.v("Encrypt in ${System.currentTimeMillis() - t0} ms")
return Try.just(result)
} catch (oom: OutOfMemoryError) {
Timber.e(oom, "## encryptAttachment failed")
@@ -206,13 +204,13 @@ object MXEncryptedAttachments {
val decryptedStream = ByteArrayInputStream(outStream.toByteArray())
outStream.close()
Timber.v("Decrypt in " + (System.currentTimeMillis() - t0) + " ms")
Timber.v("Decrypt in ${System.currentTimeMillis() - t0} ms")
return decryptedStream
} catch (oom: OutOfMemoryError) {
Timber.e(oom, "## decryptAttachment() : failed " + oom.message)
Timber.e(oom, "## decryptAttachment() : failed ${oom.message}")
} catch (e: Exception) {
Timber.e(e, "## decryptAttachment() : failed " + e.message)
Timber.e(e, "## decryptAttachment() : failed ${e.message}")
}
try {
@@ -228,34 +226,20 @@ object MXEncryptedAttachments {
* Base64 URL conversion methods
*/
private fun base64UrlToBase64(base64Url: String?): String? {
var result = base64Url
if (null != result) {
result = result.replace("-".toRegex(), "+")
result = result.replace("_".toRegex(), "/")
}
return result
private fun base64UrlToBase64(base64Url: String): String {
return base64Url.replace('-', '+')
.replace('_', '/')
}
private fun base64ToBase64Url(base64: String?): String? {
var result = base64
if (null != result) {
result = result.replace("\n".toRegex(), "")
result = result.replace("\\+".toRegex(), "-")
result = result.replace("/".toRegex(), "_")
result = result.replace("=".toRegex(), "")
}
return result
private fun base64ToBase64Url(base64: String): String {
return base64.replace("\n".toRegex(), "")
.replace("\\+".toRegex(), "-")
.replace('/', '_')
.replace("=", "")
}
private fun base64ToUnpaddedBase64(base64: String?): String? {
var result = base64
if (null != result) {
result = result.replace("\n".toRegex(), "")
result = result.replace("=".toRegex(), "")
}
return result
private fun base64ToUnpaddedBase64(base64: String): String {
return base64.replace("\n".toRegex(), "")
.replace("=", "")
}
}

View File

@@ -51,7 +51,6 @@ import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEnt
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.*
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
@@ -67,9 +66,8 @@ import org.matrix.olm.OlmPkEncryption
import org.matrix.olm.OlmPkMessage
import timber.log.Timber
import java.security.InvalidParameterException
import java.util.*
import javax.inject.Inject
import kotlin.collections.HashMap
import kotlin.random.Random
/**
* A KeysBackup class instance manage incremental backup of e2e keys (megolm keys)
@@ -115,8 +113,6 @@ internal class KeysBackup @Inject constructor(
// The backup key being used.
private var backupOlmPkEncryption: OlmPkEncryption? = null
private val random = Random()
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
private var keysBackupStateListener: KeysBackupStateListener? = null
@@ -204,31 +200,32 @@ internal class KeysBackup @Inject constructor(
keysBackupStateManager.state = KeysBackupState.Enabling
createKeysBackupVersionTask
.configureWith(createKeysBackupVersionBody)
.dispatchTo(object : MatrixCallback<KeysVersion> {
override fun onSuccess(info: KeysVersion) {
// Reset backup markers.
cryptoStore.resetBackupMarkers()
.configureWith(createKeysBackupVersionBody) {
this.callback = object : MatrixCallback<KeysVersion> {
override fun onSuccess(info: KeysVersion) {
// Reset backup markers.
cryptoStore.resetBackupMarkers()
val keyBackupVersion = KeysVersionResult()
keyBackupVersion.algorithm = createKeysBackupVersionBody.algorithm
keyBackupVersion.authData = createKeysBackupVersionBody.authData
keyBackupVersion.version = info.version
val keyBackupVersion = KeysVersionResult()
keyBackupVersion.algorithm = createKeysBackupVersionBody.algorithm
keyBackupVersion.authData = createKeysBackupVersionBody.authData
keyBackupVersion.version = info.version
// We can consider that the server does not have keys yet
keyBackupVersion.count = 0
keyBackupVersion.hash = null
// We can consider that the server does not have keys yet
keyBackupVersion.count = 0
keyBackupVersion.hash = null
enableKeysBackup(keyBackupVersion)
enableKeysBackup(keyBackupVersion)
callback.onSuccess(info)
callback.onSuccess(info)
}
override fun onFailure(failure: Throwable) {
keysBackupStateManager.state = KeysBackupState.Disabled
callback.onFailure(failure)
}
}
override fun onFailure(failure: Throwable) {
keysBackupStateManager.state = KeysBackupState.Disabled
callback.onFailure(failure)
}
})
}
.executeBy(taskExecutor)
}
@@ -243,27 +240,29 @@ internal class KeysBackup @Inject constructor(
keysBackupStateManager.state = KeysBackupState.Unknown
}
deleteBackupTask.configureWith(DeleteBackupTask.Params(version))
.dispatchTo(object : MatrixCallback<Unit> {
private fun eventuallyRestartBackup() {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup()
deleteBackupTask
.configureWith(DeleteBackupTask.Params(version)) {
this.callback = object : MatrixCallback<Unit> {
private fun eventuallyRestartBackup() {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup()
}
}
override fun onSuccess(data: Unit) {
eventuallyRestartBackup()
uiHandler.post { callback?.onSuccess(Unit) }
}
override fun onFailure(failure: Throwable) {
eventuallyRestartBackup()
uiHandler.post { callback?.onFailure(failure) }
}
}
override fun onSuccess(data: Unit) {
eventuallyRestartBackup()
uiHandler.post { callback?.onSuccess(Unit) }
}
override fun onFailure(failure: Throwable) {
eventuallyRestartBackup()
uiHandler.post { callback?.onFailure(failure) }
}
})
}
.executeBy(taskExecutor)
}
}
@@ -355,15 +354,14 @@ internal class KeysBackup @Inject constructor(
callback: MatrixCallback<KeysBackupVersionTrust>) {
// TODO Validate with François that this is correct
object : Task<KeysVersionResult, KeysBackupVersionTrust> {
override suspend fun execute(params: KeysVersionResult): Try<KeysBackupVersionTrust> {
return Try {
getKeysBackupTrustBg(params)
}
override suspend fun execute(params: KeysVersionResult): KeysBackupVersionTrust {
return getKeysBackupTrustBg(params)
}
}
.configureWith(keysBackupVersion)
.dispatchTo(callback)
.executeOn(TaskThread.COMPUTATION)
.configureWith(keysBackupVersion) {
this.callback = callback
this.executionThread = TaskThread.COMPUTATION
}
.executeBy(taskExecutor)
}
@@ -454,7 +452,8 @@ internal class KeysBackup @Inject constructor(
val myUserId = credentials.userId
// Get current signatures, or create an empty set
val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap() ?: HashMap()
val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap()
?: HashMap()
if (trust) {
// Add current device signature
@@ -492,27 +491,28 @@ internal class KeysBackup @Inject constructor(
// And send it to the homeserver
updateKeysBackupVersionTask
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody))
.dispatchTo(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// Relaunch the state machine on this updated backup version
val newKeysBackupVersion = KeysVersionResult()
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// Relaunch the state machine on this updated backup version
val newKeysBackupVersion = KeysVersionResult()
newKeysBackupVersion.version = keysBackupVersion.version
newKeysBackupVersion.algorithm = keysBackupVersion.algorithm
newKeysBackupVersion.count = keysBackupVersion.count
newKeysBackupVersion.hash = keysBackupVersion.hash
newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData
newKeysBackupVersion.version = keysBackupVersion.version
newKeysBackupVersion.algorithm = keysBackupVersion.algorithm
newKeysBackupVersion.count = keysBackupVersion.count
newKeysBackupVersion.hash = keysBackupVersion.hash
newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
callback.onSuccess(data)
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
}
.executeBy(taskExecutor)
}
}
@@ -758,49 +758,52 @@ internal class KeysBackup @Inject constructor(
if (roomId != null && sessionId != null) {
// Get key for the room and for the session
getRoomSessionDataTask
.configureWith(GetRoomSessionDataTask.Params(roomId, sessionId, version))
.dispatchTo(object : MatrixCallback<KeyBackupData> {
override fun onSuccess(data: KeyBackupData) {
// Convert to KeysBackupData
val keysBackupData = KeysBackupData()
keysBackupData.roomIdToRoomKeysBackupData = HashMap()
val roomKeysBackupData = RoomKeysBackupData()
roomKeysBackupData.sessionIdToKeyBackupData = HashMap()
roomKeysBackupData.sessionIdToKeyBackupData[sessionId] = data
keysBackupData.roomIdToRoomKeysBackupData[roomId] = roomKeysBackupData
.configureWith(GetRoomSessionDataTask.Params(roomId, sessionId, version)) {
this.callback = object : MatrixCallback<KeyBackupData> {
override fun onSuccess(data: KeyBackupData) {
// Convert to KeysBackupData
val keysBackupData = KeysBackupData()
keysBackupData.roomIdToRoomKeysBackupData = HashMap()
val roomKeysBackupData = RoomKeysBackupData()
roomKeysBackupData.sessionIdToKeyBackupData = HashMap()
roomKeysBackupData.sessionIdToKeyBackupData[sessionId] = data
keysBackupData.roomIdToRoomKeysBackupData[roomId] = roomKeysBackupData
callback.onSuccess(keysBackupData)
}
callback.onSuccess(keysBackupData)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}
})
}
.executeBy(taskExecutor)
} else if (roomId != null) {
// Get all keys for the room
getRoomSessionsDataTask
.configureWith(GetRoomSessionsDataTask.Params(roomId, version))
.dispatchTo(object : MatrixCallback<RoomKeysBackupData> {
override fun onSuccess(data: RoomKeysBackupData) {
// Convert to KeysBackupData
val keysBackupData = KeysBackupData()
keysBackupData.roomIdToRoomKeysBackupData = HashMap()
keysBackupData.roomIdToRoomKeysBackupData[roomId] = data
.configureWith(GetRoomSessionsDataTask.Params(roomId, version)) {
this.callback = object : MatrixCallback<RoomKeysBackupData> {
override fun onSuccess(data: RoomKeysBackupData) {
// Convert to KeysBackupData
val keysBackupData = KeysBackupData()
keysBackupData.roomIdToRoomKeysBackupData = HashMap()
keysBackupData.roomIdToRoomKeysBackupData[roomId] = data
callback.onSuccess(keysBackupData)
}
callback.onSuccess(keysBackupData)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}
})
}
.executeBy(taskExecutor)
} else {
// Get all keys
getSessionsDataTask
.configureWith(GetSessionsDataTask.Params(version))
.dispatchTo(callback)
.configureWith(GetSessionsDataTask.Params(version)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
}
@@ -842,7 +845,7 @@ internal class KeysBackup @Inject constructor(
// Wait between 0 and 10 seconds, to avoid backup requests from
// different clients hitting the server all at the same time when a
// new key is sent
val delayInMs = random.nextInt(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS).toLong()
val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
uiHandler.postDelayed({ backupKeys() }, delayInMs)
}
@@ -855,45 +858,47 @@ internal class KeysBackup @Inject constructor(
override fun getVersion(version: String,
callback: MatrixCallback<KeysVersionResult?>) {
getKeysBackupVersionTask
.configureWith(version)
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
override fun onSuccess(data: KeysVersionResult) {
callback.onSuccess(data)
}
.configureWith(version) {
this.callback = object : MatrixCallback<KeysVersionResult> {
override fun onSuccess(data: KeysVersionResult) {
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError
&& failure.error.code == MatrixError.NOT_FOUND) {
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
callback.onSuccess(null)
} else {
// Transmit the error
callback.onFailure(failure)
override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError
&& failure.error.code == MatrixError.NOT_FOUND) {
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
callback.onSuccess(null)
} else {
// Transmit the error
callback.onFailure(failure)
}
}
}
})
}
.executeBy(taskExecutor)
}
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
getKeysBackupLastVersionTask
.toConfigurableTask()
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
override fun onSuccess(data: KeysVersionResult) {
callback.onSuccess(data)
}
.configureWith {
this.callback = object : MatrixCallback<KeysVersionResult> {
override fun onSuccess(data: KeysVersionResult) {
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError
&& failure.error.code == MatrixError.NOT_FOUND) {
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
callback.onSuccess(null)
} else {
// Transmit the error
callback.onFailure(failure)
override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError
&& failure.error.code == MatrixError.NOT_FOUND) {
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
callback.onSuccess(null)
} else {
// Transmit the error
callback.onFailure(failure)
}
}
}
})
}
.executeBy(taskExecutor)
}
@@ -1236,69 +1241,72 @@ internal class KeysBackup @Inject constructor(
Timber.v("backupKeys: 4 - Sending request")
// Make the request
storeSessionDataTask
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData))
.dispatchTo(object : MatrixCallback<BackupKeysResult> {
override fun onSuccess(data: BackupKeysResult) {
uiHandler.post {
Timber.v("backupKeys: 5a - Request complete")
val sendingRequestCallback = object : MatrixCallback<BackupKeysResult> {
override fun onSuccess(data: BackupKeysResult) {
uiHandler.post {
Timber.v("backupKeys: 5a - Request complete")
// Mark keys as backed up
cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
// Mark keys as backed up
cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) {
Timber.v("backupKeys: All keys have been backed up")
onServerDataRetrieved(data.count, data.hash)
if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) {
Timber.v("backupKeys: All keys have been backed up")
onServerDataRetrieved(data.count, data.hash)
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} else {
Timber.v("backupKeys: Continue to back up keys")
keysBackupStateManager.state = KeysBackupState.WillBackUp
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} else {
Timber.v("backupKeys: Continue to back up keys")
keysBackupStateManager.state = KeysBackupState.WillBackUp
backupKeys()
}
}
backupKeys()
}
}
}
override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError) {
uiHandler.post {
Timber.e(failure, "backupKeys: backupKeys failed.")
override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError) {
uiHandler.post {
Timber.e(failure, "backupKeys: backupKeys failed.")
when (failure.error.code) {
MatrixError.NOT_FOUND,
MatrixError.WRONG_ROOM_KEYS_VERSION -> {
// Backup has been deleted on the server, or we are not using the last backup version
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()
resetKeysBackupData()
keysBackupVersion = null
// Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver
checkAndStartKeysBackup()
}
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
}
}
} else {
uiHandler.post {
when (failure.error.code) {
MatrixError.NOT_FOUND,
MatrixError.WRONG_ROOM_KEYS_VERSION -> {
// Backup has been deleted on the server, or we are not using the last backup version
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()
resetKeysBackupData()
keysBackupVersion = null
Timber.e("backupKeys: backupKeys failed.")
// Retry a bit later
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
maybeBackupKeys()
// Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver
checkAndStartKeysBackup()
}
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
}
}
})
} else {
uiHandler.post {
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()
Timber.e("backupKeys: backupKeys failed.")
// Retry a bit later
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
maybeBackupKeys()
}
}
}
}
// Make the request
storeSessionDataTask
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) {
this.callback = sendingRequestCallback
}
.executeBy(taskExecutor)
}
}
@@ -1394,7 +1402,7 @@ internal class KeysBackup @Inject constructor(
companion object {
// Maximum delay in ms in {@link maybeBackupKeys}
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10000
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L
// Maximum number of keys to send at a time to the homeserver.
private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100

View File

@@ -22,7 +22,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup
import androidx.annotation.WorkerThread
import im.vector.matrix.android.api.listeners.ProgressListener
import timber.log.Timber
import java.util.*
import java.util.UUID
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import kotlin.experimental.xor
@@ -142,12 +142,11 @@ private fun deriveKey(password: String,
* Generate a 32 chars salt
*/
private fun generateSalt(): String {
var salt = ""
do {
salt += UUID.randomUUID().toString()
} while (salt.length < SALT_LENGTH)
val salt = buildString {
do {
append(UUID.randomUUID().toString())
} while (length < SALT_LENGTH)
}
return salt.substring(0, SALT_LENGTH)
}

View File

@@ -20,7 +20,6 @@ import android.os.Handler
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
import timber.log.Timber
import java.util.*
internal class KeysBackupStateManager(private val uiHandler: Handler) {

Some files were not shown because too many files have changed in this diff Show More