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

Compare commits

...

169 Commits

Author SHA1 Message Date
Benoit Marty
56677f0908 Merge branch 'release/0.16.0' 2020-02-14 15:14:55 +01:00
Benoit Marty
e20d1724c9 Prepare release 0.16.0 2020-02-14 15:14:48 +01:00
Benoit Marty
f6f9c349ec Add entry in changelog for the 2 mistakes 2020-02-14 14:52:10 +01:00
Benoit Marty
88b8ceaeb5 Merge pull request #1005 from vector-im/feature/fix_event_redaction_prompt
Do not ask for a reason if user wants to delete his own message.
2020-02-14 14:36:01 +01:00
Benoit Marty
e4577d8fef Small cleanup before merge 2020-02-14 14:34:22 +01:00
onurays
030d6824e3 Code review fixes. 2020-02-14 15:04:25 +03:00
Benoit Marty
ab437e249d Update emoji for API change 2020-02-14 12:43:27 +01:00
Valere
a250a895fe Remove redudant calls to eventBus post 2020-02-14 10:47:34 +01:00
Benoit Marty
5808c1de22 TextView displaying emoji should have text color set to black 2020-02-13 19:50:31 +01:00
Benoit Marty
d21604b791 Merge pull request #989 from vector-im/feature/qr_code_mistake
Fix issue with verification when other client declares it can only show QR code (#988)
2020-02-13 19:41:10 +01:00
onurays
fd135e1eeb Compute message body for encrypted messages too. 2020-02-13 18:09:26 +03:00
onurays
f28e4cf991 Fix comparison of user ids. 2020-02-13 17:57:38 +03:00
Valere
1941862499 Fix / Remove debug code line 2020-02-13 15:50:08 +01:00
onurays
983593d647 getRedactionReason function is refactored. 2020-02-13 17:28:14 +03:00
onurays
1b413934b5 Set redaction reason as message body. 2020-02-13 16:42:13 +03:00
onurays
8a3e93ae96 Do not ask for a reason if user wants to delete his own message.
Fixes (#1003)
2020-02-13 14:59:41 +03:00
Benoit Marty
5191cbaf93 Merge pull request #1001 from vector-im/feature/pusher_service
Cleanup pusher service and improve API
2020-02-13 08:02:33 +01:00
Benoit Marty
f2e6900cfb Move throw line 2020-02-13 07:44:54 +01:00
Benoit Marty
0b7e757f3c Code lisibility: use generic name for parameters in Dagger modules 2020-02-13 07:12:11 +01:00
Benoit Marty
5b2c947af1 Rename internal class 2020-02-13 07:05:46 +01:00
Benoit Marty
6a69c6356d PushersService.pushers() has been renamed to PushersService.getPushers() 2020-02-13 07:04:54 +01:00
Benoit Marty
e492e4318b Do some parameter checks 2020-02-13 06:48:52 +01:00
Benoit Marty
93d38843c3 Add Javadoc 2020-02-13 06:36:21 +01:00
Benoit Marty
7d94519064 Merge pull request #998 from vector-im/feature/msc_2192_update_event_prefix
Use vendor prefix for non merged MSC
2020-02-12 22:41:12 +01:00
Valere
21bac0f867 Fix / typo in poll 2020-02-12 22:31:13 +01:00
Valere
32c4ad9ecb Use vendor prefix for non merged MSC 2020-02-12 19:09:23 +01:00
Valere
e1342d096b Merge pull request #810 from vector-im/feature/msc_2192
Feature/msc 2192
2020-02-12 17:41:50 +01:00
Valere
e91c6c216d code quality 2020-02-12 17:03:21 +01:00
Onuray Sahin
6013e1653b Open room member profile from reactions and read receipts. (#990)
Open room member profile from reactions and read receipts. Fixes #875
2020-02-12 15:57:49 +01:00
Benoit Marty
47f47e40c4 code review and cleanup 2020-02-12 15:09:38 +01:00
Benoit Marty
9016688aec Fix compilation issue after rebase 2020-02-12 13:48:08 +01:00
Valere
d5f8a0c0fc code quality 2020-02-12 13:40:54 +01:00
Valere
33c657bff8 Update change log 2020-02-12 13:40:54 +01:00
Valere
9a7bd35ddc Add m.buttons support (a.k.a bot buttons) 2020-02-12 13:40:54 +01:00
Valere
3ac54c51f6 cleaning 2020-02-12 13:39:30 +01:00
Valere
c4ea2507f8 Add command to send poll 2020-02-12 13:39:30 +01:00
Valere
6001ac60ab klint cleaning 2020-02-12 13:37:16 +01:00
Valere
0cea26ec77 ux tweak on poll result 2020-02-12 13:37:16 +01:00
Valere
3dae220501 Fix / filter edits and poll response form lastPreviewableMessage 2020-02-12 13:37:16 +01:00
Valere
577c5a16b3 Support incremental poll response aggregation + display 2020-02-12 12:47:28 +01:00
Valere
a0aebed3f7 Message Poll UX, and model 2020-02-12 12:42:17 +01:00
Benoit Marty
3a044bd655 Add Javadoc 2020-02-12 11:39:36 +01:00
Benoit Marty
6acfab3242 Rename VerificationListener to Listener 2020-02-12 11:39:36 +01:00
Benoit Marty
bf02746d87 Clenaup VerificationService.VerificationListener 2020-02-12 11:39:36 +01:00
Benoit Marty
377d944228 Cleanup API 2020-02-12 11:39:36 +01:00
Benoit Marty
6ff974b3ea Fix issue with verification when other client declares it can only show QR code (#988) 2020-02-12 11:39:36 +01:00
Onuray Sahin
7c5bb4ff5b Merge pull request #986 from vector-im/feature/fix_clear_cache_notification_click
Fix crash by removing all notifications after clearing cache.
2020-02-11 18:06:16 +03:00
onurays
3573aea600 Fix crash by removing all notifications after clearing cache.
Fixes #878
2020-02-11 16:48:28 +03:00
Benoit Marty
251de0b89c Merge pull request #982 from vector-im/feature/remove_message_step2
Redact message step2
2020-02-11 11:35:16 +01:00
Benoit Marty
6fce2a3066 Change wording adding "..." because there is now confirmation dialog
And make the item red because it is destructive. Also use "redact" terminology
2020-02-11 11:04:21 +01:00
Benoit Marty
b40da4ef0f Disable TextInputLayout instead of TextInputEditText 2020-02-11 11:00:33 +01:00
Benoit Marty
96341df5e7 Format layout 2020-02-11 11:00:01 +01:00
Benoit Marty
7ccc1c559c Merge pull request #977 from vector-im/feature/event_deletion_dialog
Show confirmation dialog before deleting a message
2020-02-11 10:45:17 +01:00
Benoit Marty
35ed22ab2b improve script 2020-02-11 10:38:13 +01:00
onurays
afbd9cff70 Merge branch 'develop' into feature/event_deletion_dialog
# Conflicts:
#	CHANGES.md
#	vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
2020-02-11 12:11:36 +03:00
onurays
ce028f8bd2 Gathering reason input is refactored. 2020-02-11 00:10:24 +03:00
Benoit Marty
a7274b9df0 Version++ 2020-02-10 21:41:59 +01:00
Benoit Marty
c498416075 Merge branch 'release/0.15.0' 2020-02-10 21:40:36 +01:00
Benoit Marty
a9d6cb7be3 Merge branch 'release/0.15.0' into develop 2020-02-10 21:40:35 +01:00
Benoit Marty
d71797319c Prepare release 0.15.0 2020-02-10 21:40:27 +01:00
onurays
ba0133a047 Reason input for redacting event is added. 2020-02-10 23:03:37 +03:00
Benoit Marty
01a7ea0bd7 Merge branch 'feature/fix_notif' into develop 2020-02-10 19:16:20 +01:00
Benoit Marty
2e7fa23ce7 Fix #980 2020-02-10 19:13:35 +01:00
Benoit Marty
6750237764 Merge pull request #976 from vector-im/feature/viewEvents
Use View events
2020-02-10 16:46:21 +01:00
Benoit Marty
588a644e02 Merge pull request #975 from vector-im/feature/cleanup
Cleanup push rule code
2020-02-10 16:32:24 +01:00
onurays
1bc1c8ec4a Reorder improvements. 2020-02-10 18:17:22 +03:00
Benoit Marty
7e362be568 Convert to ViewEvents -> Cleanup after review 2020-02-10 16:10:57 +01:00
onurays
83d83e0812 Show confirmation dialog before deleting a message
Fixes #967
2020-02-10 17:58:48 +03:00
Benoit Marty
c97d298166 Merge pull request #948 from vector-im/feature/artifacts_downloading
Feature/artifacts downloading
2020-02-10 14:52:25 +01:00
Benoit Marty
81413e49bc Remove '(yet)' 2020-02-10 14:51:35 +01:00
Benoit Marty
b24f133105 Import strings from Riot 2020-02-10 14:50:21 +01:00
Benoit Marty
5b13be6332 Rename 2020-02-07 19:58:14 +01:00
Benoit Marty
abeb741cad Convert to ViewEvents -> RoomDetailViewModel part 5 2020-02-07 19:50:15 +01:00
Benoit Marty
84e1169525 Convert to ViewEvents -> RoomDetailViewModel part 4 2020-02-07 19:45:54 +01:00
Benoit Marty
27e217fce5 Convert to ViewEvents -> RoomDetailViewModel part 3 2020-02-07 19:41:23 +01:00
Benoit Marty
bdb1f850b2 Convert to ViewEvents -> RoomDetailViewModel part 2 2020-02-07 19:37:30 +01:00
Benoit Marty
0338535efa Convert to ViewEvents -> RoomDetailViewModel part 1 2020-02-07 19:27:18 +01:00
Benoit Marty
0dd3894a49 Use handle() pattern 2020-02-07 19:20:02 +01:00
Benoit Marty
c8ff8d3c9e Convert to ViewEvents -> DeviceListBottomSheetViewModel 2020-02-07 18:59:31 +01:00
Benoit Marty
70973c3302 Convert to ViewEvents -> CrossSigningSettingsViewModel 2020-02-07 18:47:54 +01:00
Benoit Marty
a930313bf3 Convert to ViewEvents -> VerificationBottomSheetViewModel 2020-02-07 18:27:13 +01:00
Benoit Marty
35054dc5e8 Convert to ViewEvents -> CreateDirectRoomViewModel 2020-02-07 18:11:53 +01:00
Benoit Marty
33b123f719 Convert to ViewEvents -> GroupListViewModel 2020-02-07 18:02:42 +01:00
Benoit Marty
24667f38b8 Convert to ViewEvents -> RoomMemberProfileViewModel 2020-02-07 17:47:37 +01:00
Benoit Marty
256a6e4322 Convert to ViewEvents -> RoomDetailViewModel 2020-02-07 17:34:26 +01:00
Benoit Marty
7f5cc77ee0 Convert to ViewEvents -> DevicesViewModel 2020-02-07 17:14:03 +01:00
Benoit Marty
c34307ecf7 Convert to ViewEvents -> RoomDirectoryViewModel 2020-02-07 16:32:01 +01:00
Benoit Marty
dd13b6bd99 Add test for DisplayName condition (passing) 2020-02-07 15:44:46 +01:00
Benoit Marty
9df699db59 Reorder tests 2020-02-07 15:35:09 +01:00
Benoit Marty
8bdb2b88fd make the test compile 2020-02-07 15:32:36 +01:00
Benoit Marty
2a534b5874 Handle SenderNotificationPermissionCondition 2020-02-07 15:32:20 +01:00
Benoit Marty
f719da96ed Rename Condition.Kind enum values and add some documentation 2020-02-07 15:02:39 +01:00
Benoit Marty
31e5c0eb1a Improve algorithm 2020-02-07 14:48:08 +01:00
Benoit Marty
34c5f37bbc findAll() does not return null value 2020-02-07 14:30:44 +01:00
Benoit Marty
9aadbbc3c7 Rework DefaultConditionResolver, and create RoomGetter 2020-02-07 14:27:24 +01:00
Benoit Marty
57758af2d7 Merge pull request #974 from vector-im/feature/crosssigning_cleanup
Improve prompt password dialog
2020-02-07 14:02:15 +01:00
Benoit Marty
0a2474ef12 ktlint 2020-02-07 14:01:18 +01:00
Valere
67bc100782 Merge pull request #959 from vector-im/feature/roomshields_perf
Refactor Room Shield / Profile shield
2020-02-06 16:54:17 +01:00
Benoit Marty
5fedfd9286 Cleanup 2020-02-06 16:00:05 +01:00
Valere
1917fbcc93 eventBus private 2020-02-06 13:56:57 +01:00
Valere
a48bf61ad7 quick dispatch to correct thread (will need more work) 2020-02-06 13:56:57 +01:00
Valere
5c1fcc47a1 Catch all decryption fails in mapping 2020-02-06 13:56:57 +01:00
Valere
d80c15f52f Dispatch init on crypto thread to avoid blocking cold start 2020-02-06 13:56:57 +01:00
Valere
3e2219cbb5 Ignore interrupted exception in setupRx 2020-02-06 13:56:57 +01:00
Valere
bf2e01b8c3 More Rx startWithcallable 2020-02-06 13:56:57 +01:00
Valere
75131fdf44 Post merge fix 2020-02-06 13:56:57 +01:00
Valere
911ff8cf16 cleaning (klint) 2020-02-06 13:56:57 +01:00
Valere
4506b7d6e6 Fix / annoying loading to get active sessions
Start with what's known locally
2020-02-06 13:56:57 +01:00
Valere
320dc4accd Refactor Room Shield / Profile shield 2020-02-06 13:56:57 +01:00
Benoit Marty
ffc9a595dd Merge pull request #964 from vector-im/feature/firefox_account_sso
Support SSO login with Firefox account (#606)
2020-02-06 11:28:16 +01:00
Benoit Marty
1602c6544f We have too many strings... 2020-02-05 18:06:39 +01:00
Benoit Marty
79242c8c16 Merge pull request #892 from vector-im/feature/indonesian_check
Import Indonesian resource check from TravisCI to Weblate pipeline
2020-02-05 18:02:00 +01:00
Benoit Marty
7d00aabc85 Share the same Copyright header. Disabled for XML files 2020-02-05 17:48:59 +01:00
Benoit Marty
ef72f2246a Import Indonesian resource check from TravisCI to Weblate pipeline 2020-02-05 17:47:11 +01:00
Benoit Marty
51c2b9e1e9 Import strings from Riot 2020-02-05 17:35:23 +01:00
Benoit Marty
2a4c8b3199 Support SSO login with Firefox account (#606) 2020-02-05 15:21:54 +01:00
Benoit Marty
3189c114dc Merge pull request #945 from vector-im/feature/stabilization_2
Feature/stabilization 2
2020-02-05 15:19:16 +01:00
Benoit Marty
50814dafe9 Update wording 2020-02-05 14:19:04 +01:00
Benoit Marty
dcd7d17ffd Fix compilation issue after merge and update CHANGES.md 2020-02-05 14:13:43 +01:00
Benoit Marty
d9c007d017 Merge branch 'develop' into feature/stabilization_2 2020-02-05 12:57:42 +01:00
Benoit Marty
05dd587fa8 Move FORMAT_MATRIX_HTML to a proper object and so fix a wrong usage issue 2020-02-05 12:44:31 +01:00
Benoit Marty
3384d91adb Rename MessageContent.type to MessageContent.msgType for code clarity and update a few the Javadoc 2020-02-05 12:39:26 +01:00
Benoit Marty
f72e5c1d94 Explain why and when to use EllipsizingTextView 2020-02-05 12:16:43 +01:00
Benoit Marty
a3ec0e03a0 Use NoOpMatrixCallback when it's possible 2020-02-05 12:05:12 +01:00
Benoit Marty
47ee2a24a7 Move NoOpMatrixCallback to MatrixCallback.kt file 2020-02-05 11:58:08 +01:00
Benoit Marty
b8096f21ea Restore Copyright and cleanup 2020-02-05 11:56:07 +01:00
Benoit Marty
a17ec14dd7 Cleanup and little change on Throwable logging 2020-02-05 11:39:23 +01:00
Benoit Marty
062c4559a2 Merge pull request #955 from vector-im/feature/room_history_in_e2e
Feature/room history in e2e
2020-02-04 21:18:26 +01:00
Benoit Marty
f7d511df38 Ganfra's review 2020-02-04 17:48:17 +01:00
Benoit Marty
6bff951c72 Keep MXCryptoConfig, but do a correct usage of it 2020-02-04 15:37:46 +01:00
Benoit Marty
bd033866a8 Encrypt for invited users by default, if the room state allows it (#803) 2020-02-04 14:41:32 +01:00
Benoit Marty
3e9b2e4a06 Use the correct enableEncryption() method 2020-02-04 14:04:48 +01:00
Ganard
ed9c3379bf Rename file 2020-02-04 13:50:10 +01:00
Ganard
1728d31401 Fix some issues and make test passes 2020-02-04 13:19:02 +01:00
Benoit Marty
225a6e00e6 Add doc and reorder 2020-02-04 12:10:51 +01:00
Benoit Marty
96a729ab2b Move file 2020-02-04 11:53:09 +01:00
Benoit Marty
badb4042ce Add sign_apk_unsafe.sh to the repo 2020-02-04 11:53:09 +01:00
Benoit Marty
c3bb421c1e ignore .idea/inspectionProfiles 2020-02-04 11:53:09 +01:00
Benoit Marty
09212a05d0 Add a script to download artifacts from buildkite 2020-02-04 11:53:09 +01:00
Benoit Marty
e02430bcd0 Ask for permission before opening the camera (#934) 2020-02-03 20:06:37 +01:00
Benoit Marty
4335fa4f72 ktlint 2020-02-03 16:26:18 +01:00
Benoit Marty
6df5edbad1 Version++ 2020-02-03 16:20:55 +01:00
Benoit Marty
362799ac08 Merge branch 'release/0.14.3' into develop 2020-02-03 16:15:52 +01:00
Ganard
f454078c6b Clean code 2020-02-03 16:14:36 +01:00
Benoit Marty
783a514496 Improve prompt password dialog
Reveal password, inline error when empty
2020-02-03 15:53:44 +01:00
Ganard
88755a79b4 In memory sending: fix broken filtering 2020-02-03 15:18:19 +01:00
Ganard
e6cd8a3a86 Merge develop into feature/stabilization_2 2020-02-03 13:59:20 +01:00
Ganard
37230b0614 Fix issues with read marker and jumpToBottom 2020-01-31 18:09:34 +01:00
Ganard
ec6d78bf96 Merge branch 'develop' into feature/stabilization_2 2020-01-31 15:47:33 +01:00
Ganard
759b680e63 Timeline/Sync: Fix some issues 2020-01-31 15:28:35 +01:00
Ganard
5e1b59f9d3 Timeline: handle an in memory local echo to make the UI snappier 2020-01-30 17:13:44 +01:00
ganfra
9fc3fa7f19 Update some libs and remove incremental from dagger 2020-01-29 21:14:38 +01:00
ganfra
71a02a58af Sync/Timeline: handle displayName isUnique 2020-01-29 17:30:31 +01:00
ganfra
7f72af426b Timeline: fix getContext 2020-01-29 16:02:53 +01:00
Ganard
a8f783bbfa Add state events to chunks 2020-01-28 18:59:21 +01:00
Ganard
bf7c53ecab Sync/pagination: get a working version 2020-01-28 14:46:26 +01:00
Ganard
15b0bea870 Use clone for retrofit request to be able to retry 2020-01-28 10:13:36 +01:00
Ganard
e5e62dc4a7 Fix SyncService and Alarm 2020-01-28 10:13:19 +01:00
Ganard
f3db43f317 Ellipsize: introduce EllipsizingTextView 2020-01-27 12:41:43 +01:00
ganfra
3a89a30056 Continue reworking sync/timeline events handling 2020-01-25 18:59:45 +01:00
Ganard
1d8b81bb04 Try reworking events/timeline process [WIP] 2020-01-24 18:43:35 +01:00
ganfra
7bcae75314 Remove some code from main thread 2020-01-23 20:31:18 +01:00
ganfra
4331d2ef47 Network: reword the strategy for handling NetworkConnectivity (remove Merlin) 2020-01-23 19:08:55 +01:00
ganfra
c65f25d7ae Rx: fix startWith on mainThread 2020-01-23 10:18:22 +01:00
ganfra
fee2ec6b66 Scroll when event build come from sync/send + remove use of monarchy writeAsync 2020-01-22 20:33:52 +01:00
ganfra
76065ac4fc Read: allow setting read marker and read receipt to latest known event independently 2020-01-22 14:43:39 +01:00
ganfra
d93050240a Start reworking networkConnectivityCheck (WIP) 2020-01-22 14:41:42 +01:00
ganfra
2bddf61afe Update realm to 6.1.0: should fix some of the native crashes 2020-01-21 15:15:29 +01:00
ganfra
d1b8d81fb1 Fix double read receipts 2020-01-21 14:17:04 +01:00
347 changed files with 8676 additions and 3523 deletions

View File

@@ -86,3 +86,10 @@ steps:
plugins:
- docker#v3.1.0:
image: "openjdk"
# Check that indonesians files are identical.
# Due to Android issue, the resource folder must be values-in/, and Weblate export data into values-id/.
# If this step fails, it means that Weblate has updated the file in value-id/ so to fix it, copy the file to values-in/
- label: "Indonesian"
command:
- "diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml"

5
.gitignore vendored
View File

@@ -4,6 +4,7 @@
# idea files: exclude everything except dictionnaries
.idea/caches
.idea/libraries
.idea/inspectionProfiles
.idea/*.xml
.DS_Store
/build
@@ -13,7 +14,3 @@
/tmp
ktlint
.idea/copyright/New_vector.xml
.idea/copyright/profiles_settings.xml
.idea/copyright/New_Vector_Ltd.xml

View File

@@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="160" />
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>

6
.idea/copyright/NewVector.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright (c) &amp;#36;today.year New Vector Ltd&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10; http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
<option name="myName" value="NewVector" />
</copyright>
</component>

8
.idea/copyright/profiles_settings.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<component name="CopyrightManager">
<settings default="NewVector">
<LanguageOptions name="XML">
<option name="fileTypeOverride" value="1" />
<option name="prefixLines" value="false" />
</LanguageOptions>
</settings>
</component>

View File

@@ -1,3 +1,36 @@
Changes in RiotX 0.16.0 (2020-02-14)
===================================================
Features ✨:
- Polls and Bot Buttons (MSC 2192 matrix-org/matrix-doc#2192)
Improvements 🙌:
- Show confirmation dialog before deleting a message (#967, #1003)
- Open room member profile from reactions list and read receipts list (#875)
Bugfix 🐛:
- Fix crash by removing all notifications after clearing cache (#878)
- Fix issue with verification when other client declares it can only show QR code (#988)
- Fix too errors in the code (1941862499c9ec5268cc80882512ced379cafcfd, a250a895fe0a4acf08c671e03434edcd29ccd84f)
SDK API changes ⚠️:
- Javadoc improved for PushersService
- PushersService.pushers() has been renamed to PushersService.getPushers()
Changes in RiotX 0.15.0 (2020-02-10)
===================================================
Improvements 🙌:
- Improve navigation to the timeline (#789, #862)
- Improve network detection. It is now based on the sync request status (#873, #882)
Other changes:
- Support SSO login with Firefox account (#606)
Bugfix 🐛:
- Ask for permission before opening the camera (#934)
- Encrypt for invited users by default, if the room state allows it (#803)
Changes in RiotX 0.14.3 (2020-02-03)
===================================================
@@ -349,15 +382,17 @@ Features ✨:
Improvements 🙌:
-
Other changes:
-
Bugfix 🐛:
-
Translations 🗣:
-
SDK API changes ⚠️:
-
Build 🧱:
-
Other changes:
-

View File

@@ -47,23 +47,11 @@ allprojects {
jcenter()
}
tasks.withType(JavaCompile).all {
options.compilerArgs += [
'-Adagger.gradle.incremental=enabled'
]
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// Warnings are potential errors, so stop ignoring them
kotlinOptions.allWarningsAsErrors = true
}
afterEvaluate {
extensions.findByName("kapt")?.arguments {
arg("dagger.gradle.incremental", "enabled")
}
}
}
task clean(type: Delete) {

View File

@@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import io.reactivex.Observable
import io.reactivex.android.MainThreadDisposable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
private class LiveDataObservable<T>(
@@ -60,3 +61,11 @@ private class LiveDataObservable<T>(
fun <T> LiveData<T>.asObservable(): Observable<T> {
return LiveDataObservable(this).observeOn(Schedulers.computation())
}
internal fun <T> Observable<T>.startWithCallable(supplier: () -> T): Observable<T> {
val startObservable = Observable
.fromCallable(supplier)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
return startWith(startObservable)
}

View File

@@ -16,8 +16,6 @@
package im.vector.matrix.rx
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
@@ -30,114 +28,43 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.functions.BiFunction
import timber.log.Timber
class RxRoom(private val room: Room, private val session: Session) {
class RxRoom(private val room: Room) {
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
val summaryObservable = room.getRoomSummaryLive()
return room.getRoomSummaryLive()
.asObservable()
.startWith(room.roomSummary().toOptional())
.doOnNext { Timber.v("RX: summary emitted for: ${it.getOrNull()?.roomId}") }
val memberIdsChangeObservable = summaryObservable
.map {
it.getOrNull()?.let { roomSummary ->
if (roomSummary.isEncrypted) {
// Return the list of other users
roomSummary.otherMemberIds + listOf(session.myUserId)
} else {
// Return an empty list, the room is not encrypted
emptyList()
}
}.orEmpty()
}.distinctUntilChanged()
.doOnNext { Timber.v("RX: memberIds emitted. Size: ${it.size}") }
// Observe the device info of the users in the room
val cryptoDeviceInfoObservable = memberIdsChangeObservable
.switchMap { membersIds ->
session.getLiveCryptoDeviceInfo(membersIds)
.asObservable()
.map {
// If any key change, emit the userIds list
membersIds
}
.startWith(membersIds)
.doOnNext { Timber.v("RX: CryptoDeviceInfo emitted. Size: ${it.size}") }
}
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted 2. Size: ${it.size}") }
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
.map { userIds ->
if (userIds.isEmpty()) {
Optional<RoomEncryptionTrustLevel>(null)
} else {
session.getCrossSigningService().getTrustLevelForUsers(userIds).toOptional()
}
}
.doOnNext { Timber.v("RX: roomEncryptionTrustLevel emitted: ${it.getOrNull()?.name}") }
return Observable
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
summaryObservable,
roomEncryptionTrustLevelObservable,
BiFunction { summary, level ->
summary.getOrNull()?.copy(
roomEncryptionTrustLevel = level.getOrNull()
).toOptional()
}
)
.doOnNext { Timber.v("RX: final room summary emitted for ${it.getOrNull()?.roomId}") }
.startWithCallable { room.roomSummary().toOptional() }
}
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
.startWith(room.getRoomMembers(queryParams))
.doOnNext { Timber.v("RX: room members emitted. Size: ${it.size}") }
// TODO Do it only for room members of the room (switchMap)
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
.startWith(emptyList<CryptoDeviceInfo>())
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted. Size: ${it.size}") }
return Observable
.combineLatest<List<RoomMemberSummary>, List<CryptoDeviceInfo>, List<RoomMemberSummary>>(
roomMembersObservable,
cryptoDeviceInfoObservable,
BiFunction { summaries, _ ->
summaries.map {
if (room.isEncrypted()) {
it.copy(
// Get the trust level of a virtual room with only this user
userEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(listOf(it.userId))
)
} else {
it
}
}
}
)
.doOnNext { Timber.v("RX: final room members emitted. Size: ${it.size}") }
return room.getRoomMembersLive(queryParams).asObservable()
.startWithCallable {
room.getRoomMembers(queryParams)
}
}
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
.startWith(room.getEventAnnotationsSummary(eventId).toOptional())
.startWithCallable {
room.getEventAnnotationsSummary(eventId).toOptional()
}
}
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
return room.getTimeLineEventLive(eventId).asObservable()
.startWith(room.getTimeLineEvent(eventId).toOptional())
.startWithCallable {
room.getTimeLineEvent(eventId).toOptional()
}
}
fun liveStateEvent(eventType: String): Observable<Optional<Event>> {
return room.getStateEventLive(eventType).asObservable()
.startWith(room.getStateEvent(eventType).toOptional())
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
return room.getStateEventLive(eventType, stateKey).asObservable()
.startWithCallable {
room.getStateEvent(eventType, stateKey).toOptional()
}
}
fun liveReadMarker(): Observable<Optional<String>> {
@@ -170,6 +97,6 @@ class RxRoom(private val room: Room, private val session: Session) {
}
}
fun Room.rx(session: Session): RxRoom {
return RxRoom(this, session)
fun Room.rx(): RxRoom {
return RxRoom(this)
}

View File

@@ -33,48 +33,28 @@ import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.functions.BiFunction
import timber.log.Timber
class RxSession(private val session: Session) {
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
.startWith(session.getRoomSummaries(queryParams))
.doOnNext { Timber.v("RX: summaries emitted: size: ${it.size}") }
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
.startWith(emptyList<CryptoDeviceInfo>())
.doOnNext { Timber.v("RX: crypto device info emitted: size: ${it.size}") }
return Observable
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
summariesObservable,
cryptoDeviceInfoObservable,
BiFunction { summaries, _ ->
summaries.map {
if (it.isEncrypted) {
it.copy(
roomEncryptionTrustLevel = session.getCrossSigningService()
.getTrustLevelForUsers(it.otherMemberIds + session.myUserId)
)
} else {
it
}
}
}
)
.doOnNext { Timber.d("RX: final summaries emitted: size: ${it.size}") }
return session.getRoomSummariesLive(queryParams).asObservable()
.startWithCallable {
session.getRoomSummaries(queryParams)
}
}
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
return session.getGroupSummariesLive(queryParams).asObservable()
.startWith(session.getGroupSummaries(queryParams))
.startWithCallable {
session.getGroupSummaries(queryParams)
}
}
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
return session.getBreadcrumbsLive().asObservable()
.startWith(session.getBreadcrumbs())
.startWithCallable {
session.getBreadcrumbs()
}
}
fun liveSyncState(): Observable<SyncState> {
@@ -87,7 +67,9 @@ class RxSession(private val session: Session) {
fun liveUser(userId: String): Observable<Optional<User>> {
return session.getUserLive(userId).asObservable()
.startWith(session.getUser(userId).toOptional())
.startWithCallable {
session.getUser(userId).toOptional()
}
}
fun liveUsers(): Observable<List<User>> {
@@ -128,12 +110,16 @@ class RxSession(private val session: Session) {
}
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
return session.getLiveCryptoDeviceInfo(userId).asObservable()
return session.getLiveCryptoDeviceInfo(userId).asObservable().startWithCallable {
session.getCryptoDeviceInfo(userId)
}
}
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable()
.startWith(session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional())
.startWithCallable {
session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional()
}
}
}

View File

@@ -92,10 +92,11 @@ dependencies {
def arrow_version = "0.8.2"
def moshi_version = '1.8.0'
def lifecycle_version = '2.1.0'
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
def coroutines_version = "1.3.2"
def markwon_version = '3.1.0'
def daggerVersion = '2.24'
def daggerVersion = '2.25.4'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
@@ -104,14 +105,13 @@ dependencies {
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// Network
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-moshi:2.6.2'
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
implementation 'com.novoda:merlin:1.2.0'
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
@@ -125,7 +125,7 @@ dependencies {
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
// Work
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
implementation "androidx.work:work-runtime-ktx:2.3.0"
// FP
implementation "io.arrow-kt:arrow-core:$arrow_version"
@@ -167,7 +167,7 @@ dependencies {
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
}

View File

@@ -117,6 +117,10 @@ class CommonTestHelper(context: Context) {
override fun onTimelineFailure(throwable: Throwable) {
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
// TODO Count only new messages?
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {

View File

@@ -74,7 +74,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
val room = aliceSession.getRoom(roomId!!)!!
val lock2 = CountDownLatch(1)
room.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM, object : TestMatrixCallback<Unit>(lock2) {})
room.enableEncryption(callback = TestMatrixCallback(lock2))
mTestHelper.await(lock2)
return CryptoTestData(aliceSession, roomId!!)
@@ -241,6 +241,11 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
val bobEventsListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
// noop
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {

View File

@@ -66,14 +66,10 @@ class SASTest : InstrumentedTest {
val bobVerificationService = bobSession!!.getVerificationService()
val bobTxCreatedLatch = CountDownLatch(1)
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
bobTxCreatedLatch.countDown()
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobVerificationService.addListener(bobListener)
@@ -106,9 +102,7 @@ class SASTest : InstrumentedTest {
// Let's cancel from alice side
val cancelLatch = CountDownLatch(1)
val bobListener2 = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val bobListener2 = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == txID) {
val immutableState = (tx as SASDefaultVerificationTransaction).state
@@ -117,8 +111,6 @@ class SASTest : InstrumentedTest {
}
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobVerificationService.addListener(bobListener2)
@@ -157,17 +149,13 @@ class SASTest : InstrumentedTest {
var cancelReason: CancelCode? = null
val cancelLatch = CountDownLatch(1)
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
cancelLatch.countDown()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobSession.getVerificationService().addListener(bobListener)
@@ -186,16 +174,12 @@ class SASTest : InstrumentedTest {
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.getMyDevice().deviceId
val aliceListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
(tx as IncomingSasVerificationTransaction).performAccept()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
aliceSession.getVerificationService().addListener(aliceListener)
@@ -328,7 +312,7 @@ class SASTest : InstrumentedTest {
val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(2)
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
val aliceListener = object : VerificationService.VerificationListener {
val aliceListener = object : VerificationService.Listener {
override fun transactionCreated(tx: VerificationTransaction) {
createdTx.add(tx as SASDefaultVerificationTransaction)
aliceCreatedLatch.countDown()
@@ -339,8 +323,6 @@ class SASTest : InstrumentedTest {
aliceCancelledLatch.countDown()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
aliceVerificationService.addListener(aliceListener)
@@ -372,11 +354,7 @@ class SASTest : InstrumentedTest {
var startReq: KeyVerificationStart? = null
val aliceAcceptedLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.VerificationListener {
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
override fun transactionCreated(tx: VerificationTransaction) {}
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
val at = tx as SASDefaultVerificationTransaction
@@ -388,17 +366,13 @@ class SASTest : InstrumentedTest {
}
aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
val at = tx as IncomingSasVerificationTransaction
at.performAccept()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobVerificationService.addListener(bobListener)
@@ -433,9 +407,7 @@ class SASTest : InstrumentedTest {
val bobVerificationService = bobSession!!.getVerificationService()
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
when (uxState) {
@@ -445,15 +417,11 @@ class SASTest : InstrumentedTest {
else -> Unit
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
when (uxState) {
@@ -466,8 +434,6 @@ class SASTest : InstrumentedTest {
bobSASLatch.countDown()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobVerificationService.addListener(bobListener)
@@ -497,9 +463,7 @@ class SASTest : InstrumentedTest {
val bobVerificationService = bobSession!!.getVerificationService()
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
when (uxState) {
@@ -512,15 +476,11 @@ class SASTest : InstrumentedTest {
else -> Unit
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
when (uxState) {
@@ -536,8 +496,6 @@ class SASTest : InstrumentedTest {
else -> Unit
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobVerificationService.addListener(bobListener)

View File

@@ -0,0 +1,232 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class VerificationTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
data class ExpectedResult(
val sasIsSupported: Boolean = false,
val otherCanScanQrCode: Boolean = false,
val otherCanShowQrCode: Boolean = false
)
private val sas = listOf(
VerificationMethod.SAS
)
private val sasShow = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SHOW
)
private val sasScan = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SCAN
)
private val sasShowScan = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SHOW,
VerificationMethod.QR_CODE_SCAN
)
@Test
fun test_aliceAndBob_sas_sas() = doTest(
sas,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_sas_show() = doTest(
sas,
sasShow,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_sas() = doTest(
sasShow,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_sas_scan() = doTest(
sas,
sasScan,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_scan_sas() = doTest(
sasScan,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_scan_scan() = doTest(
sasScan,
sasScan,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_show() = doTest(
sasShow,
sasShow,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_scan() = doTest(
sasShow,
sasScan,
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true)
)
@Test
fun test_aliceAndBob_scan_show() = doTest(
sasScan,
sasShow,
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true)
)
@Test
fun test_aliceAndBob_all_all() = doTest(
sasShowScan,
sasShowScan,
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
)
// TODO Add tests without SAS
private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
bobSupportedMethods: List<VerificationMethod>,
expectedResultForAlice: ExpectedResult,
expectedResultForBob: ExpectedResult) {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
mTestHelper.doSync<Unit> { callback ->
aliceSession.getCrossSigningService()
.initializeCrossSigning(UserPasswordAuth(
user = aliceSession.myUserId,
password = TestConstants.PASSWORD
), callback)
}
mTestHelper.doSync<Unit> { callback ->
bobSession.getCrossSigningService()
.initializeCrossSigning(UserPasswordAuth(
user = bobSession.myUserId,
password = TestConstants.PASSWORD
), callback)
}
val aliceVerificationService = aliceSession.getVerificationService()
val bobVerificationService = bobSession.getVerificationService()
var aliceReadyPendingVerificationRequest: PendingVerificationRequest? = null
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
val latch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 4: Alice receive the ready request
if (pr.isReady) {
aliceReadyPendingVerificationRequest = pr
latch.countDown()
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Step 2: Bob accepts the verification request
bobVerificationService.readyPendingVerificationInDMs(
bobSupportedMethods,
aliceSession.myUserId,
cryptoTestData.roomId,
pr.transactionId!!
)
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 3: Bob is ready
if (pr.isReady) {
bobReadyPendingVerificationRequest = pr
latch.countDown()
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
mTestHelper.await(latch)
aliceReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported
pr.otherCanShowQrCode() shouldBe expectedResultForAlice.otherCanShowQrCode
pr.otherCanScanQrCode() shouldBe expectedResultForAlice.otherCanScanQrCode
}
bobReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported() shouldBe expectedResultForBob.sasIsSupported
pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
}
cryptoTestData.close()
}
}

View File

@@ -20,15 +20,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.helper.add
import im.vector.matrix.android.internal.database.helper.lastStateIndex
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.helper.addTimelineEvent
import im.vector.matrix.android.internal.database.helper.merge
import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeRoomMemberEvent
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.createObject
@@ -58,8 +58,11 @@ internal class ChunkEntityTest : InstrumentedTest {
fun add_shouldAdd_whenNotAlreadyIncluded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
realm.copyToRealmOrUpdate(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.timelineEvents.size shouldEqual 1
}
}
@@ -68,65 +71,23 @@ internal class ChunkEntityTest : InstrumentedTest {
fun add_shouldNotAdd_whenAlreadyIncluded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
realm.copyToRealmOrUpdate(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.timelineEvents.size shouldEqual 1
}
}
@Test
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeRoomMemberEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
}
}
@Test
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
}
}
@Test
fun addAll_shouldStateIndexIncremented_whenStateEventsAreAddedForward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvents = createFakeListOfEvents(30)
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
chunk.addAll("roomId", fakeEvents, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual numberOfStateEvents
}
}
@Test
fun addAll_shouldStateIndexDecremented_whenStateEventsAreAddedBackward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvents = createFakeListOfEvents(30)
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
val lastIsState = fakeEvents.last().isStateEvent()
val expectedStateIndex = if (lastIsState) -numberOfStateEvents + 1 else -numberOfStateEvents
chunk.addAll("roomId", fakeEvents, PaginationDirection.BACKWARDS)
chunk.lastStateIndex(PaginationDirection.BACKWARDS) shouldEqual expectedStateIndex
}
}
@Test
fun merge_shouldAddEvents_whenMergingBackward() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldEqual 60
}
}
@@ -140,9 +101,9 @@ internal class ChunkEntityTest : InstrumentedTest {
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
chunk1.isLastForward = true
chunk2.isLastForward = false
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldEqual 40
chunk1.isLastForward.shouldBeTrue()
}
@@ -155,9 +116,9 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject()
val prevToken = "prev_token"
chunk1.prevToken = prevToken
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS)
chunk1.prevToken shouldEqual prevToken
}
}
@@ -169,19 +130,25 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject()
val nextToken = "next_token"
chunk1.nextToken = nextToken
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.nextToken shouldEqual nextToken
}
}
private fun ChunkEntity.addAll(roomId: String,
events: List<Event>,
direction: PaginationDirection,
stateIndexOffset: Int = 0) {
direction: PaginationDirection) {
events.forEach { event ->
add(roomId, event, direction, stateIndexOffset)
val fakeEvent = event.toEntity(roomId, SendState.SYNCED).let {
realm.copyToRealmOrUpdate(it)
}
addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
}
}
companion object {
private const val ROOM_ID = "roomId"
}
}

View File

@@ -23,6 +23,7 @@ import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.crypto.MXCryptoConfig
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
@@ -35,7 +36,8 @@ import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
data class MatrixConfiguration(
val applicationFlavor: String = "Default-application-flavor"
val applicationFlavor: String = "Default-application-flavor",
val cryptoConfig: MXCryptoConfig = MXCryptoConfig()
) {
interface Provider {
@@ -57,12 +59,11 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
init {
Monarchy.init(context)
DaggerMatrixComponent.factory().create(context).inject(this)
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
if (context.applicationContext !is Configuration.Provider) {
WorkManager.initialize(context, Configuration.Builder().build())
}
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor)
}
fun getUserAgent() = userAgentHolder.userAgent

View File

@@ -38,3 +38,8 @@ interface MatrixCallback<in T> {
// no-op
}
}
/**
* Basic no op implementation
*/
class NoOpMatrixCallback<T>: MatrixCallback<T>

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
package im.vector.matrix.android.api.crypto
/**
* Class to define the parameters used to customize or configure the end-to-end crypto.
*/
data class MXCryptoConfig(
// Tell whether the encryption of the event content is enabled for the invited members.
// By default, we encrypt messages only for the joined members.
// The encryption for the invited members will be blocked if the history visibility is "joined".
var enableEncryptionForInvitedMembers: Boolean = false
// SDK clients can disable this by settings it to false.
// Note that the encryption for the invited members will be blocked if the history visibility is "joined".
var enableEncryptionForInvitedMembers: Boolean = true
)

View File

@@ -15,30 +15,32 @@
*/
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event
abstract class Condition(val kind: Kind) {
enum class Kind(val value: String) {
event_match("event_match"),
contains_display_name("contains_display_name"),
room_member_count("room_member_count"),
sender_notification_permission("sender_notification_permission"),
UNRECOGNIZE("");
EventMatch("event_match"),
ContainsDisplayName("contains_display_name"),
RoomMemberCount("room_member_count"),
SenderNotificationPermission("sender_notification_permission"),
Unrecognised("");
companion object {
fun fromString(value: String): Kind {
return when (value) {
"event_match" -> event_match
"contains_display_name" -> contains_display_name
"room_member_count" -> room_member_count
"sender_notification_permission" -> sender_notification_permission
else -> UNRECOGNIZE
"event_match" -> EventMatch
"contains_display_name" -> ContainsDisplayName
"room_member_count" -> RoomMemberCount
"sender_notification_permission" -> SenderNotificationPermission
else -> Unrecognised
}
}
}
}
abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean
abstract fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean
open fun technicalDescription(): String {
return "Kind: $kind"

View File

@@ -15,14 +15,22 @@
*/
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event
/**
* Acts like a visitor on Conditions.
* This class as all required context needed to evaluate rules
*/
interface ConditionResolver {
fun resolveEventMatchCondition(event: Event,
condition: EventMatchCondition): Boolean
fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean
fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean
fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean
fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean
fun resolveRoomMemberCountCondition(event: Event,
condition: RoomMemberCountCondition): Boolean
fun resolveSenderNotificationPermissionCondition(event: Event,
condition: SenderNotificationPermissionCondition): Boolean
fun resolveContainsDisplayNameCondition(event: Event,
condition: ContainsDisplayNameCondition): Boolean
}

View File

@@ -21,10 +21,10 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import timber.log.Timber
class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
class ContainsDisplayNameCondition : Condition(Kind.ContainsDisplayName) {
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveContainsDisplayNameCondition(this)
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveContainsDisplayNameCondition(event, this)
}
override fun technicalDescription(): String {

View File

@@ -19,10 +19,20 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.di.MoshiProvider
import timber.log.Timber
class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind.event_match) {
class EventMatchCondition(
/**
* The dot-separated field of the event to match, e.g. content.body
*/
val key: String,
/**
* The glob-style pattern to match against. Patterns with no special glob characters should
* be treated as having asterisks prepended and appended when testing the condition.
*/
val pattern: String
) : Condition(Kind.EventMatch) {
override fun isSatisfied(conditionResolver: ConditionResolver) : Boolean {
return conditionResolver.resolveEventMatchCondition(this)
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveEventMatchCondition(event, this)
}
override fun technicalDescription(): String {

View File

@@ -16,25 +16,32 @@
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.session.room.RoomGetter
import timber.log.Timber
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
class RoomMemberCountCondition(
/**
* A decimal integer optionally prefixed by one of ==, <, >, >= or <=.
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth.
* If no prefix is present, this parameter defaults to ==.
*/
val iz: String
) : Condition(Kind.RoomMemberCount) {
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveRoomMemberCountCondition(this)
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveRoomMemberCountCondition(event, this)
}
override fun technicalDescription(): String {
return "Room member count is $iz"
}
fun isSatisfied(event: Event, session: RoomService?): Boolean {
// sanity check^
internal fun isSatisfied(event: Event, roomGetter: RoomGetter): Boolean {
// sanity checks
val roomId = event.roomId ?: return false
val room = session?.getRoom(roomId) ?: return false
val room = roomGetter.getRoom(roomId) ?: return false
// Parse the is field into prefix and number the first time
val (prefix, count) = parseIsField() ?: return false
@@ -59,7 +66,7 @@ class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_coun
val (prefix, count) = match.destructured
return prefix to count.toInt()
} catch (t: Throwable) {
Timber.d(t)
Timber.e(t, "Unable to parse 'is' field")
}
return null
}

View File

@@ -19,10 +19,18 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) {
class SenderNotificationPermissionCondition(
/**
* A string that determines the power level the sender must have to trigger notifications of a given type,
* such as room. Refer to the m.room.power_levels event schema for information about what the defaults are
* and how to interpret the event. The key is used to look up the power level required to send a notification
* type from the notifications object in the power level event content.
*/
val key: String
) : Condition(Kind.SenderNotificationPermission) {
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveSenderNotificationPermissionCondition(this)
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveSenderNotificationPermissionCondition(event, this)
}
override fun technicalDescription(): String {

View File

@@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
* All push rulesets for a user.
*/
@JsonClass(generateAdapter = true)
data class GetPushRulesResponse(
internal data class GetPushRulesResponse(
/**
* Global rules, account level applying to all devices
*/

View File

@@ -17,7 +17,11 @@ package im.vector.matrix.android.api.pushrules.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.pushrules.*
import im.vector.matrix.android.api.pushrules.Condition
import im.vector.matrix.android.api.pushrules.ContainsDisplayNameCondition
import im.vector.matrix.android.api.pushrules.EventMatchCondition
import im.vector.matrix.android.api.pushrules.RoomMemberCountCondition
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
import timber.log.Timber
@JsonClass(generateAdapter = true)
@@ -30,13 +34,13 @@ data class PushCondition(
/**
* Required for event_match conditions. The dot- separated field of the event to match.
*/
val key: String? = null,
/**
*Required for event_match conditions.
*/
/**
* Required for event_match conditions.
*/
val pattern: String? = null,
/**
* Required for room_member_count conditions.
* A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
@@ -47,30 +51,35 @@ data class PushCondition(
) {
fun asExecutableCondition(): Condition? {
return when (Condition.Kind.fromString(this.kind)) {
Condition.Kind.event_match -> {
if (this.key != null && this.pattern != null) {
return when (Condition.Kind.fromString(kind)) {
Condition.Kind.EventMatch -> {
if (key != null && pattern != null) {
EventMatchCondition(key, pattern)
} else {
Timber.e("Malformed Event match condition")
null
}
}
Condition.Kind.contains_display_name -> {
Condition.Kind.ContainsDisplayName -> {
ContainsDisplayNameCondition()
}
Condition.Kind.room_member_count -> {
if (this.iz.isNullOrBlank()) {
Condition.Kind.RoomMemberCount -> {
if (iz.isNullOrEmpty()) {
Timber.e("Malformed ROOM_MEMBER_COUNT condition")
null
} else {
RoomMemberCountCondition(this.iz)
RoomMemberCountCondition(iz)
}
}
Condition.Kind.sender_notification_permission -> {
this.key?.let { SenderNotificationPermissionCondition(it) }
Condition.Kind.SenderNotificationPermission -> {
if (key == null) {
Timber.e("Malformed Sender Notification Permission condition")
null
} else {
SenderNotificationPermissionCondition(key)
}
}
Condition.Kind.UNRECOGNIZE -> {
Condition.Kind.Unrecognised -> {
Timber.e("Unknown kind $kind")
null
}

View File

@@ -18,7 +18,7 @@ package im.vector.matrix.android.api.pushrules.rest
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Ruleset(
internal data class Ruleset(
val content: List<PushRule>? = null,
val override: List<PushRule>? = null,
val room: List<PushRule>? = null,

View File

@@ -18,7 +18,6 @@ package im.vector.matrix.android.api.session.crypto.crosssigning
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
@@ -63,6 +62,4 @@ interface CrossSigningService {
fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String,
locallyTrusted: Boolean?): DeviceTrustResult
fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel
}

View File

@@ -30,9 +30,9 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification
*/
interface VerificationService {
fun addListener(listener: VerificationListener)
fun addListener(listener: Listener)
fun removeListener(listener: VerificationListener)
fun removeListener(listener: Listener)
/**
* Mark this device as verified manually
@@ -68,11 +68,11 @@ interface VerificationService {
otherDevices: List<String>?): PendingVerificationRequest
fun declineVerificationRequestInDMs(otherUserId: String,
otherDeviceId: String,
transactionId: String,
roomId: String)
// Only SAS method is supported for the moment
// TODO Parameter otherDeviceId should be removed in this case
fun beginKeyVerificationInDMs(method: VerificationMethod,
transactionId: String,
roomId: String,
@@ -95,15 +95,33 @@ interface VerificationService {
otherUserId: String,
transactionId: String): Boolean
// fun transactionUpdated(tx: SasVerificationTransaction)
interface VerificationListener {
fun transactionCreated(tx: VerificationTransaction)
fun transactionUpdated(tx: VerificationTransaction)
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
interface Listener {
/**
* Called when a verification request is created either by the user, or by the other user.
*/
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
/**
* Called when a verification request is updated.
*/
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
/**
* Called when a transaction is created, either by the user or initiated by the other user.
*/
fun transactionCreated(tx: VerificationTransaction) {}
/**
* Called when a transaction is updated. You may be interested to track the state of the VerificationTransaction.
*/
fun transactionUpdated(tx: VerificationTransaction) {}
/**
* Inform the the deviceId of the userId has been marked as manually verified by the SDK.
* It will be called after VerificationService.markedLocallyAsManuallyVerified() is called.
*
*/
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
companion object {

View File

@@ -157,6 +157,11 @@ data class Event(
*/
fun isRedacted() = unsignedData?.redactedEvent != null
/**
* Tells if the event is redacted by the user himself.
*/
fun isRedactedBySameUser() = senderId == unsignedData?.redactedEvent?.senderId
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@@ -200,7 +205,7 @@ data class Event(
fun Event.isTextMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_NOTICE -> true
@@ -210,7 +215,7 @@ fun Event.isTextMessage(): Boolean {
fun Event.isImageMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
MessageType.MSGTYPE_IMAGE -> true
else -> false
}

View File

@@ -19,11 +19,12 @@ package im.vector.matrix.android.api.session.events.model
* Constants defining known event relation types from Matrix specifications
*/
object RelationType {
/** Lets you define an event which annotates an existing event.*/
const val ANNOTATION = "m.annotation"
/** Lets you define an event which replaces an existing event.*/
const val REPLACE = "m.replace"
/** Lets you define an event which references an existing event.*/
const val REFERENCE = "m.reference"
/** Lets you define an event which adds a response to an existing event.*/
const val RESPONSE = "m.response"
}

View File

@@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.pushers
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import java.util.UUID
interface PushersService {
@@ -28,19 +29,32 @@ interface PushersService {
/**
* Add a new HTTP pusher.
* Note that only `http` kind is supported by the SDK for now.
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
*
* @param pushkey the pushkey
* @param pushkey This is a unique identifier for this pusher. The value you should use for
* this is the routing or destination address information for the notification,
* for example, the APNS token for APNS or the Registration ID for GCM. If your
* notification client has no such concept, use any unique identifier. Max length, 512 chars.
* If the kind is "email", this is the email address to send notifications to.
* @param appId the application id
* @param profileTag the profile tag
* @param lang the language
* @param appDisplayName a human-readable application name
* @param deviceDisplayName a human-readable device name
* @param url the URL that should be used to send notifications
* @param append append the pusher
* @param withEventIdOnly true to limit the push content
* This is a reverse-DNS style identifier for the application. It is recommended
* that this end with the platform, such that different platform versions get
* different app identifiers. Max length, 64 chars.
* @param profileTag This string determines which set of device specific rules this pusher executes.
* @param lang The preferred language for receiving notifications (e.g. "en" or "en-US").
* @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher.
* @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher.
* @param url The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify.
* @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition
* to any others with different user IDs. Otherwise, the homeserver must remove any other pushers
* with the same App ID and pushkey for different users.
* @param withEventIdOnly true to limit the push content to only id and not message content
* Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour
*
* @return A work request uuid. Can be used to listen to the status
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
* @return A work request uuid. Can be used to listen to the status
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
* @throws [InvalidParameterException] if a parameter is not correct
*/
fun addHttpPusher(pushkey: String,
appId: String,
@@ -52,13 +66,18 @@ interface PushersService {
append: Boolean,
withEventIdOnly: Boolean): UUID
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>)
companion object {
const val EVENT_ID_ONLY = "event_id_only"
}
/**
* Remove the http pusher
*/
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Get the current pushers, as a LiveData
*/
fun getPushersLive(): LiveData<List<Pusher>>
fun pushers() : List<Pusher>
/**
* Get the current pushers
*/
fun getPushers(): List<Pusher>
}

View File

@@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.room.crypto
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
interface RoomCryptoService {
@@ -26,5 +27,9 @@ interface RoomCryptoService {
fun shouldEncryptForInvitedMembers(): Boolean
fun enableEncryptionWithAlgorithm(algorithm: String, callback: MatrixCallback<Unit>)
/**
* Enable encryption of the room
*/
fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM,
callback: MatrixCallback<Unit>)
}

View File

@@ -19,5 +19,6 @@ data class EventAnnotationsSummary(
var eventId: String,
var reactionsSummary: List<ReactionAggregatedSummary>,
var editSummary: EditAggregatedSummary?,
var pollResponseSummary: PollResponseAggregatedSummary?,
var referencesAggregatedSummary: ReferencesAggregatedSummary? = null
)

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model
data class PollResponseAggregatedSummary(
var aggregatedContent: PollSummaryContent? = null,
// If set the poll is closed (Clients SHOULD NOT consider responses after the close event)
var closedTime: Long? = null,
// Clients SHOULD validate that the option in the relationship is a valid option, and ignore the response if invalid
var nbOptions: Int = 0,
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
val sourceEvents: List<String>,
val localEchos: List<String>
)

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.JsonClass
/**
* Contains an aggregated summary info of the poll response.
* Put pre-computed info that you want to access quickly without having
* to go through all references events
*/
@JsonClass(generateAdapter = true)
data class PollSummaryContent(
// Index of my vote
var myVote: Int? = null,
// Array of VoteInfo, list is constructed so that there is only one vote by user
// And that optionIndex is valid
var votes: List<VoteInfo>? = null
) {
fun voteCount(): Int {
return votes?.size ?: 0
}
fun voteCountForOption(optionIndex: Int) : Int {
return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0
}
}
@JsonClass(generateAdapter = true)
data class VoteInfo(
val userId: String,
val optionIndex: Int,
val voteTimestamp: Long
)

View File

@@ -18,9 +18,28 @@ package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.Json
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
*/
enum class RoomHistoryVisibility {
/**
* All events while this is the m.room.history_visibility value may be shared by any
* participating homeserver with anyone, regardless of whether they have ever joined the room.
*/
@Json(name = "world_readable") WORLD_READABLE,
/**
* Previous events are always accessible to newly joined members. All events in the
* room are accessible, even those sent when the member was not a part of the room.
*/
@Json(name = "shared") SHARED,
/**
* Events are accessible to newly joined members from the point they were invited onwards.
* Events stop being accessible when the member's state changes to something other than invite or join.
*/
@Json(name = "invited") INVITED,
@Json(name = "joined") JOINED,
@Json(name = "world_readable") WORLD_READABLE
/**
* Events are accessible to newly joined members from the point they joined the room onwards.
* Events stop being accessible when the member's state changes to something other than join.
*/
@Json(name = "joined") JOINED
}

View File

@@ -16,8 +16,6 @@
package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
/**
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
*/
@@ -25,7 +23,5 @@ data class RoomMemberSummary constructor(
val membership: Membership,
val userId: String,
val displayName: String? = null,
val avatarUrl: String? = null,
// TODO Warning: Will not be populated if not using RxRoom
val userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
val avatarUrl: String? = null
)

View File

@@ -25,9 +25,9 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
@JsonClass(generateAdapter = true)
data class MessageAudioContent(
/**
* Not documented
* Required. Must be 'm.audio'.
*/
@Json(name = "msgtype") override val type: String,
@Json(name = "msgtype") override val msgType: String,
/**
* Required. A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'.
@@ -40,7 +40,7 @@ data class MessageAudioContent(
@Json(name = "info") val audioInfo: AudioInfo? = null,
/**
* Required. Required if the file is not encrypted. The URL (typically MXC URI) to the audio clip.
* Required if the file is not encrypted. The URL (typically MXC URI) to the audio clip.
*/
@Json(name = "url") override val url: String? = null,

View File

@@ -20,8 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
interface MessageContent {
// TODO Rename to msgType
val type: String
val msgType: String
val body: String
val relatesTo: RelationDefaultContent?
val newContent: Content?

View File

@@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
@JsonClass(generateAdapter = true)
data class MessageDefaultContent(
@Json(name = "msgtype") override val type: String,
@Json(name = "msgtype") override val msgType: String,
@Json(name = "body") override val body: String,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null

View File

@@ -23,10 +23,26 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
@JsonClass(generateAdapter = true)
data class MessageEmoteContent(
@Json(name = "msgtype") override val type: String,
/**
* Required. Must be 'm.emote'.
*/
@Json(name = "msgtype") override val msgType: String,
/**
* Required. The emote action to perform.
*/
@Json(name = "body") override val body: String,
/**
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
*/
@Json(name = "format") val format: String? = null,
/**
* The formatted version of the body. This is required if format is specified.
*/
@Json(name = "formatted_body") val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent

View File

@@ -23,10 +23,13 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
*/
interface MessageEncryptedContent : MessageContent {
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
* Required if the file is unencrypted. The URL (typically MXC URI) to the image.
*/
val url: String?
/**
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/
val encryptedFileInfo: EncryptedFileInfo?
}

View File

@@ -26,9 +26,9 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
@JsonClass(generateAdapter = true)
data class MessageFileContent(
/**
* Not documented
* Required. Must be 'm.file'.
*/
@Json(name = "msgtype") override val type: String,
@Json(name = "msgtype") override val msgType: String,
/**
* Required. A human-readable description of the file. This is recommended to be the filename of the original upload.
@@ -46,13 +46,16 @@ data class MessageFileContent(
@Json(name = "info") val info: FileInfo? = null,
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the file.
* Required if the file is unencrypted. The URL (typically MXC URI) to the file.
*/
@Json(name = "url") override val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null,
/**
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.room.detail
package im.vector.matrix.android.api.session.room.model.message
data class FileTooBigError(
val filename: String,
val fileSizeInBytes: Long,
val homeServerLimitInBytes: Long
)
object MessageFormat {
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
}

View File

@@ -27,7 +27,7 @@ data class MessageImageContent(
/**
* Required. Must be 'm.image'.
*/
@Json(name = "msgtype") override val type: String,
@Json(name = "msgtype") override val msgType: String,
/**
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
@@ -41,7 +41,7 @@ data class MessageImageContent(
@Json(name = "info") override val info: ImageInfo? = null,
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
* Required if the file is unencrypted. The URL (typically MXC URI) to the image.
*/
@Json(name = "url") override val url: String? = null,

View File

@@ -24,9 +24,9 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
@JsonClass(generateAdapter = true)
data class MessageLocationContent(
/**
* Not documented
* Required. Must be 'm.location'.
*/
@Json(name = "msgtype") override val type: String,
@Json(name = "msgtype") override val msgType: String,
/**
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'.

View File

@@ -23,10 +23,26 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
@JsonClass(generateAdapter = true)
data class MessageNoticeContent(
@Json(name = "msgtype") override val type: String,
/**
* Required. Must be 'm.notice'.
*/
@Json(name = "msgtype") override val msgType: String,
/**
* Required. The notice text to send.
*/
@Json(name = "body") override val body: String,
/**
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
*/
@Json(name = "format") val format: String? = null,
/**
* The formatted version of the body. This is required if format is specified.
*/
@Json(name = "formatted_body") val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
// Possible values for optionType
const val OPTION_TYPE_POLL = "org.matrix.poll"
const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
/**
* Polls and bot buttons are m.room.message events with a msgtype of m.options,
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
*/
@JsonClass(generateAdapter = true)
data class MessageOptionsContent(
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_OPTIONS,
@Json(name = "type") val optionType: String? = null,
@Json(name = "body") override val body: String,
@Json(name = "label") val label: String?,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "options") val options: List<OptionItem>? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
/**
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
*/
@JsonClass(generateAdapter = true)
data class MessagePollResponseContent(
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_RESPONSE,
@Json(name = "body") override val body: String,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent

View File

@@ -12,7 +12,6 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.api.session.room.model.message
@@ -28,7 +27,7 @@ data class MessageStickerContent(
/**
* Set in local, not from server
*/
override val type: String = MessageType.MSGTYPE_STICKER_LOCAL,
override val msgType: String = MessageType.MSGTYPE_STICKER_LOCAL,
/**
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
@@ -42,7 +41,7 @@ data class MessageStickerContent(
@Json(name = "info") override val info: ImageInfo? = null,
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
* Required if the file is unencrypted. The URL (typically MXC URI) to the image.
*/
@Json(name = "url") override val url: String? = null,

View File

@@ -23,10 +23,26 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
@JsonClass(generateAdapter = true)
data class MessageTextContent(
@Json(name = "msgtype") override val type: String,
/**
* Required. Must be 'm.text'.
*/
@Json(name = "msgtype") override val msgType: String,
/**
* Required. The body of the message.
*/
@Json(name = "body") override val body: String,
/**
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
*/
@Json(name = "format") val format: String? = null,
/**
* The formatted version of the body. This is required if format is specified.
*/
@Json(name = "formatted_body") val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent

View File

@@ -17,7 +17,6 @@
package im.vector.matrix.android.api.session.room.model.message
object MessageType {
const val MSGTYPE_TEXT = "m.text"
const val MSGTYPE_EMOTE = "m.emote"
const val MSGTYPE_NOTICE = "m.notice"
@@ -26,8 +25,10 @@ object MessageType {
const val MSGTYPE_VIDEO = "m.video"
const val MSGTYPE_LOCATION = "m.location"
const val MSGTYPE_FILE = "m.file"
const val MSGTYPE_OPTIONS = "org.matrix.options"
const val MSGTYPE_RESPONSE = "org.matrix.response"
const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed"
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
// Because sticker isn't a message type but a event type without msgtype field
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"

View File

@@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReq
@JsonClass(generateAdapter = true)
data class MessageVerificationRequestContent(
@Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
@Json(name = "body") override val body: String,
@Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>,

View File

@@ -27,7 +27,7 @@ data class MessageVideoContent(
/**
* Required. Must be 'm.video'.
*/
@Json(name = "msgtype") override val type: String,
@Json(name = "msgtype") override val msgType: String,
/**
* Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
@@ -40,7 +40,7 @@ data class MessageVideoContent(
@Json(name = "info") val videoInfo: VideoInfo? = null,
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the video clip.
* Required if the file is unencrypted. The URL (typically MXC URI) to the video clip.
*/
@Json(name = "url") override val url: String? = null,

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
*/
@JsonClass(generateAdapter = true)
data class OptionItem(
@Json(name = "label") val label: String?,
@Json(name = "value") val value: String?
)

View File

@@ -25,5 +25,6 @@ data class ReactionInfo(
@Json(name = "event_id") override val eventId: String,
val key: String,
// always null for reaction
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
@Json(name = "option") override val option: Int? = null
) : RelationContent

View File

@@ -23,4 +23,5 @@ interface RelationContent {
val type: String?
val eventId: String?
val inReplyTo: ReplyToContent?
val option: Int?
}

View File

@@ -22,5 +22,6 @@ import com.squareup.moshi.JsonClass
data class RelationDefaultContent(
@Json(name = "rel_type") override val type: String?,
@Json(name = "event_id") override val eventId: String?,
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
@Json(name = "option") override val option: Int? = null
) : RelationContent

View File

@@ -26,10 +26,16 @@ import im.vector.matrix.android.api.util.Optional
*/
interface ReadService {
enum class MarkAsReadParams {
READ_RECEIPT,
READ_MARKER,
BOTH
}
/**
* Force the read marker to be set on the latest event.
*/
fun markAllAsRead(callback: MatrixCallback<Unit>)
fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, callback: MatrixCallback<Unit>)
/**
* Set the read receipt on the event with provided eventId.

View File

@@ -17,18 +17,20 @@
package im.vector.matrix.android.api.session.room.send
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
interface DraftService {
/**
* Save or update a draft to the room
*/
fun saveDraft(draft: UserDraft)
fun saveDraft(draft: UserDraft, callback: MatrixCallback<Unit>): Cancelable
/**
* Delete the last draft, basically just after sending the message
*/
fun deleteDraft()
fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable
/**
* Return the current drafts if any, as a live data

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.model.message.OptionItem
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable
@@ -62,7 +63,24 @@ interface SendService {
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
/**
* Redacts (delete) the given event.
* Send a poll to the room.
* @param question the question
* @param options list of (label, value)
* @return a [Cancelable]
*/
fun sendPoll(question: String, options: List<OptionItem>): Cancelable
/**
* Method to send a poll response.
* @param pollEventId the poll currently replied to
* @param optionIndex The reply index
* @param optionValue The option value (for compatibility)
* @return a [Cancelable]
*/
fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
/**
* Redact (delete) the given event.
* @param event The event to redact
* @param reason Optional reason string
*/

View File

@@ -28,12 +28,7 @@ interface StateService {
*/
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
/**
* Enable encryption of the room
*/
fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>)
fun getStateEvent(eventType: String, stateKey: String): Event?
fun getStateEvent(eventType: String): Event?
fun getStateEventLive(eventType: String): LiveData<Optional<Event>>
fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>>
}

View File

@@ -112,6 +112,11 @@ interface Timeline {
* Called whenever an error we can't recover from occurred
*/
fun onTimelineFailure(throwable: Throwable)
/**
* Called when new events come through the sync
*/
fun onNewTimelineEvents(eventIds: List<String>)
}
/**

View File

@@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventConten
data class TimelineEvent(
val root: Event,
val localId: Long,
val eventId: String,
val displayIndex: Int,
val senderName: String?,
val isUniqueDisplayName: Boolean,

View File

@@ -19,10 +19,11 @@ package im.vector.matrix.android.internal.crypto
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.crosssigning.ComputeTrustTask
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultComputeTrustTask
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
@@ -137,15 +138,6 @@ internal abstract class CryptoModule {
return RealmClearCacheTask(realmConfiguration)
}
@JvmStatic
@Provides
fun providesCryptoStore(@CryptoDatabase
realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore {
return RealmCryptoStore(
realmConfiguration,
credentials)
}
@JvmStatic
@Provides
@SessionScope
@@ -159,101 +151,98 @@ internal abstract class CryptoModule {
fun providesRoomKeysAPI(retrofit: Retrofit): RoomKeysApi {
return retrofit.create(RoomKeysApi::class.java)
}
@JvmStatic
@Provides
@SessionScope
fun providesCryptoConfig(): MXCryptoConfig {
return MXCryptoConfig()
}
}
@Binds
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
abstract fun bindCryptoService(service: DefaultCryptoService): CryptoService
@Binds
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask
abstract fun bindDeleteDeviceTask(task: DefaultDeleteDeviceTask): DeleteDeviceTask
@Binds
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
abstract fun bindGetDevicesTask(task: DefaultGetDevicesTask): GetDevicesTask
@Binds
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
@Binds
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask
abstract fun bindSetDeviceNameTask(task: DefaultSetDeviceNameTask): SetDeviceNameTask
@Binds
abstract fun bindUploadKeysTask(uploadKeysTask: DefaultUploadKeysTask): UploadKeysTask
abstract fun bindUploadKeysTask(task: DefaultUploadKeysTask): UploadKeysTask
@Binds
abstract fun bindUploadSigningKeysTask(uploadKeysTask: DefaultUploadSigningKeysTask): UploadSigningKeysTask
abstract fun bindUploadSigningKeysTask(task: DefaultUploadSigningKeysTask): UploadSigningKeysTask
@Binds
abstract fun bindUploadSignaturesTask(uploadSignaturesTask: DefaultUploadSignaturesTask): UploadSignaturesTask
abstract fun bindUploadSignaturesTask(task: DefaultUploadSignaturesTask): UploadSignaturesTask
@Binds
abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsersTask: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
abstract fun bindDownloadKeysForUsersTask(task: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
@Binds
abstract fun bindCreateKeysBackupVersionTask(createKeysBackupVersionTask: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
abstract fun bindCreateKeysBackupVersionTask(task: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
@Binds
abstract fun bindDeleteBackupTask(deleteBackupTask: DefaultDeleteBackupTask): DeleteBackupTask
abstract fun bindDeleteBackupTask(task: DefaultDeleteBackupTask): DeleteBackupTask
@Binds
abstract fun bindDeleteRoomSessionDataTask(deleteRoomSessionDataTask: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
abstract fun bindDeleteRoomSessionDataTask(task: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
@Binds
abstract fun bindDeleteRoomSessionsDataTask(deleteRoomSessionsDataTask: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
abstract fun bindDeleteRoomSessionsDataTask(task: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
@Binds
abstract fun bindDeleteSessionsDataTask(deleteSessionsDataTask: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
abstract fun bindDeleteSessionsDataTask(task: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
@Binds
abstract fun bindGetKeysBackupLastVersionTask(getKeysBackupLastVersionTask: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
abstract fun bindGetKeysBackupLastVersionTask(task: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
@Binds
abstract fun bindGetKeysBackupVersionTask(getKeysBackupVersionTask: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
abstract fun bindGetKeysBackupVersionTask(task: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
@Binds
abstract fun bindGetRoomSessionDataTask(getRoomSessionDataTask: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
abstract fun bindGetRoomSessionDataTask(task: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
@Binds
abstract fun bindGetRoomSessionsDataTask(getRoomSessionsDataTask: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
abstract fun bindGetRoomSessionsDataTask(task: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
@Binds
abstract fun bindGetSessionsDataTask(getSessionsDataTask: DefaultGetSessionsDataTask): GetSessionsDataTask
abstract fun bindGetSessionsDataTask(task: DefaultGetSessionsDataTask): GetSessionsDataTask
@Binds
abstract fun bindStoreRoomSessionDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
abstract fun bindStoreRoomSessionDataTask(task: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
@Binds
abstract fun bindStoreRoomSessionsDataTask(storeRoomSessionsDataTask: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
abstract fun bindStoreRoomSessionsDataTask(task: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
@Binds
abstract fun bindStoreSessionsDataTask(storeSessionsDataTask: DefaultStoreSessionsDataTask): StoreSessionsDataTask
abstract fun bindStoreSessionsDataTask(task: DefaultStoreSessionsDataTask): StoreSessionsDataTask
@Binds
abstract fun bindUpdateKeysBackupVersionTask(updateKeysBackupVersionTask: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
abstract fun bindUpdateKeysBackupVersionTask(task: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
@Binds
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
abstract fun bindSendToDeviceTask(task: DefaultSendToDeviceTask): SendToDeviceTask
@Binds
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
abstract fun bindEncryptEventTask(task: DefaultEncryptEventTask): EncryptEventTask
@Binds
abstract fun bindSendVerificationMessageTask(sendDefaultSendVerificationMessageTask: DefaultSendVerificationMessageTask): SendVerificationMessageTask
abstract fun bindSendVerificationMessageTask(task: DefaultSendVerificationMessageTask): SendVerificationMessageTask
@Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
: ClaimOneTimeKeysForUsersDeviceTask
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
@Binds
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask)
: DeleteDeviceWithUserPasswordTask
abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
@Binds
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
@Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
@Binds
abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask
}

View File

@@ -26,7 +26,9 @@ import com.squareup.moshi.Types
import com.zhuinden.monarchy.Monarchy
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.NoOpMatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.crypto.MXCryptoConfig
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.crypto.CryptoService
@@ -70,7 +72,7 @@ import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.database.query.whereType
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope
@@ -116,7 +118,7 @@ internal class DefaultCryptoService @Inject constructor(
// Olm device
private val olmDevice: MXOlmDevice,
// Set of parameters used to configure/customize the end-to-end crypto.
private val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
private val mxCryptoConfig: MXCryptoConfig,
// Device list manager
private val deviceListManager: DeviceListManager,
// The key backup service.
@@ -189,7 +191,7 @@ internal class DefaultCryptoService @Inject constructor(
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// bg refresh of crypto device
downloadKeys(listOf(credentials.userId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {})
downloadKeys(listOf(credentials.userId), true, NoOpMatrixCallback())
callback.onSuccess(data)
}
@@ -399,6 +401,7 @@ internal class DefaultCryptoService @Inject constructor(
null
}
}
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
}
@@ -531,7 +534,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
override fun isRoomEncrypted(roomId: String): Boolean {
val encryptionEvent = monarchy.fetchCopied { realm ->
EventEntity.where(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
.findFirst()
}
@@ -545,8 +548,8 @@ internal class DefaultCryptoService @Inject constructor(
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
}
fun isEncryptionEnabledForInvitedUser(): Boolean {
return cryptoConfig.enableEncryptionForInvitedMembers
private fun isEncryptionEnabledForInvitedUser(): Boolean {
return mxCryptoConfig.enableEncryptionForInvitedMembers
}
override fun getEncryptionAlgorithm(roomId: String): String? {
@@ -779,7 +782,7 @@ internal class DefaultCryptoService @Inject constructor(
deviceListManager.startTrackingDeviceList(listOf(userId))
} else if (membership == Membership.INVITE
&& shouldEncryptForInvitedMembers(roomId)
&& cryptoConfig.enableEncryptionForInvitedMembers) {
&& isEncryptionEnabledForInvitedUser()) {
// track the deviceList for this invited user.
// Caution: there's a big edge case here in that federated servers do not
// know what other servers are in the room at the time they've been invited.

View File

@@ -27,6 +27,9 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@@ -36,10 +39,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
private val olmDevice: MXOlmDevice,
private val syncTokenStore: SyncTokenStore,
private val credentials: Credentials,
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
coroutineDispatchers: MatrixCoroutineDispatchers,
taskExecutor: TaskExecutor) {
interface UserDevicesUpdateListener {
fun onUsersDeviceUpdate(users: List<String>)
fun onUsersDeviceUpdate(userIds: List<String>)
}
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
@@ -72,17 +77,19 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
private val notReadyToRetryHS = mutableSetOf<String>()
init {
var isUpdated = false
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
for ((userId, status) in deviceTrackingStatuses) {
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
// if a download was in progress when we got shut down, it isn't any more.
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
isUpdated = true
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
var isUpdated = false
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
for ((userId, status) in deviceTrackingStatuses) {
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
// if a download was in progress when we got shut down, it isn't any more.
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
isUpdated = true
}
}
if (isUpdated) {
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
}
}
if (isUpdated) {
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
}
}
@@ -320,7 +327,6 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
// al devices =
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
if (!models.isNullOrEmpty()) {
val workingCopy = models.toMutableMap()

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncryptionTrustLevel> {
data class Params(
val userIds: List<String>
)
}
internal class DefaultComputeTrustTask @Inject constructor(
val cryptoStore: IMXCryptoStore
) : ComputeTrustTask {
override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel {
val allTrustedUserIds = params.userIds
.filter { userId -> getUserCrossSigningKeys(userId)?.isTrusted() == true }
return if (allTrustedUserIds.isEmpty()) {
RoomEncryptionTrustLevel.Default
} else {
// If one of the verified user as an untrusted device -> warning
// If all devices of all verified users are trusted -> green
// else -> black
allTrustedUserIds
.mapNotNull { cryptoStore.getUserDeviceList(it) }
.flatten()
.let { allDevices ->
if (getMyCrossSigningKeys() != null) {
allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
} else {
// Legacy method
allDevices.any { !it.isVerified }
}
}
.let { hasWarning ->
if (hasWarning) {
RoomEncryptionTrustLevel.Warning
} else {
if (params.userIds.size == allTrustedUserIds.size) {
// all users are trusted and all devices are verified
RoomEncryptionTrustLevel.Trusted
} else {
RoomEncryptionTrustLevel.Default
}
}
}
}
}
private fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return cryptoStore.getCrossSigningInfo(otherUserId)
}
private fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
return cryptoStore.getMyCrossSigningInfo()
}
}

View File

@@ -19,8 +19,6 @@ package im.vector.matrix.android.internal.crypto.crosssigning
import androidx.lifecycle.LiveData
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.util.Optional
@@ -36,13 +34,15 @@ import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskConstraints
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.withoutPrefix
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.matrix.olm.OlmPkSigning
import org.matrix.olm.OlmUtility
import timber.log.Timber
@@ -57,9 +57,11 @@ internal class DefaultCrossSigningService @Inject constructor(
private val deviceListManager: DeviceListManager,
private val uploadSigningKeysTask: UploadSigningKeysTask,
private val uploadSignaturesTask: UploadSignaturesTask,
private val cryptoCoroutineScope: CoroutineScope,
private val computeTrustTask: ComputeTrustTask,
private val taskExecutor: TaskExecutor,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
private val cryptoCoroutineScope: CoroutineScope,
private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
private var olmUtility: OlmUtility? = null
@@ -211,7 +213,7 @@ internal class DefaultCrossSigningService @Inject constructor(
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
uploadSigningKeysTask.configureWith(params) {
this.constraints = TaskConstraints(true)
this.executionThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.i("## CrossSigning - Keys successfully uploaded")
@@ -247,7 +249,7 @@ internal class DefaultCrossSigningService @Inject constructor(
resetTrustOnKeyChange()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
// this.retryCount = 3
this.constraints = TaskConstraints(true)
this.executionThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.i("## CrossSigning - signatures successfully uploaded")
@@ -396,7 +398,7 @@ internal class DefaultCrossSigningService @Inject constructor(
return@forEach
} catch (failure: Throwable) {
// log
Timber.v(failure)
Timber.w(failure, "Signature not valid?")
}
}
}
@@ -500,6 +502,7 @@ internal class DefaultCrossSigningService @Inject constructor(
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
.build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}.executeBy(taskExecutor)
}
@@ -546,6 +549,7 @@ internal class DefaultCrossSigningService @Inject constructor(
.withDeviceInfo(toUpload)
.build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}.executeBy(taskExecutor)
}
@@ -611,81 +615,52 @@ internal class DefaultCrossSigningService @Inject constructor(
}
}
override fun onUsersDeviceUpdate(users: List<String>) {
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users")
users.forEach { otherUserId ->
override fun onUsersDeviceUpdate(userIds: List<String>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users")
userIds.forEach { otherUserId ->
checkUserTrust(otherUserId).let {
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
setUserKeysAsTrusted(otherUserId, it.isVerified())
}
checkUserTrust(otherUserId).let {
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
setUserKeysAsTrusted(otherUserId, it.isVerified())
// TODO if my keys have changes, i should recheck all devices of all users?
val devices = cryptoStore.getUserDeviceList(otherUserId)
devices?.forEach { device ->
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
}
if (otherUserId == userId) {
// It's me, i should check if a newly trusted device is signing my master key
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
}
}
// TODO if my keys have changes, i should recheck all devices of all users?
val devices = cryptoStore.getUserDeviceList(otherUserId)
devices?.forEach { device ->
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
}
if (otherUserId == userId) {
// It's me, i should check if a newly trusted device is signing my master key
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
}
eventBus.post(CryptoToSessionUserTrustChange(userIds))
}
}
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
// If it's me, recheck trust of all users and devices?
val users = ArrayList<String>()
if (otherUserId == userId && currentTrust != trusted) {
cryptoStore.updateUsersTrust {
users.add(it)
checkUserTrust(it).isVerified()
}
users.forEach {
cryptoStore.getUserDeviceList(it)?.forEach { device ->
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
// If it's me, recheck trust of all users and devices?
val users = ArrayList<String>()
if (otherUserId == userId && currentTrust != trusted) {
cryptoStore.updateUsersTrust {
users.add(it)
checkUserTrust(it).isVerified()
}
}
}
}
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
val allTrusted = userIds
.filter { getUserCrossSigningKeys(it)?.isTrusted() == true }
val allUsersAreVerified = userIds.size == allTrusted.size
return if (allTrusted.isEmpty()) {
RoomEncryptionTrustLevel.Default
} else {
// If one of the verified user as an untrusted device -> warning
// Green if all devices of all verified users are trusted -> green
// else black
val allDevices = allTrusted.mapNotNull {
cryptoStore.getUserDeviceList(it)
}.flatten()
if (getMyCrossSigningKeys() != null) {
val hasWarning = allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
if (hasWarning) {
RoomEncryptionTrustLevel.Warning
} else {
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
}
} else {
val hasWarningLegacy = allDevices.any { !it.isVerified }
if (hasWarningLegacy) {
RoomEncryptionTrustLevel.Warning
} else {
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
users.forEach {
cryptoStore.getUserDeviceList(it)?.forEach { device ->
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
}
}
}
}

View File

@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
package im.vector.riotx.features.home.room.detail
data class SessionToCryptoRoomMembersUpdate(
val roomId: String,
val userIds: List<String>
)
import java.io.File
data class DownloadFileState(
val mimeType: String,
val file: File?,
val throwable: Throwable?
)
data class CryptoToSessionUserTrustChange(
val userIds: List<String>
)

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.createBackgroundHandler
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
internal class ShieldTrustUpdater @Inject constructor(
private val eventBus: EventBus,
private val computeTrustTask: ComputeTrustTask,
private val taskExecutor: TaskExecutor,
@CryptoDatabase private val cryptoRealmConfiguration: RealmConfiguration,
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration,
private val roomSummaryUpdater: RoomSummaryUpdater
) {
companion object {
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
}
private val backgroundCryptoRealm = AtomicReference<Realm>()
private val backgroundSessionRealm = AtomicReference<Realm>()
// private var cryptoDevicesResult: RealmResults<DeviceInfoEntity>? = null
// private val cryptoDeviceChangeListener = object : OrderedRealmCollectionChangeListener<RealmResults<DeviceInfoEntity>> {
// override fun onChange(t: RealmResults<DeviceInfoEntity>, changeSet: OrderedCollectionChangeSet) {
// val grouped = t.groupBy { it.userId }
// onCryptoDevicesChange(grouped.keys.mapNotNull { it })
// }
// }
fun start() {
eventBus.register(this)
BACKGROUND_HANDLER.post {
val cryptoRealm = Realm.getInstance(cryptoRealmConfiguration)
backgroundCryptoRealm.set(cryptoRealm)
// cryptoDevicesResult = cryptoRealm.where<DeviceInfoEntity>().findAll()
// cryptoDevicesResult?.addChangeListener(cryptoDeviceChangeListener)
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
}
}
fun stop() {
eventBus.unregister(this)
BACKGROUND_HANDLER.post {
// cryptoDevicesResult?.removeAllChangeListeners()
backgroundCryptoRealm.getAndSet(null).also {
it?.close()
}
backgroundSessionRealm.getAndSet(null).also {
it?.close()
}
}
}
@Subscribe
fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) {
taskExecutor.executorScope.launch {
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
// We need to send that back to session base
BACKGROUND_HANDLER.post {
backgroundSessionRealm.get().executeTransaction { realm ->
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
}
}
}
}
@Subscribe
fun onTrustUpdate(update: CryptoToSessionUserTrustChange) {
onCryptoDevicesChange(update.userIds)
}
private fun onCryptoDevicesChange(users: List<String>) {
BACKGROUND_HANDLER.post {
val impactedRoomsId = backgroundSessionRealm.get().where(RoomMemberSummaryEntity::class.java)
.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
.findAll()
.map { it.roomId }
.distinct()
val map = HashMap<String, List<String>>()
impactedRoomsId.forEach { roomId ->
RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId)
.findAll()
.let { results ->
map[roomId] = results.map { it.userId }
}
}
map.forEach { entry ->
val roomId = entry.key
val userList = entry.value
taskExecutor.executorScope.launch {
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
BACKGROUND_HANDLER.post {
backgroundSessionRealm.get().executeTransaction { realm ->
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
}
}
}
}
}
}
}

View File

@@ -414,7 +414,7 @@ internal class KeysBackup @Inject constructor(
olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySignature)
isSignatureValid = true
} catch (e: OlmException) {
Timber.v(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}")
Timber.w(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}")
}
}

View File

@@ -61,6 +61,7 @@ import im.vector.matrix.android.internal.crypto.store.db.query.delete
import im.vector.matrix.android.internal.crypto.store.db.query.get
import im.vector.matrix.android.internal.crypto.store.db.query.getById
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.session.SessionScope
import io.realm.Realm
import io.realm.RealmConfiguration
@@ -70,11 +71,13 @@ import io.realm.kotlin.where
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmException
import timber.log.Timber
import javax.inject.Inject
import kotlin.collections.set
@SessionScope
internal class RealmCryptoStore(private val realmConfiguration: RealmConfiguration,
private val credentials: Credentials) : IMXCryptoStore {
internal class RealmCryptoStore @Inject constructor(
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
private val credentials: Credentials) : IMXCryptoStore {
/* ==========================================================================================
* Memory cache, to correctly release JNI objects
@@ -403,14 +406,14 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
{ realm: Realm ->
realm
.where<UserEntity>()
.`in`(UserEntityFields.USER_ID, userIds.toTypedArray())
.`in`(UserEntityFields.USER_ID, userIds.distinct().toTypedArray())
},
{ entity ->
entity.devices.map { CryptoMapper.mapToModel(it) }
}
)
return Transformations.map(liveData) {
it.firstOrNull() ?: emptyList()
it.flatten()
}
}

View File

@@ -88,7 +88,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// done from another device of mine
if (EventType.MESSAGE == event.type) {
val msgType = event.getClearContent().toModel<MessageContent>()?.type
val msgType = event.getClearContent().toModel<MessageContent>()?.msgType
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
if (it.fromDevice != deviceId) {
@@ -144,7 +144,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
params.verificationService.onRoomEvent(event)
}
EventType.MESSAGE -> {
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.type) {
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
params.verificationService.onRoomRequestReceived(event)
}
}

View File

@@ -37,7 +37,9 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
override suspend fun execute(params: UploadSignaturesTask.Params) {
try {
val response = executeRequest<SignatureUploadResponse>(eventBus) {
apiCall = cryptoApi.uploadSignatures(params.signatures)
this.isRetryable = true
this.maxRetryCount = 10
this.apiCall = cryptoApi.uploadSignatures(params.signatures)
}
if (response.failures?.isNotEmpty() == true) {
throw Throwable(response.failures.toString())

View File

@@ -165,7 +165,7 @@ internal class DefaultVerificationService @Inject constructor(
onRoomDoneReceived(event)
}
EventType.MESSAGE -> {
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.type) {
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
onRoomRequestReceived(event)
}
}
@@ -176,9 +176,9 @@ internal class DefaultVerificationService @Inject constructor(
}
}
private var listeners = ArrayList<VerificationService.VerificationListener>()
private var listeners = ArrayList<VerificationService.Listener>()
override fun addListener(listener: VerificationService.VerificationListener) {
override fun addListener(listener: VerificationService.Listener) {
uiHandler.post {
if (!listeners.contains(listener)) {
listeners.add(listener)
@@ -186,7 +186,7 @@ internal class DefaultVerificationService @Inject constructor(
}
}
override fun removeListener(listener: VerificationService.VerificationListener) {
override fun removeListener(listener: VerificationService.Listener) {
uiHandler.post {
listeners.remove(listener)
}
@@ -1151,9 +1151,9 @@ internal class DefaultVerificationService @Inject constructor(
return verificationRequest
}
override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) {
override fun declineVerificationRequestInDMs(otherUserId: String, transactionId: String, roomId: String) {
verificationTransportRoomMessageFactory.createTransport(roomId, null)
.cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User)
.cancelTransaction(transactionId, otherUserId, null, CancelCode.User)
getExistingVerificationRequest(otherUserId, transactionId)?.let {
updatePendingRequest(it.copy(

View File

@@ -15,8 +15,8 @@
*/
package im.vector.matrix.android.internal.crypto.verification
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
@@ -46,11 +46,37 @@ data class PendingVerificationRequest(
val isFinished: Boolean = isSuccessful || cancelConclusion != null
fun hasMethod(method: VerificationMethod): Boolean? {
return when (method) {
VerificationMethod.SAS -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS)
VerificationMethod.QR_CODE_SHOW -> readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW)
VerificationMethod.QR_CODE_SCAN -> readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN)
/**
* SAS is supported if I support it and the other party support it
*/
fun isSasSupported(): Boolean {
return requestInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
}
/**
* Other can show QR code if I can scan QR code and other can show QR code
*/
fun otherCanShowQrCode(): Boolean {
return if (isIncoming) {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
} else {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
}
}
/**
* Other can scan QR code if I can show QR code and other can scan QR code
*/
fun otherCanScanQrCode(): Boolean {
return if (isIncoming) {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
} else {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
}
}
}

View File

@@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.database.query.whereTypes
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@@ -42,7 +42,7 @@ internal class VerificationMessageLiveObserver @Inject constructor(
) : RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
override val query = Monarchy.Query {
EventEntity.types(it, listOf(
EventEntity.whereTypes(it, listOf(
EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_KEY,

View File

@@ -42,7 +42,7 @@ internal interface VerificationTransport {
fun cancelTransaction(transactionId: String,
otherUserId: String,
otherUserDeviceId: String,
otherUserDeviceId: String?,
code: CancelCode)
fun done(transactionId: String)
@@ -79,11 +79,13 @@ internal interface VerificationTransport {
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady
fun createReady(tid: String,
fromDevice: String,
methods: List<String>): VerificationInfoReady
// TODO Refactor
fun sendVerificationReady(keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String,
otherDeviceId: String?,
callback: (() -> Unit)?)
}

View File

@@ -208,7 +208,7 @@ internal class VerificationTransportRoomMessage(
}
}
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) {
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
Timber.d("## SAS canceling transaction $transactionId for reason $code")
val event = createEventAndLocalEcho(
type = EventType.KEY_VERIFICATION_CANCEL,
@@ -331,13 +331,13 @@ internal class VerificationTransportRoomMessage(
content = content,
unsignedData = UnsignedData(age = null, transactionId = localID)
).also {
localEchoEventFactory.saveLocalEcho(monarchy, it)
localEchoEventFactory.createLocalEcho(it)
}
}
override fun sendVerificationReady(keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String,
otherDeviceId: String?,
callback: (() -> Unit)?) {
// Not applicable (send event is called directly)
Timber.w("## SAS ignored verification ready with methods: ${keyReq.methods}")

View File

@@ -80,7 +80,7 @@ internal class VerificationTransportToDevice(
override fun sendVerificationReady(keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String,
otherDeviceId: String?,
callback: (() -> Unit)?) {
Timber.d("## SAS sending verification ready with methods: ${keyReq.methods}")
val contentMap = MXUsersDevicesMap<Any>()
@@ -159,7 +159,7 @@ internal class VerificationTransportToDevice(
.executeBy(taskExecutor)
}
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) {
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
Timber.d("## SAS canceling transaction $transactionId for reason $code")
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
val contentMap = MXUsersDevicesMap<Any>()

View File

@@ -16,19 +16,27 @@
package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.createObject
import timber.log.Timber
internal fun ChunkEntity.deleteOnCascade() {
assertIsManaged()
@@ -36,116 +44,154 @@ internal fun ChunkEntity.deleteOnCascade() {
this.deleteFromRealm()
}
internal fun ChunkEntity.merge(roomId: String,
chunkToMerge: ChunkEntity,
direction: PaginationDirection): List<TimelineEventEntity> {
internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direction: PaginationDirection) {
assertIsManaged()
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked
val isCurrentChunkUnlinked = isUnlinked
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
this.timelineEvents.forEach { it.root?.isUnlinked = false }
}
val localRealm = this.realm
val eventsToMerge: List<TimelineEventEntity>
if (direction == PaginationDirection.FORWARDS) {
this.nextToken = chunkToMerge.nextToken
this.isLastForward = chunkToMerge.isLastForward
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
} else {
this.prevToken = chunkToMerge.prevToken
this.isLastBackward = chunkToMerge.isLastBackward
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
}
chunkToMerge.stateEvents.forEach { stateEvent ->
addStateEvent(roomId, stateEvent, direction)
}
return eventsToMerge
.mapNotNull {
val event = it.root?.asDomain() ?: return@mapNotNull null
add(roomId, event, direction)
.forEach {
addTimelineEventFromMerge(localRealm, it, direction)
}
}
internal fun ChunkEntity.add(roomId: String,
event: Event,
direction: PaginationDirection,
stateIndexOffset: Int = 0
): TimelineEventEntity? {
assertIsManaged()
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
return null
}
var currentDisplayIndex = lastDisplayIndex(direction, 0)
if (direction == PaginationDirection.FORWARDS) {
currentDisplayIndex += 1
forwardsDisplayIndex = currentDisplayIndex
internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) {
if (direction == PaginationDirection.BACKWARDS) {
Timber.v("We don't keep chunk state events when paginating backward")
} else {
currentDisplayIndex -= 1
backwardsDisplayIndex = currentDisplayIndex
}
var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset)
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.type)) {
currentStateIndex += 1
forwardsStateIndex = currentStateIndex
} else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) {
val lastEventType = timelineEvents.last()?.root?.type ?: ""
if (EventType.isStateEvent(lastEventType)) {
currentStateIndex -= 1
backwardsStateIndex = currentStateIndex
val stateKey = stateEvent.stateKey ?: return
val type = stateEvent.type
val pastStateEvent = stateEvents.where()
.equalTo(EventEntityFields.ROOM_ID, roomId)
.equalTo(EventEntityFields.STATE_KEY, stateKey)
.equalTo(CurrentStateEventEntityFields.TYPE, type)
.findFirst()
if (pastStateEvent != null) {
stateEvents.remove(pastStateEvent)
}
stateEvents.add(stateEvent)
}
}
val isChunkUnlinked = isUnlinked
internal fun ChunkEntity.addTimelineEvent(roomId: String,
eventEntity: EventEntity,
direction: PaginationDirection,
roomMemberContentsByUser: Map<String, RoomMemberContent?>) {
val eventId = eventEntity.eventId
if (timelineEvents.find(eventId) != null) {
return
}
val displayIndex = nextDisplayIndex(direction)
val localId = TimelineEventEntity.nextId(realm)
val eventId = event.eventId ?: ""
val senderId = event.senderId ?: ""
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
?: realm.createObject<ReadReceiptsSummaryEntity>(eventId).apply {
this.roomId = roomId
}
val senderId = eventEntity.sender ?: ""
// Update RR for the sender of a new message with a dummy one
val readReceiptsSummaryEntity = handleReadReceipts(realm, roomId, eventEntity, senderId)
val timelineEventEntity = realm.createObject<TimelineEventEntity>().apply {
this.localId = localId
this.root = eventEntity
this.eventId = eventId
this.roomId = roomId
this.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
this.readReceipts = readReceiptsSummaryEntity
this.displayIndex = displayIndex
val roomMemberContent = roomMemberContentsByUser[senderId]
this.senderAvatar = roomMemberContent?.avatarUrl
this.senderName = roomMemberContent?.displayName
isUniqueDisplayName = if (roomMemberContent?.displayName != null) {
computeIsUnique(realm, roomId, isLastForward, roomMemberContent, roomMemberContentsByUser)
} else {
true
}
}
timelineEvents.add(timelineEventEntity)
}
if (event.originServerTs != null) {
val timestampOfEvent = event.originServerTs.toDouble()
private fun computeIsUnique(
realm: Realm,
roomId: String,
isLastForward: Boolean,
myRoomMemberContent: RoomMemberContent,
roomMemberContentsByUser: Map<String, RoomMemberContent?>
): Boolean {
val isHistoricalUnique = roomMemberContentsByUser.values.find {
it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName
} == null
return if (isLastForward) {
val isLiveUnique = RoomMemberSummaryEntity
.where(realm, roomId)
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, myRoomMemberContent.displayName)
.findAll().none {
!roomMemberContentsByUser.containsKey(it.userId)
}
isHistoricalUnique && isLiveUnique
} else {
isHistoricalUnique
}
}
private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) {
val eventId = timelineEventEntity.eventId
if (timelineEvents.find(eventId) != null) {
return
}
val displayIndex = nextDisplayIndex(direction)
val localId = TimelineEventEntity.nextId(realm)
val copied = realm.createObject<TimelineEventEntity>().apply {
this.localId = localId
this.root = timelineEventEntity.root
this.eventId = timelineEventEntity.eventId
this.roomId = timelineEventEntity.roomId
this.annotations = timelineEventEntity.annotations
this.readReceipts = timelineEventEntity.readReceipts
this.displayIndex = displayIndex
this.senderAvatar = timelineEventEntity.senderAvatar
this.senderName = timelineEventEntity.senderName
this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName
}
timelineEvents.add(copied)
}
private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity {
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst()
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {
this.roomId = roomId
}
val originServerTs = eventEntity.originServerTs
if (originServerTs != null) {
val timestampOfEvent = originServerTs.toDouble()
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId)
// If the synced RR is older, update
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
readReceiptOfSender.eventId = eventId
readReceiptOfSender.eventId = eventEntity.eventId
readReceiptOfSender.originServerTs = timestampOfEvent
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
}
}
val rootEvent = event.toEntity(roomId).apply {
this.stateIndex = currentStateIndex
this.displayIndex = currentDisplayIndex
this.sendState = SendState.SYNCED
this.isUnlinked = isChunkUnlinked
}
val eventEntity = realm.createObject<TimelineEventEntity>().also {
it.localId = localId
it.root = realm.copyToRealm(rootEvent)
it.eventId = eventId
it.roomId = roomId
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
it.readReceipts = readReceiptsSummaryEntity
}
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
timelineEvents.add(position, eventEntity)
return eventEntity
return readReceiptsSummaryEntity
}
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int {
return when (direction) {
PaginationDirection.FORWARDS -> forwardsDisplayIndex
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
} ?: defaultValue
}
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) {
PaginationDirection.FORWARDS -> forwardsStateIndex
PaginationDirection.BACKWARDS -> backwardsStateIndex
} ?: defaultValue
PaginationDirection.FORWARDS -> {
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1
}
PaginationDirection.BACKWARDS -> {
(timelineEvents.where().min(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) - 1
}
}
}

View File

@@ -16,15 +16,8 @@
package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity)
@@ -36,39 +29,3 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
chunks.add(chunkEntity)
}
}
internal fun RoomEntity.addStateEvent(stateEvent: Event,
stateIndex: Int = Int.MIN_VALUE,
filterDuplicates: Boolean = false,
isUnlinked: Boolean = false) {
assertIsManaged()
if (stateEvent.eventId == null || (filterDuplicates && fastContains(stateEvent.eventId))) {
return
} else {
val entity = stateEvent.toEntity(roomId).apply {
this.stateIndex = stateIndex
this.isUnlinked = isUnlinked
this.sendState = SendState.SYNCED
}
untimelinedStateEvents.add(entity)
}
}
internal fun RoomEntity.addSendingEvent(event: Event) {
assertIsManaged()
val senderId = event.senderId ?: return
val eventEntity = event.toEntity(roomId).apply {
this.sendState = SendState.UNSENT
}
val roomMembers = RoomMemberHelper(realm, roomId)
val myUser = roomMembers.getLastRoomMember(senderId)
val localId = TimelineEventEntity.nextId(realm)
val timelineEventEntity = TimelineEventEntity(localId).also {
it.root = eventEntity
it.eventId = event.eventId ?: ""
it.roomId = roomId
it.senderName = myUser?.displayName
it.senderAvatar = myUser?.avatarUrl
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
}
sendingTimelineEvents.add(0, timelineEventEntity)
}

View File

@@ -1,148 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.query.next
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import io.realm.RealmList
import io.realm.RealmQuery
import javax.inject.Inject
/**
* This is an internal cache to avoid querying all the time the room member events
*/
@SessionScope
internal class TimelineEventSenderVisitor @Inject constructor() {
internal data class Key(
val roomId: String,
val stateIndex: Int,
val senderId: String
)
internal class Value(
var senderAvatar: String? = null,
var senderName: String? = null,
var isUniqueDisplayName: Boolean = false,
var senderMembershipEventId: String? = null
)
private val values = HashMap<Key, Value>()
fun clear() {
values.clear()
}
fun clear(roomId: String, senderId: String) {
val keysToRemove = values.keys.filter { it.senderId == senderId && it.roomId == roomId }
keysToRemove.forEach {
values.remove(it)
}
}
fun visit(timelineEventEntities: List<TimelineEventEntity>) = timelineEventEntities.forEach { visit(it) }
fun visit(timelineEventEntity: TimelineEventEntity) {
if (!timelineEventEntity.isValid) {
return
}
val key = Key(
roomId = timelineEventEntity.roomId,
stateIndex = timelineEventEntity.root?.stateIndex ?: 0,
senderId = timelineEventEntity.root?.sender ?: ""
)
val result = values.getOrPut(key) {
timelineEventEntity.computeValue()
}
timelineEventEntity.apply {
this.isUniqueDisplayName = result.isUniqueDisplayName
this.senderAvatar = result.senderAvatar
this.senderName = result.senderName
this.senderMembershipEventId = result.senderMembershipEventId
}
}
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
return where()
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
}
private fun TimelineEventEntity.computeValue(): Value {
assertIsManaged()
val result = Value()
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return result
val stateIndex = root?.stateIndex ?: return result
val senderId = root?.sender ?: return result
val chunkEntity = chunk?.firstOrNull() ?: return result
val isUnlinked = chunkEntity.isUnlinked
var senderMembershipEvent: EventEntity?
var senderRoomMemberContent: String?
var senderRoomMemberPrevContent: String?
if (stateIndex <= 0) {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.prevContent
senderRoomMemberPrevContent = senderMembershipEvent?.content
} else {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.content
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
}
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
if (senderMembershipEvent == null) {
senderMembershipEvent = roomEntity.untimelinedStateEvents
.where()
.equalTo(EventEntityFields.STATE_KEY, senderId)
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
.prev(since = stateIndex)
senderRoomMemberContent = senderMembershipEvent?.content
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
}
ContentMapper.map(senderRoomMemberContent).toModel<RoomMemberContent>()?.also {
result.senderAvatar = it.avatarUrl
result.senderName = it.displayName
result.isUniqueDisplayName = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName)
}
// We try to fallback on prev content if we got a room member state events with null fields
if (root?.type == EventType.STATE_ROOM_MEMBER) {
ContentMapper.map(senderRoomMemberPrevContent).toModel<RoomMemberContent>()?.also {
if (result.senderAvatar == null && it.avatarUrl != null) {
result.senderAvatar = it.avatarUrl
}
if (result.senderName == null && it.displayName != null) {
result.senderName = it.displayName
result.isUniqueDisplayName = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName)
}
}
}
result.senderMembershipEventId = senderMembershipEvent?.eventId
return result
}
}

View File

@@ -55,7 +55,11 @@ internal object EventAnnotationsSummaryMapper {
it.sourceEvents.toList(),
it.sourceLocalEcho.toList()
)
},
pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
PollResponseAggregatedSummaryEntityMapper.map(it)
}
)
}
@@ -93,6 +97,9 @@ internal object EventAnnotationsSummaryMapper {
RealmList<String>().apply { addAll(it.localEchos) }
)
}
eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
PollResponseAggregatedSummaryEntityMapper.map(it)
}
return eventAnnotationsSummaryEntity
}
}

View File

@@ -20,6 +20,7 @@ import com.squareup.moshi.JsonDataException
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.UnsignedData
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.di.MoshiProvider
@@ -43,7 +44,6 @@ internal object EventMapper {
eventEntity.redacts = event.redacts
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
eventEntity.unsignedData = uds
eventEntity.ageLocalTs = event.ageLocalTs
return eventEntity
}
@@ -92,6 +92,9 @@ internal fun EventEntity.asDomain(): Event {
return EventMapper.map(this)
}
internal fun Event.toEntity(roomId: String): EventEntity {
return EventMapper.map(this, roomId)
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): EventEntity {
return EventMapper.map(this, roomId).apply {
this.sendState = sendState
this.ageLocalTs = ageLocalTs
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.PollResponseAggregatedSummary
import im.vector.matrix.android.internal.database.model.PollResponseAggregatedSummaryEntity
import io.realm.RealmList
internal object PollResponseAggregatedSummaryEntityMapper {
fun map(entity: PollResponseAggregatedSummaryEntity): PollResponseAggregatedSummary {
return PollResponseAggregatedSummary(
aggregatedContent = ContentMapper.map(entity.aggregatedContent).toModel(),
closedTime = entity.closedTime,
localEchos = entity.sourceLocalEchoEvents.toList(),
sourceEvents = entity.sourceEvents.toList(),
nbOptions = entity.nbOptions
)
}
fun map(model: PollResponseAggregatedSummary): PollResponseAggregatedSummaryEntity {
return PollResponseAggregatedSummaryEntity(
aggregatedContent = ContentMapper.map(model.aggregatedContent.toContent()),
nbOptions = model.nbOptions,
closedTime = model.closedTime,
sourceEvents = RealmList<String>().apply { addAll(model.sourceEvents) },
sourceLocalEchoEvents = RealmList<String>().apply { addAll(model.localEchos) }
)
}
}
internal fun PollResponseAggregatedSummaryEntity.asDomain(): PollResponseAggregatedSummary {
return PollResponseAggregatedSummaryEntityMapper.map(this)
}

View File

@@ -38,7 +38,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.event_match.name, "content.body", pushrule.pattern)
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern)
)
)
}
@@ -59,7 +59,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.event_match.name, "room_id", pushrule.ruleId)
PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.ruleId)
)
)
}
@@ -71,7 +71,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.event_match.name, "user_id", pushrule.ruleId)
PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.ruleId)
)
)
}

View File

@@ -26,8 +26,7 @@ internal object RoomMemberSummaryMapper {
userId = roomMemberSummaryEntity.userId,
avatarUrl = roomMemberSummaryEntity.avatarUrl,
displayName = roomMemberSummaryEntity.displayName,
membership = roomMemberSummaryEntity.membership,
userEncryptionTrustLevel = null
membership = roomMemberSummaryEntity.membership
)
}
}

View File

@@ -17,11 +17,11 @@
package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@@ -49,7 +49,8 @@ internal class RoomSummaryMapper @Inject constructor(
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
} catch (e: MXCryptoError) {
} catch (e: Throwable) {
Timber.d(e)
}
}
@@ -75,7 +76,8 @@ internal class RoomSummaryMapper @Inject constructor(
aliases = roomSummaryEntity.aliases.toList(),
isEncrypted = roomSummaryEntity.isEncrypted,
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel
)
}
}

View File

@@ -37,15 +37,19 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
return TimelineEvent(
root = timelineEventEntity.root?.asDomain()
?: Event("", timelineEventEntity.eventId),
eventId = timelineEventEntity.eventId,
annotations = timelineEventEntity.annotations?.asDomain(),
localId = timelineEventEntity.localId,
displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
displayIndex = timelineEventEntity.displayIndex,
senderName = timelineEventEntity.senderName,
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
senderAvatar = timelineEventEntity.senderAvatar,
readReceipts = readReceipts?.sortedByDescending {
it.originServerTs
} ?: emptyList()
readReceipts = readReceipts
?.distinctBy {
it.user
}?.sortedByDescending {
it.originServerTs
} ?: emptyList()
)
}
}

View File

@@ -24,14 +24,10 @@ import io.realm.annotations.LinkingObjects
internal open class ChunkEntity(@Index var prevToken: String? = null,
@Index var nextToken: String? = null,
var stateEvents: RealmList<EventEntity> = RealmList(),
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
@Index var isLastForward: Boolean = false,
@Index var isLastBackward: Boolean = false,
var backwardsDisplayIndex: Int? = null,
var forwardsDisplayIndex: Int? = null,
var backwardsStateIndex: Int? = null,
var forwardsStateIndex: Int? = null,
var isUnlinked: Boolean = false
@Index var isLastBackward: Boolean = false
) : RealmObject() {
fun identifier() = "${prevToken}_$nextToken"

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.internal.database.model
import io.realm.RealmObject
import io.realm.annotations.Index
internal open class CurrentStateEventEntity(var eventId: String = "",
var root: EventEntity? = null,
@Index var roomId: String = "",
@Index var type: String = "",
@Index var stateKey: String = ""
) : RealmObject() {
companion object
}

View File

@@ -25,7 +25,8 @@ internal open class EventAnnotationsSummaryEntity(
var roomId: String? = null,
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
var editSummary: EditAggregatedSummaryEntity? = null,
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
) : RealmObject() {
companion object

View File

@@ -21,11 +21,10 @@ import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.di.MoshiProvider
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
internal open class EventEntity(@Index var eventId: String = "",
internal open class EventEntity(@PrimaryKey var eventId: String = "",
@Index var roomId: String = "",
@Index var type: String = "",
var content: String? = null,
@@ -36,20 +35,11 @@ internal open class EventEntity(@Index var eventId: String = "",
var age: Long? = 0,
var unsignedData: String? = null,
var redacts: String? = null,
@Index var stateIndex: Int = 0,
@Index var displayIndex: Int = 0,
@Index var isUnlinked: Boolean = false,
var decryptionResultJson: String? = null,
var decryptionErrorCode: String? = null,
var ageLocalTs: Long? = null
) : RealmObject() {
enum class LinkFilterMode {
LINKED_ONLY,
UNLINKED_ONLY,
BOTH
}
private var sendStateStr: String = SendState.UNKNOWN.name
var sendState: SendState
@@ -62,12 +52,6 @@ internal open class EventEntity(@Index var eventId: String = "",
companion object
@LinkingObjects("untimelinedStateEvents")
val room: RealmResults<RoomEntity>? = null
@LinkingObjects("root")
val timelineEventEntity: RealmResults<TimelineEventEntity>? = null
fun setDecryptionResult(result: MXEventDecryptionResult) {
val decryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
@@ -78,6 +62,5 @@ internal open class EventEntity(@Index var eventId: String = "",
val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java)
decryptionResultJson = adapter.toJson(decryptionResult)
decryptionErrorCode = null
timelineEventEntity?.firstOrNull()?.root = this
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database.model
import io.realm.RealmList
import io.realm.RealmObject
/**
* Keep the latest state of a poll
*/
internal open class PollResponseAggregatedSummaryEntity(
// For now we persist this a JSON for greater flexibility
// #see PollSummaryContent
var aggregatedContent: String? = null,
// If set the poll is closed (Clients SHOULD NOT consider responses after the close event)
var closedTime: Long? = null,
// Clients SHOULD validate that the option in the relationship is a valid option, and ignore the response if invalid
var nbOptions: Int = 0,
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
var sourceEvents: RealmList<String> = RealmList(),
var sourceLocalEchoEvents: RealmList<String> = RealmList()
) : RealmObject() {
companion object
}

View File

@@ -23,7 +23,6 @@ import io.realm.annotations.PrimaryKey
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
var chunks: RealmList<ChunkEntity> = RealmList(),
var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
var areAllMembersLoaded: Boolean = false
) : RealmObject() {

View File

@@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.VersioningState
@@ -47,7 +48,8 @@ internal open class RoomSummaryEntity(
// this is required for querying
var flatAliases: String = "",
var isEncrypted: Boolean = false,
var typingUserIds: RealmList<String> = RealmList()
var typingUserIds: RealmList<String> = RealmList(),
var roomEncryptionTrustLevelStr: String? = null
) : RealmObject() {
private var membershipStr: String = Membership.NONE.name
@@ -68,5 +70,19 @@ internal open class RoomSummaryEntity(
versioningStateStr = value.name
}
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
get() {
return roomEncryptionTrustLevelStr?.let {
try {
RoomEncryptionTrustLevel.valueOf(it)
} catch (failure: Throwable) {
null
}
}
}
set(value) {
roomEncryptionTrustLevelStr = value?.name
}
companion object
}

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