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

Compare commits

...

482 Commits

Author SHA1 Message Date
ganfra
9ec684f438 Merge branch 'hotfix/1.4.7' into main 2022-03-24 19:33:44 +01:00
ganfra
60ecfd4fc2 Update changes 2022-03-24 19:31:29 +01:00
ganfra
8bcc2f5b0c Fix formating 2022-03-24 19:07:44 +01:00
ganfra
04b136e3e4 RoomList: more fixes on count 2022-03-24 18:50:33 +01:00
ganfra
87438085c6 RoomList: fix count not showing if not collapsable 2022-03-24 18:49:57 +01:00
ganfra
81aa42a8e8 Merge pull request #5616 from vector-im/hotfix/fre/collapse_rooms_section
Fix rooms section collapsing
2022-03-24 16:04:16 +01:00
Florian Renaud
3c73ccce7b Add changelog entry 2022-03-24 15:46:16 +01:00
Florian Renaud
1ef1bd81bc Improve room section collapsing 2022-03-24 15:46:16 +01:00
Florian Renaud
a97d3eae7e Pass lambda to updateSection method 2022-03-24 15:46:16 +01:00
Florian Renaud
a362d5427d Fix arrow visibility on section header 2022-03-24 15:46:16 +01:00
ganfra
745382cdfa RoomList : avoid using flow extension on realm results (leads to frozen object and leaks). 2022-03-24 15:41:42 +01:00
ganfra
70e5698082 Update versions to 1.4.7 2022-03-24 15:41:35 +01:00
ganfra
866a5a7e3a Merge pull request #5626 from vector-im/feature/fre/collapse_rooms_sections
Restore #5580 "Do not suggest collapse if there is only one section"
2022-03-24 15:39:39 +01:00
Florian Renaud
1a0bd3f31e Revert "Revert "Do not suggest collapse if there is only one section""
This reverts commit 55b1a60f96.
2022-03-24 15:27:35 +01:00
ganfra
c2d2afbe72 Merge pull request #5624 from vector-im/feature/adm/account-created-celebration-on-sign-in-with-id
Fixing account creation celebration shown on `sign in with matrix id`
2022-03-24 11:52:23 +01:00
Adam Brown
4ef0bc9052 fixing wrong account created flag when creating a session from a direct login 2022-03-24 10:19:53 +00:00
ganfra
7c27ce5e88 Merge branch 'release/1.4.6' into main 2022-03-23 16:24:01 +01:00
ganfra
55b1a60f96 Revert "Do not suggest collapse if there is only one section"
This reverts commit 6787980185.
2022-03-23 16:09:05 +01:00
ganfra
4755ebfa97 Update Changes 2022-03-23 13:38:33 +01:00
Adam Brown
55b946a019 fixing the onboarding sanity test failing
- adds tapping the new take me home button within the sanity test
2022-03-23 12:57:53 +01:00
ganfra
951171cb94 Merge pull request #5607 from vector-im/feature/fga/weblate_1.4.6
Feature/fga/weblate 1.4.6
2022-03-22 19:39:00 +01:00
ganfra
c06c9ea1d6 Fix lint issues on weblate sync 2022-03-22 18:55:34 +01:00
ganfra
3d3f5444b1 Merge pull request #5606 from RiotTranslateBot/weblate-element-android-element-app
Translations update from Weblate
2022-03-22 18:11:28 +01:00
ClaireG
6787980185 Do not suggest collapse if there is only one section 2022-03-22 17:31:21 +01:00
Weblate
095f06bd89 Merge branch 'origin/develop' into Weblate. 2022-03-22 15:59:34 +00:00
iaiz
8550db9f23 Translated using Weblate (Spanish)
Currently translated at 95.7% (2065 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/es/
2022-03-22 15:58:54 +00:00
iaiz
707800532f Translated using Weblate (Spanish)
Currently translated at 66.6% (34 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/es/
2022-03-22 15:58:52 +00:00
ganfra
073475854e Merge pull request #5564 from SpiritCroc/timeline-chunk-double-linking
Fix another case of read markers not updating: Ensure proper double linking of TimelineChunks
2022-03-22 15:46:48 +01:00
ganfra
67804da205 Merge pull request #5604 from vector-im/feature/adm/toolbar-icon-positions
Fixing presence icon anchoring to the middle of the room icon
2022-03-22 15:16:17 +01:00
ganfra
a2f64e7f3c Merge pull request #5576 from vector-im/feature/aris/thread_labs_notice_users
Threads Migration
2022-03-22 14:57:07 +01:00
Adam Brown
1e6e8b546d fixing presence icon anchoring to the middle of the room icon
- creates a secondary verification shield and aligns to the start of the room title when presence is present
2022-03-22 13:43:43 +00:00
ariskotsomitopoulos
c6593f0cee PR remarks 2022-03-22 13:57:20 +02:00
Adam Brown
21e46c5840 Merge pull request #5601 from vector-im/dependabot/gradle/org.json-json-20220320
Bump json from 20211205 to 20220320
2022-03-22 11:00:13 +00:00
ariskotsomitopoulos
a431d885f0 Increase the thread summaries limit 2022-03-22 11:20:47 +02:00
dependabot[bot]
c2e3c63100 Bump json from 20211205 to 20220320
Bumps [json](https://github.com/douglascrockford/JSON-java) from 20211205 to 20220320.
- [Release notes](https://github.com/douglascrockford/JSON-java/releases)
- [Changelog](https://github.com/stleary/JSON-java/blob/master/docs/RELEASES.md)
- [Commits](https://github.com/douglascrockford/JSON-java/commits)

---
updated-dependencies:
- dependency-name: org.json:json
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-21 23:09:06 +00:00
ganfra
794131d274 Merge pull request #5551 from SpiritCroc/local-echo
Fix local echos not being shown when re-opening rooms
2022-03-21 19:24:07 +01:00
Michael Kaye
4a8aeadbcf Merge pull request #5471 from vector-im/michaelk/ci_script_improvements
CI Script improvements
2022-03-21 15:06:11 +00:00
Benoit Marty
835497682f Merge pull request #5572 from p1gp1g/play-sticker
Show stickers on click
2022-03-21 15:51:36 +01:00
Benoit Marty
579e6164d6 Merge pull request #5550 from vector-im/feature/adm/presence-theme-patch
Presence indicator theme attribute
2022-03-21 15:45:36 +01:00
Benoit Marty
176895a37d Merge pull request #5589 from vector-im/sync-emojis
Sync Emojis
2022-03-21 15:42:43 +01:00
Michael Kaye
c687252fb6 Update .github/workflows/nightly.yml 2022-03-21 14:31:17 +00:00
ariskotsomitopoulos
ed2cb5f0fe Enhance text constants 2022-03-21 15:45:08 +02:00
Linerly
75fe14561e Translated using Weblate (Indonesian)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/id/
2022-03-21 08:38:31 +00:00
bmarty
ffb007e96d Sync Emojis 2022-03-21 00:03:12 +00:00
notramo
777913493f Translated using Weblate (Hungarian)
Currently translated at 100.0% (51 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/
2022-03-19 21:38:32 +00:00
notramo
06af5a8017 Translated using Weblate (Hungarian)
Currently translated at 99.8% (2154 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/
2022-03-19 21:38:31 +00:00
Jeanne Lavoie
8a783a7a0b Translated using Weblate (French (Canada))
Currently translated at 83.9% (1811 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fr_CA/
2022-03-19 21:38:31 +00:00
libexus
7910b4cd35 Translated using Weblate (German)
Currently translated at 99.9% (2155 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
2022-03-19 21:38:31 +00:00
ravit
cb290550a9 Translated using Weblate (Hebrew)
Currently translated at 94.5% (2039 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
2022-03-18 18:38:46 +00:00
Эдуард Гера
db828152a6 Translated using Weblate (Hebrew)
Currently translated at 94.5% (2039 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
2022-03-18 18:38:39 +00:00
Modificator
20d9f68679 Translated using Weblate (Chinese (Simplified))
Currently translated at 95.7% (2065 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/
2022-03-18 18:38:35 +00:00
Besnik Bleta
1ecbe41529 Translated using Weblate (Albanian)
Currently translated at 99.4% (2145 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/
2022-03-18 18:38:35 +00:00
Jeanne Lavoie
d24f01c001 Translated using Weblate (French)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/
2022-03-18 18:38:35 +00:00
Jeanne Lavoie
4b6c8b8980 Translated using Weblate (French (Canada))
Currently translated at 83.7% (1806 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fr_CA/
2022-03-18 18:38:35 +00:00
Theo
96904d0fd7 Translated using Weblate (Greek)
Currently translated at 16.0% (347 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/el/
2022-03-18 18:38:34 +00:00
waclaw66
805666b2f5 Translated using Weblate (Czech)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/
2022-03-18 18:38:33 +00:00
Zet
df717dd82d Translated using Weblate (Arabic)
Currently translated at 32.7% (706 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ar/
2022-03-18 18:38:33 +00:00
Adam Brown
ea9c9ae490 Merge pull request #5408 from vector-im/feature/adm/onboarding-tests
FTUE - Onboarding registration steps unit tests
2022-03-18 15:57:38 +00:00
Maxime NATUREL
4761f7701b Merge pull request #5579 from vector-im/feature/mna/PSF-664-5571-live-location-indicator
#5571: Live location status bar indicator
2022-03-18 16:30:41 +01:00
Maxime Naturel
d1aba46b52 Using one line per parameter for the custom view 2022-03-18 15:58:42 +01:00
Maxime Naturel
840a224c9f Fixing coding style issue 2022-03-18 15:01:44 +01:00
Adam Brown
ce2c309d72 including verification to ensure no other methods are being called 2022-03-18 14:00:56 +00:00
ariskotsomitopoulos
e574fda099 Remove unused import 2022-03-18 14:55:14 +01:00
ariskotsomitopoulos
06db7e2374 Enhance migration logic & PR remarks 2022-03-18 14:51:06 +01:00
ariskotsomitopoulos
fd4d99d6fc Possible NullPointerException crash fix 2022-03-18 14:50:34 +01:00
Adam Brown
7f943d37fd explicitly declaring the fake registrationb wizard as not relaxed and creating new test instances for each case 2022-03-18 13:15:14 +00:00
Maxime Naturel
526d11e912 Removing TODO 2022-03-18 14:12:55 +01:00
Maxime Naturel
56bffb2657 Adding stop button 2022-03-18 14:11:16 +01:00
Maxime Naturel
67297bbdef Custom View in timeline screen without stop button 2022-03-18 14:11:08 +01:00
Maxime Naturel
70ab5354c3 Adding strings of the view 2022-03-18 14:10:57 +01:00
Maxime Naturel
32f18d5af3 Adding changelog entry 2022-03-18 14:10:47 +01:00
Maxime NATUREL
3547c5acba Merge pull request #5565 from vector-im/feature/mna/PSF-664-5536-permissions
Live Location Sharing - Background permission
2022-03-18 12:06:30 +01:00
ariskotsomitopoulos
2e5d45ec6e Format code 2022-03-18 12:03:17 +01:00
ariskotsomitopoulos
f00b1f29bf Enhance text context for thread migration notice 2022-03-18 11:58:29 +01:00
Valere
189683a8a1 Merge pull request #5552 from SpiritCroc/timeline-decryptor-crash
Fix crash when closing room during timeline decryption
2022-03-18 11:34:27 +01:00
ariskotsomitopoulos
a0e6dd5f6c Merge branch 'develop' into feature/aris/thread_labs_notice_users
# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
#	vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
2022-03-18 11:28:21 +01:00
aringenbach
1d74e34362 Merge pull request #5566 from vector-im/feature/aringenbach/presence-sync-build-config
Add a presence sync enabling build config
2022-03-18 09:19:45 +01:00
sim
e5c04d9cc8 Show stickers on click
Play animated stickers

Signed-off-by: sim <git@sgougeon.fr>
2022-03-18 01:03:34 +01:00
ariskotsomitopoulos
61cb7a6ffb Prepare next RC threads migration to all users 2022-03-17 19:09:09 +01:00
ariskotsomitopoulos
2ca3387ab3 Migrate Threads and notify user 2022-03-17 18:51:54 +01:00
Adam Brown
abf62aff47 extracting named function out for cancelling the email validation job, giving more context to the currentjob=null 2022-03-17 17:51:01 +00:00
Adam Brown
192d1c4f2d converting open class to sealed interface for extra type safety 2022-03-17 17:01:16 +00:00
Adam Brown
ba76aac965 removing unused fake helper methods 2022-03-17 16:54:51 +00:00
Adam Brown
d514751ffd avoiding shadowed lambda parameters 2022-03-17 16:52:37 +00:00
Adam Brown
5df2ae9ae2 updating with previous state helper and including javadoc to help explain its usage 2022-03-17 16:50:20 +00:00
Adam Brown
d77061b229 removing fully qualified import 2022-03-17 16:38:42 +00:00
Arnaud Ringenbach
7593f7a2c6 Fix lint issues 2022-03-17 17:15:16 +01:00
Arnaud Ringenbach
bc3cef53fe Re-trigger checks 2022-03-17 17:06:45 +01:00
Onuray Sahin
a13ba13fb5 Code review fixes. 2022-03-17 18:30:39 +03:00
Arnaud Ringenbach
d3459d1d3d Use MatrixConfiguration & move presence sync enabled checks out of view 2022-03-17 14:50:15 +01:00
Arnaud Ringenbach
4bcf31e0c2 Add a presence sync enabling build config 2022-03-17 11:43:07 +01:00
SpiritCroc
91259bef40 Ensure proper double linking of TimelineChunks
We need both directions so getOffsetIndex() produces correct results in
all cases.
2022-03-17 11:34:20 +01:00
Adam Brown
36564d3657 Merge pull request #5558 from vector-im/feature/adm/crash-when-tapping-verification-item
Fixing crash when tapping timeline verification item
2022-03-17 10:25:52 +00:00
Onuray Sahin
33e5a206bd Fix background location permission for Android > 10. 2022-03-17 12:29:46 +03:00
Adam Brown
4154f036db fixing crash when tapping verification item
- caused by the setArguments being called on the TimelineFragment not the bottomsheet we've just created
2022-03-16 16:19:39 +00:00
Aris Kotsomitopoulos
d1a77d2eca Merge pull request #5378 from vector-im/feature/aris/threads_analytics
Feature/aris/threads analytics
2022-03-16 15:51:24 +01:00
ClaireG
d1bca78083 [Notification mode] Wrong mode is displayed when the mention only is selected on the web client 2022-03-16 14:51:10 +01:00
ariskotsomitopoulos
bcf3f1e302 Format code 2022-03-16 14:49:40 +01:00
Onuray Sahin
575d62a354 Naming convention fix. 2022-03-16 16:34:48 +03:00
ariskotsomitopoulos
eee1ec1423 Merge branch 'develop' into feature/aris/threads_analytics 2022-03-16 13:19:08 +01:00
Adam Brown
99b43fd771 Merge pull request #5433 from vector-im/sync-analytics-plan
Sync analytics plan
2022-03-16 12:04:59 +00:00
ariskotsomitopoulos
60db2e424e Merge branch 'develop' into feature/aris/threads_analytics 2022-03-16 13:01:09 +01:00
Adam Brown
e1b8a2e8ec trigger CI 2022-03-16 11:08:15 +00:00
SpiritCroc
a994f859e1 Fix crash when closing room during timeline decryption 2022-03-16 10:19:01 +01:00
SpiritCroc
856f25f684 Fix local echos not being shown when re-opening rooms 2022-03-15 19:34:04 +01:00
Adam Brown
4a7646a7ba providing online indicator color from the colors file instead of the element palette 2022-03-15 17:59:37 +00:00
Adam Brown
c257488ca8 Merge pull request #5513 from chagai95/add-presence-indicator-online
add  presence indicator online
2022-03-15 17:58:02 +00:00
Adam Brown
3d20d46eb3 enabling the personalize step for the unit tests preemptively for the feature to be enabled 2022-03-15 17:49:44 +00:00
Adam Brown
fe206fe130 fixing wrong action for starting the sign up 2022-03-15 17:49:44 +00:00
Adam Brown
fc5c0579bb adding changelog entry 2022-03-15 17:49:44 +00:00
Adam Brown
694016fc16 adding test case for the non loading registration steps 2022-03-15 17:49:44 +00:00
Adam Brown
390ae4344d allowing test withPrevious to be supplied a list 2022-03-15 17:49:44 +00:00
Adam Brown
804513c808 adding case for result ignoring register actions 2022-03-15 17:49:44 +00:00
Adam Brown
75cbb727a4 cleaning up test names and bodies to be clearer 2022-03-15 17:49:43 +00:00
Adam Brown
b2a1aa17bd adding commas to separate the test name sections 2022-03-15 17:49:17 +00:00
Adam Brown
434ee67982 ensure the pid add/resend methods do not trigger the next registration steps
- keeps the previous behaviour
2022-03-15 17:49:17 +00:00
Adam Brown
3fa415007c extracting registration steps to separate handler to make testing the flow simpler 2022-03-15 17:49:16 +00:00
Adam Brown
4225f62120 adding test helper for asserting states whilst combining previous updates 2022-03-15 17:42:42 +00:00
Adam Brown
c15e908a15 converting onboarding action to sealed interface 2022-03-15 17:42:42 +00:00
Adam Brown
17d363cf9a Merge pull request #5389 from vector-im/feature/adm/personalisation-complete
FTUE - Personalisation complete
2022-03-15 17:33:29 +00:00
Maxime Naturel
d776f0c09c Check permission for background location (OS version <= Android 10 case) 2022-03-15 17:50:58 +01:00
Maxime Naturel
6f6bb3dbfe Adding method to show dialog when permission is missing 2022-03-15 17:45:36 +01:00
Maxime Naturel
9c6cd9f630 Adding build config field 2022-03-15 17:45:26 +01:00
Maxime Naturel
ae6040e01e Adding changelog entry 2022-03-15 17:45:19 +01:00
Maxime NATUREL
9ef235f3d7 Merge pull request #5479 from vector-im/feature/mna/PSF-735-pinned-location
#5417: Pinned location sharing
2022-03-15 17:40:21 +01:00
aringenbach
a7639f4424 Merge pull request #5544 from vector-im/aringenbach/5521_permalink_base_url_mention
Fix mentions using matrix.to rather than client defined permalink base url
2022-03-15 17:28:16 +01:00
Maxime Naturel
b72c87da44 Renaming "btn" into "button" 2022-03-15 17:09:31 +01:00
Maxime Naturel
9ecf12a7ba Using extensions for Boolean? type 2022-03-15 17:09:31 +01:00
Maxime Naturel
abd25c2292 Removing Action suffix in ViewAction types 2022-03-15 17:09:31 +01:00
Maxime Naturel
6779fa1175 Change naming for initLocateButton() method 2022-03-15 17:09:31 +01:00
Maxime Naturel
f495150b4e Fixing asset type representation 2022-03-15 17:09:31 +01:00
Maxime Naturel
9864e51927 Adding changelog entry 2022-03-15 17:09:31 +01:00
Maxime Naturel
094e62c95b Adding unit tests for use case 2022-03-15 17:09:31 +01:00
Maxime Naturel
04405c7970 Distinguish user location and pinned location sharing 2022-03-15 17:09:30 +01:00
Maxime Naturel
8d1822da96 Recompute location comparison on new user location 2022-03-15 17:09:30 +01:00
Maxime Naturel
125b8d2058 Zoom to user location action 2022-03-15 17:09:30 +01:00
Maxime Naturel
01aff36597 Using style attibute for locate button visibility 2022-03-15 17:09:30 +01:00
Maxime Naturel
40e92842ea Show icon to reset map to user location 2022-03-15 17:09:30 +01:00
Maxime Naturel
57fcfeb1c1 Show dot pin for user location 2022-03-15 17:09:30 +01:00
Maxime Naturel
93c397d492 Updating location target drawable dynamically 2022-03-15 17:09:30 +01:00
Maxime Naturel
6fc6bf1c7d Compare locations use case implementation 2022-03-15 17:09:30 +01:00
Maxime Naturel
01879e252d Comparing target and user location using Flow in ViewModel 2022-03-15 17:09:29 +01:00
Maxime Naturel
dec075faf3 Handling press on pinned location option 2022-03-15 17:09:29 +01:00
Maxime Naturel
a1d155df71 Adding static pin at the center of the map 2022-03-15 17:09:29 +01:00
Maxime Naturel
9256b5671b Show options menu on top of the map 2022-03-15 17:09:29 +01:00
chagai95
f8ca2fecd5 Update changelog.d/5513.misc
Co-authored-by: Adam Brown <adampsbrown@googlemail.com>
2022-03-15 17:08:31 +01:00
Arnaud Ringenbach
15e3f258f3 Fix wildcard imports 2022-03-15 16:55:24 +01:00
ariskotsomitopoulos
c3f1b748a3 Fix thread deleted root message summary 2022-03-15 16:34:17 +01:00
Onuray Sahin
db0cfd4704 Merge pull request #5455 from vector-im/feature/ons/fix_poll_start_notifications
Show notification for poll start events but not for responses
2022-03-15 18:34:03 +03:00
Arnaud Ringenbach
56760ecddc Create SpanTemplateType and factorize template creation 2022-03-15 16:33:59 +01:00
Adam Brown
9d49ef5f3a removing unused drawable 2022-03-15 15:12:51 +00:00
Onuray Sahin
a47b589e72 Code review fixes. 2022-03-15 17:51:02 +03:00
Onuray Sahin
7fa43f0d1b Merge branch 'develop' into feature/ons/fix_poll_start_notifications
* develop: (163 commits)
  Fix lint error.
  Removes changelog file
  Fix PR comment
  Adds changelog file
  Refactors MessageBubbleView
  Updating changelog copy
  making use of the fake overrides for testing
  extracting the personalization complete emitting to a dedicated function
  making use of binding api instead of manual findviewbyid
  using consistent method naming for setting the capabilities override
  taking the personalization feature flag into account when calculating if personalization is supported - also removes a legacy loading workaround for the account creation step, we're navigating to a new screen AccountCreated so we have to stop the loading
  adding changelog entry
  using correct label for the avatar capability debug override
  forwarding to the profile picture flow when display name changing isn't supported but pictures are when personalising the profile
  formatting
  dynamically switching the onboarding flow based on the capabilities of the homeserver - when avatars can't be changed we complete the personlisation flow
  hiding the toolbar back button and handling system back as take the user home if the display name personalisation is not supported
  adding test around account creation via dummy
  dynamically changing the account created layout based on if the homeserver supports personalisation
  adding entry points for injecting and overriding the homeserver capabilities
  ...
2022-03-15 17:49:53 +03:00
Aris Kotsomitopoulos
e0b93c2d2c Merge pull request #5298 from vector-im/feature/aris/thread_live_thread_list
Live Threads
2022-03-15 15:14:26 +01:00
Arnaud Ringenbach
094ebe6764 Fix ktlint on TestPermalinkService 2022-03-15 14:57:59 +01:00
Arnaud Ringenbach
a68471afe3 Fix documentation on PermalinkService 2022-03-15 14:54:24 +01:00
Arnaud Ringenbach
c7aab7a3f6 Remove useless imports on TextPillsUtils 2022-03-15 14:52:13 +01:00
Onuray Sahin
30c325b16c Merge pull request #5504 from vector-im/feature/ons/poll_tests
Poll Integration Tests
2022-03-15 16:51:12 +03:00
Arnaud Ringenbach
55fbed1a81 Remove useless PermalinkFactory import 2022-03-15 14:49:55 +01:00
Arnaud Ringenbach
ea4addf446 Fix TestPermalinkService header 2022-03-15 14:45:04 +01:00
ariskotsomitopoulos
7db2ff2cfd Merge branch 'feature/aris/thread_live_thread_list' into feature/aris/threads_analytics 2022-03-15 14:35:31 +01:00
ariskotsomitopoulos
4d76c0d822 Fix build error 2022-03-15 14:34:53 +01:00
Arnaud Ringenbach
c80df6943b Add changelog 2022-03-15 14:32:08 +01:00
Arnaud Ringenbach
5b04686827 Fix MarkdownParser unit tests 2022-03-15 14:27:17 +01:00
ariskotsomitopoulos
07eabf110d Merge branch 'feature/aris/thread_live_thread_list' into feature/aris/threads_analytics
# Conflicts:
#	vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt
2022-03-15 14:23:48 +01:00
ariskotsomitopoulos
8a862d006e Merge branch 'develop' into feature/aris/thread_live_thread_list
# Conflicts:
#	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
2022-03-15 14:21:04 +01:00
Onuray Sahin
b4df6e1ae8 Merge branch 'develop' into feature/ons/poll_tests
* develop:
  Fix lint error.
  Fix lint error.
  Avoid stable prefix
  Support both unstable and stable prefixes.
2022-03-15 16:16:17 +03:00
Adam Brown
1c63789cd0 removing unused import 2022-03-15 13:12:28 +00:00
Adam Brown
161cd3f9ec replacing the ftue icons with only the foreground asset to allow for tinting
- making use of the generic circle shape the background, this allows the foreground and background layers to be tintable separately
2022-03-15 13:07:22 +00:00
Adam Brown
049ae4a22a making the profile screen toolbar transparent, matches the designs for dark mode 2022-03-15 13:07:22 +00:00
Adam Brown
8d0435db77 updating all confetti usages to use design palette colours 2022-03-15 13:07:22 +00:00
Adam Brown
9575cb5de7 removing unneeded button background colour
- causes the rounded corners and highlight to stop working
2022-03-15 13:07:22 +00:00
Adam Brown
9c114f371e adding changelog entry 2022-03-15 13:07:22 +00:00
Adam Brown
e3c3db2964 adding personalization complete screen 2022-03-15 13:07:22 +00:00
Adam Brown
9f6d3ec380 playing confetti effect when unable to personalise account on account creation
- extracts the logic to an extension for reuse from the timeline
2022-03-15 13:07:22 +00:00
Danial Behzadi
3130e67edb Translated using Weblate (Persian)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/
2022-03-15 12:27:00 +00:00
Danial Behzadi
a40ad9543d Translated using Weblate (Persian)
Currently translated at 100.0% (51 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/
2022-03-15 12:26:59 +00:00
Jozef Gaal
ae5f09922a Translated using Weblate (Slovak)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2022-03-15 12:26:58 +00:00
Jeanne Lavoie
c2fa67ea22 Translated using Weblate (French)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/
2022-03-15 12:26:57 +00:00
Edward Gera
0013d08ac7 Translated using Weblate (Hebrew)
Currently translated at 89.1% (1923 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
2022-03-15 12:26:57 +00:00
Onuray Sahin
1a76914828 Merge pull request #5345 from vector-im/feature/ons/fix_unstable_prefixes
Support both unstable and stable prefixes
2022-03-15 15:16:16 +03:00
Onuray Sahin
f4bdaf6151 Fix lint error. 2022-03-15 14:47:19 +03:00
Onuray Sahin
dd3178c36e Merge branch 'develop' into feature/ons/poll_tests
* develop: (150 commits)
  Removes changelog file
  Fix PR comment
  Adds changelog file
  Refactors MessageBubbleView
  Updating changelog copy
  making use of the fake overrides for testing
  extracting the personalization complete emitting to a dedicated function
  making use of binding api instead of manual findviewbyid
  using consistent method naming for setting the capabilities override
  taking the personalization feature flag into account when calculating if personalization is supported - also removes a legacy loading workaround for the account creation step, we're navigating to a new screen AccountCreated so we have to stop the loading
  adding changelog entry
  using correct label for the avatar capability debug override
  forwarding to the profile picture flow when display name changing isn't supported but pictures are when personalising the profile
  formatting
  dynamically switching the onboarding flow based on the capabilities of the homeserver - when avatars can't be changed we complete the personlisation flow
  hiding the toolbar back button and handling system back as take the user home if the display name personalisation is not supported
  adding test around account creation via dummy
  dynamically changing the account created layout based on if the homeserver supports personalisation
  adding entry points for injecting and overriding the homeserver capabilities
  extracting method for the handling of the profile picture selection
  ...
2022-03-15 14:39:51 +03:00
Arnaud Ringenbach
df794ee41f Move template creation to PermalinkService 2022-03-15 11:26:47 +01:00
ariskotsomitopoulos
c9f07061ef Revert temporary fix for develop crash 2022-03-15 11:26:00 +01:00
Arnaud Ringenbach
4cf820cb12 Use client permalink base url on mentions if available 2022-03-15 10:34:04 +01:00
Eric Decanini
de14e10a45 Merge pull request #5534 from vector-im/bugfix/eric/top-space-crash
Fixes timeline crash due to additionalTopSpace
2022-03-15 09:59:09 +01:00
ClaireG
5de7873755 Merge pull request #5532 from vector-im/cgizard/ISSSUE-5514
Read receipt in wrong order
2022-03-14 17:39:30 +01:00
ericdecanini
184b35ab38 Removes changelog file 2022-03-14 17:35:34 +01:00
Claire G
6c7b4d4256 Fix PR comment 2022-03-14 17:10:58 +01:00
ariskotsomitopoulos
d7c486c55e Add fallback support rendering proposed in MSC3676 2022-03-14 16:04:08 +01:00
Adam Brown
82e1afdb72 Merge pull request #5375 from vector-im/feature/adm/display-personalisation-based-on-capabilities
FTUE - Capability based personalisation flow
2022-03-14 13:34:21 +00:00
ericdecanini
3981e72301 Adds changelog file 2022-03-14 14:21:01 +01:00
Maxime NATUREL
4939a98689 Merge pull request #5181 from vector-im/feature/mna/4533-dm-rooms-headers
#4533:  improve headers UI in room/messages lists
2022-03-14 13:51:03 +01:00
ericdecanini
d35c1e833d Refactors MessageBubbleView 2022-03-14 13:05:35 +01:00
Adam Brown
c06d3ff809 Updating changelog copy
Co-authored-by: Benoit Marty <benoitm@matrix.org>
2022-03-14 11:54:42 +00:00
Adam Brown
c84ce5a2e0 making use of the fake overrides for testing 2022-03-14 11:54:42 +00:00
Adam Brown
c2fe669670 extracting the personalization complete emitting to a dedicated function 2022-03-14 11:54:42 +00:00
Adam Brown
d89cc71eb4 making use of binding api instead of manual findviewbyid 2022-03-14 11:54:42 +00:00
Adam Brown
edee6abafa using consistent method naming for setting the capabilities override 2022-03-14 11:54:42 +00:00
Adam Brown
bdedffbb4f taking the personalization feature flag into account when calculating if personalization is supported
- also removes a legacy loading workaround for the account creation step, we're navigating to a new screen AccountCreated so we have to stop the loading
2022-03-14 11:54:42 +00:00
Adam Brown
7e79d7ed0e adding changelog entry 2022-03-14 11:54:42 +00:00
Adam Brown
ab9e4405ce using correct label for the avatar capability debug override 2022-03-14 11:54:42 +00:00
Adam Brown
4b9b177104 forwarding to the profile picture flow when display name changing isn't supported but pictures are when personalising the profile 2022-03-14 11:54:42 +00:00
Adam Brown
b5778bd6c5 formatting 2022-03-14 11:54:42 +00:00
Adam Brown
716928d9d2 dynamically switching the onboarding flow based on the capabilities of the homeserver
- when avatars can't be changed we complete the personlisation flow
2022-03-14 11:54:42 +00:00
Adam Brown
46be481eda hiding the toolbar back button and handling system back as take the user home if the display name personalisation is not supported 2022-03-14 11:54:42 +00:00
Adam Brown
a033243475 adding test around account creation via dummy 2022-03-14 11:54:42 +00:00
Adam Brown
537c2f56a1 dynamically changing the account created layout based on if the homeserver supports personalisation 2022-03-14 11:54:42 +00:00
Adam Brown
3df4f1e099 adding entry points for injecting and overriding the homeserver capabilities 2022-03-14 11:54:42 +00:00
Adam Brown
50740b1449 extracting method for the handling of the profile picture selection 2022-03-14 11:54:42 +00:00
Adam Brown
10e4fd1707 updating upstream avatar on profile picture save and continue step
- moves the personalisation state to a dedicated model to allow for back and forth state restoration
2022-03-14 11:54:42 +00:00
Adam Brown
7ded9007db add click handling for the display name actions 2022-03-14 11:54:42 +00:00
Adam Brown
0685b57720 add click handling for the display name actions 2022-03-14 11:54:42 +00:00
Adam Brown
6c4381fda5 adding dedicated camera icon for choosing profile picture 2022-03-14 11:54:42 +00:00
Adam Brown
567fd9a13d extracting method for the handling of the profile picture selection 2022-03-14 11:54:42 +00:00
Adam Brown
232524ddc3 adding barebones profile picture fragment with ability to select a user avatar 2022-03-14 11:54:42 +00:00
Adam Brown
1c80914832 adding tests around the onboarding view model
- cases for the personalisation and display name actions
2022-03-14 11:54:42 +00:00
Adam Brown
074cde4519 add click handling for the display name actions 2022-03-14 11:54:41 +00:00
Adam Brown
4a1bf11168 adding base choose name fragment with UI 2022-03-14 11:54:41 +00:00
ariskotsomitopoulos
d894d8598c Format text 2022-03-14 12:44:25 +01:00
ariskotsomitopoulos
fef36d9334 Temporarily fix develop crash 2022-03-14 12:25:11 +01:00
ariskotsomitopoulos
d215f03798 Merge branch 'develop' into feature/aris/thread_live_thread_list 2022-03-14 12:02:04 +01:00
ariskotsomitopoulos
c2ec7cfa0f Add more clear documentation 2022-03-14 11:54:29 +01:00
ericdecanini
2176129b11 Moves additional top space usage to bubble view 2022-03-14 11:46:48 +01:00
Maxime Naturel
8bb0a5cb4c Fixing wrong remove of import 2022-03-14 11:15:19 +01:00
Maxime Naturel
0942c6d648 Removing some with() code pattern 2022-03-14 11:15:19 +01:00
Maxime Naturel
a826a50c10 Renaming observe item count method 2022-03-14 11:15:19 +01:00
Maxime Naturel
291d7d7627 Fix some doc comment 2022-03-14 11:15:19 +01:00
Maxime Naturel
f327eaa3f1 Adding item counter in add existing room to space screen 2022-03-14 11:15:19 +01:00
Maxime Naturel
20749e04cb Fix coding style 2022-03-14 11:15:18 +01:00
Maxime Naturel
30c6518630 Updating remaining category item 2022-03-14 11:15:18 +01:00
Maxime Naturel
3d27d9d2d2 Fix filtering use case 2022-03-14 11:15:18 +01:00
Maxime Naturel
c79aa267c3 Fix switching space use case 2022-03-14 11:15:18 +01:00
Maxime Naturel
b72c357dd1 Removing unused imports 2022-03-14 11:15:18 +01:00
Maxime Naturel
d5345160fa Adding TODO 2022-03-14 11:15:18 +01:00
Maxime Naturel
ad9d36e58c Setting item count text only when > 0 2022-03-14 11:15:17 +01:00
Maxime Naturel
53c24d20b0 Moving notification badge on the right side 2022-03-14 11:15:17 +01:00
Maxime Naturel
0aaa650ac3 Using flow to show items counter 2022-03-14 11:15:17 +01:00
Maxime Naturel
c7dae341c0 (DRAFT) Room counter flow 2022-03-14 11:15:17 +01:00
Maxime Naturel
eb38c9d835 Fix remove of imports 2022-03-14 11:15:17 +01:00
Maxime Naturel
b1d1090d1d Adding number of items (UI part) 2022-03-14 11:15:17 +01:00
Maxime Naturel
70481e3ba3 Changing style of header 2022-03-14 11:15:16 +01:00
Maxime Naturel
134d7b2bf8 Adding changelog entry 2022-03-14 11:15:16 +01:00
Claire G
025dcc8d88 Fix bug: readReceipt in wrong order 2022-03-14 10:28:42 +01:00
lvre
9657a50a6a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/
2022-03-14 09:28:21 +00:00
libexus
bd4e251c8a Translated using Weblate (German)
Currently translated at 99.8% (2154 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
2022-03-14 09:28:21 +00:00
Ultimator14
bd50954b57 Translated using Weblate (German)
Currently translated at 99.8% (2154 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
2022-03-14 09:28:21 +00:00
Nikita Epifanov
e3d8b3d488 Translated using Weblate (Russian)
Currently translated at 99.8% (2153 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
2022-03-14 09:28:20 +00:00
Edward Gera
a5585ca3b3 Translated using Weblate (Hebrew)
Currently translated at 89.0% (1921 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
2022-03-14 09:28:19 +00:00
Nikita Epifanov
0b67e1cb3f Translated using Weblate (Russian)
Currently translated at 100.0% (51 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/ru/
2022-03-14 09:28:14 +00:00
Jozef Gaal
667189c8bc Translated using Weblate (Slovak)
Currently translated at 99.7% (2152 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2022-03-14 09:28:13 +00:00
Peter Vágner
5d67529ce2 Translated using Weblate (Slovak)
Currently translated at 99.7% (2152 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2022-03-14 09:28:12 +00:00
Szimszon
0521ff91c3 Translated using Weblate (Hungarian)
Currently translated at 99.8% (2154 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/
2022-03-14 09:28:12 +00:00
Tuomas Hietala
3d7aa99b28 Translated using Weblate (Finnish)
Currently translated at 83.2% (1795 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/
2022-03-14 09:28:11 +00:00
Sveinn í Felli
4922d5f5ff Translated using Weblate (Icelandic)
Currently translated at 65.3% (1409 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/is/
2022-03-14 09:28:07 +00:00
bmarty
5dfa362345 Sync analytics plan 2022-03-14 00:05:30 +00:00
Michael Kaye
acfeb7ff65 Merge pull request #5523 from vector-im/michaelk/concurrency_issue
Use different tags for unit tests and android test compilation.
2022-03-11 17:47:24 +00:00
Michael Kaye
9a532fc47f Use different tags for unit tests and android test compilation.
Otherwise we will cancel one in favour of the other.
2022-03-11 16:47:40 +00:00
Onuray Sahin
635be17d46 Changelog added. 2022-03-11 19:23:41 +03:00
Onuray Sahin
a12f918dd5 Add poll test for users try to change their votes after poll is ended. 2022-03-11 19:19:20 +03:00
chagai95
f68d3f2b03 Merge branch 'vector-im:develop' into add-presence-indicator-online 2022-03-11 17:13:20 +01:00
chagai95
04aaed7210 Change log line 2022-03-11 17:12:58 +01:00
Benoit Marty
c89554c3f6 Merge pull request #5443 from vector-im/task/eric/stable-hierarchy-endpoint
Changes room hierarchy endpoint to stable
2022-03-11 17:05:13 +01:00
Onuray Sahin
7c6167ace9 Add poll test for ended polls. 2022-03-11 18:59:27 +03:00
Benoit Marty
c3ac054634 Merge pull request #5518 from vector-im/bmarty-patch-1
Add space between icon and name
2022-03-11 16:47:59 +01:00
Benoit Marty
97f30ef7c1 Merge pull request #5520 from vector-im/feature/bma/fix_test_warnings
Fix all warnings in file E2eeSanityTests.kt
2022-03-11 16:47:25 +01:00
Benoit Marty
fa104adefc Fix all warnings in file E2eeSanityTests.kt 2022-03-11 15:35:21 +01:00
Benoit Marty
02ea1c0e7c Add space between icon and name
Also remove extra space for the text_template
2022-03-11 14:39:38 +01:00
cfriedlander
3ef2f824e8 add presence indicator online 2022-03-11 11:10:11 +01:00
Benoit Marty
25cfc7e05f Merge pull request #5506 from vector-im/michaelk/build_tests_on_pr
Compile tests on PR, to ensure we don't break the build when merging. Alert if we merge and the build fails.
2022-03-11 10:15:04 +01:00
Benoit Marty
d0a24afa5a Merge pull request #5510 from vector-im/dependabot/gradle/com.googlecode.libphonenumber-libphonenumber-8.12.45
Bump libphonenumber from 8.12.44 to 8.12.45
2022-03-11 09:47:26 +01:00
Michael Kaye
9a112bb010 Remove confetti from build failed message.
(there will be red icons if the build has failed in the body anyway)
2022-03-11 08:45:27 +00:00
Michael Kaye
400a47c39b Only run one gradlew build for all tests, do not split by project 2022-03-11 08:45:04 +00:00
Onuray Sahin
8b08d3db25 Fix poll test for users vote different options. 2022-03-11 11:36:06 +03:00
dependabot[bot]
2d5638baaf Bump libphonenumber from 8.12.44 to 8.12.45
Bumps [libphonenumber](https://github.com/google/libphonenumber) from 8.12.44 to 8.12.45.
- [Release notes](https://github.com/google/libphonenumber/releases)
- [Changelog](https://github.com/google/libphonenumber/blob/master/making-metadata-changes.md)
- [Commits](https://github.com/google/libphonenumber/compare/v8.12.44...v8.12.45)

---
updated-dependencies:
- dependency-name: com.googlecode.libphonenumber:libphonenumber
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-10 23:09:19 +00:00
ericdecanini
7226864cc9 Improves code formatting in ResolveSpaceInfoTask 2022-03-10 21:41:17 +01:00
Benoit Marty
eb7eb1aac8 Merge pull request #5505 from vector-im/feature/bma/must_be_tinted
Add colors for shield vector drawable
2022-03-10 20:39:29 +01:00
ariskotsomitopoulos
f31b130b49 Fix unit tests 2022-03-10 19:11:14 +02:00
ariskotsomitopoulos
34cfdfb6fe Merge branch 'develop' into feature/aris/thread_live_thread_list 2022-03-10 18:48:41 +02:00
Michael Kaye
c57d22a72c Additionally notify the matrix channel if these tests fail when run
after merge to develop or main.
2022-03-10 16:41:00 +00:00
Benoit Marty
26617988f2 Add colors for shield vector drawable 2022-03-10 17:15:19 +01:00
Benoit Marty
becdd1ce29 Fix test compilation after method renaming
Also rename in the comment
2022-03-10 17:01:45 +01:00
Michael Kaye
23f7f72e38 Compile tests on PR, to ensure we don't break the build when merging.
This was accidentally committed to develop and reverted there.
2022-03-10 15:58:00 +00:00
Michael Kaye
ed7c65d8d8 Revert "Compile tests on PR, to ensure we don't break the build when merging."
This reverts commit 9e5ff1785a.
2022-03-10 15:57:05 +00:00
ariskotsomitopoulos
a758ad71e6 Add is_falling_back support for rich thread replies
Enhance thread awareness handler so normal replies with thread disabled will be visible in te appropriate thread
Fix conflicts
2022-03-10 17:51:02 +02:00
Michael Kaye
9e5ff1785a Compile tests on PR, to ensure we don't break the build when merging. 2022-03-10 15:50:02 +00:00
Onuray Sahin
cd29b1aa4b Add poll test for users vote different options. 2022-03-10 17:40:16 +03:00
Benoit Marty
66f76fbea3 Merge pull request #5450 from SpiritCroc/fix-5448
Fix missing messages when forward paging with chunks > 50 messages
2022-03-10 15:08:58 +01:00
Benoit Marty
dfc440b904 Merge pull request #5502 from vector-im/cgizard/ISSSUE-5501
Use ColorPrimary for attachmentGalleryButton tint
2022-03-10 15:02:08 +01:00
Onuray Sahin
a5441fdf22 Add poll test for someone else chose the same option. 2022-03-10 16:51:23 +03:00
ericdecanini
a5af4783cc Renames mapToSpaceChildInfoList to mapSpaceChildren in DefaultSpaceService 2022-03-10 14:41:44 +01:00
Claire G
8f0d6a1adc use colorPrimary for tint 2022-03-10 14:30:15 +01:00
Benoit Marty
551b827753 Merge pull request #5384 from p1gp1g/bubble-space
Add padding before first own message
2022-03-10 14:09:23 +01:00
Michael Kaye
621df9d1b1 Merge pull request #5498 from vector-im/michaelk/setup-matrix-synapse
Swap to using github action to configure synapse server in CI builds
2022-03-10 12:50:50 +00:00
Onuray Sahin
f029759f9a Add poll test for changing previous answer. 2022-03-10 15:47:40 +03:00
Onuray Sahin
1b348401bd Add poll test for a single vote. 2022-03-10 15:31:32 +03:00
Benoit Marty
c339e10c07 Merge pull request #5436 from vector-im/feature/nfe/space_highlight
change selected space highlight
2022-03-10 13:28:08 +01:00
Benoit Marty
d83d5f9828 Merge pull request #5467 from vector-im/feature/bma/unused_resources_all
Remove unused resources
2022-03-10 13:03:27 +01:00
Michael Kaye
70fbcec093 Update to latest setup-matrix-synapse 2022-03-10 11:31:44 +00:00
Onuray Sahin
fd3e5128c7 Test initial poll event. 2022-03-10 14:30:03 +03:00
ericdecanini
a891f59397 Replaces lateinit var with passing params 2022-03-10 12:02:25 +01:00
ariskotsomitopoulos
21111922e6 Merge branch 'develop' into feature/aris/thread_live_thread_list
# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
2022-03-10 12:55:13 +02:00
ariskotsomitopoulos
fd30d38603 Fix line length 2022-03-10 12:51:40 +02:00
Michael Kaye
029ccfa37f Add names to all jobs 2022-03-10 10:24:41 +00:00
Michael Kaye
5ec8a2b36d Add some color to the nightly summary. 2022-03-10 10:24:41 +00:00
Michael Kaye
314f32ed45 If a FileNotFound exception occurs, log a simple message indicating the tests may
have not run.
2022-03-10 10:24:41 +00:00
Benoit Marty
fbc3b15df4 Merge pull request #5492 from vector-im/gradlew-update-7.4.1
Update Gradle Wrapper from 7.4 to 7.4.1
2022-03-10 11:16:39 +01:00
Benoit Marty
ce4ad8819a Merge pull request #5380 from vector-im/feature/bca/crypto_fix_rolling_uisi
Refactoring for safer olm and megolm session usage
2022-03-10 11:13:07 +01:00
ariskotsomitopoulos
45ee9f85e5 Check if the server supports MSC3440 using the stable flag from /versions api 2022-03-10 12:07:05 +02:00
ariskotsomitopoulos
03f293f216 Remove io.element.thread and add stable m.thread prefix 2022-03-10 12:06:02 +02:00
Benoit Marty
0122d8f933 Make comment about ignoring UnusedResources more valuable 2022-03-10 10:23:18 +01:00
gradle-update-robot
89db6a065d Update Gradle Wrapper from 7.4 to 7.4.1.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2022-03-10 00:17:17 +00:00
Adam Brown
d3fc3791bc Merge branch 'main' into develop 2022-03-09 21:33:16 +00:00
Michael Kaye
e9fe4630f1 Swap to using github action to configure server 2022-03-09 18:26:37 +00:00
ericdecanini
2f706d6fae Replaces children state event room id with space id 2022-03-09 18:42:35 +01:00
ericdecanini
f76f73f8ad Refactors DefaultSpaceService querySpaceChildren 2022-03-09 18:36:56 +01:00
Adam Brown
ecb49b3b27 Merge branch 'main' into develop 2022-03-09 16:13:15 +00:00
Adam Brown
60bc3b09b7 updating to version 1.4.6 in prep for next release cycle 2022-03-09 16:03:00 +00:00
ericdecanini
bbc6e8bbce Replaces caught Exception with HttpException 2022-03-09 16:41:18 +01:00
Maxime NATUREL
f12afe0ef0 Merge pull request #5411 from vector-im/feature/mna/PSF-735-sharing-options-view
#5395: Location sharing options view
2022-03-09 16:10:46 +01:00
Benoit Marty
3f17cf595f Remove unused resources 2022-03-09 14:55:40 +01:00
Benoit Marty
7d78c8819c Ignore some other lint warnings 2022-03-09 14:40:59 +01:00
Benoit Marty
cbdc28dd9b Merge pull request #5457 from vector-im/michaelk/ignore_flaky_test
Ignore flaky VerificationTest
2022-03-09 12:18:55 +01:00
ericdecanini
047e767f34 Adds coroutinesTest to matrix sdk gradle 2022-03-09 12:18:46 +01:00
Onuray Sahin
9b7e329000 Fix lint error. 2022-03-09 13:31:29 +03:00
Benoit Marty
104f948a08 Merge pull request #5445 from vector-im/dependabot/github_actions/actions/checkout-3
Bump actions/checkout from 2 to 3
2022-03-09 11:27:13 +01:00
Benoit Marty
02ba09900f Merge pull request #5434 from vector-im/feature/nfe/space_unread_count_dm
include dms number in space unread number badge
2022-03-09 11:16:07 +01:00
Benoit Marty
e0914781fc Merge pull request #5435 from vector-im/feature/nfe/space_menu_amend_copy
Amend spaces menu to have consistent copy
2022-03-09 11:13:46 +01:00
ericdecanini
63cd79dc4f Removes debug logs 2022-03-09 10:53:18 +01:00
Benoit Marty
67a24b38cb Avoid stable prefix 2022-03-09 10:48:35 +01:00
Benoit Marty
20c1886fed Support both unstable and stable prefixes.
Author: Onuray
2022-03-09 10:40:43 +01:00
ericdecanini
fb374b7374 Fixes wrong path parameter in getSpaceHierarchy 2022-03-09 09:57:20 +01:00
Maxime Naturel
2bdafde965 Fix wrong click listener on the current available option 2022-03-09 09:46:52 +01:00
Maxime Naturel
89db867ab3 Fix attributes style id conflicts 2022-03-09 09:46:52 +01:00
Maxime Naturel
6515e457b5 Ignore unused strings to be deleted 2022-03-09 09:46:52 +01:00
Maxime Naturel
046d906f97 Moving view attributes into uikit dedicated file 2022-03-09 09:46:52 +01:00
Maxime Naturel
9adb87bdb4 Renaming picker view method 2022-03-09 09:46:52 +01:00
Maxime Naturel
442d722980 Fix comment form in enum 2022-03-09 09:46:52 +01:00
Maxime Naturel
f4b5353424 Fix live location color on light theme 2022-03-09 09:46:52 +01:00
Maxime Naturel
3aa1032a7a Rollback wrong sharing options 2022-03-09 09:46:52 +01:00
Maxime Naturel
82515cf095 Remove usage of strings to be deleted 2022-03-09 09:46:52 +01:00
Maxime Naturel
9b6811c915 View extension methods to tint current background 2022-03-09 09:46:52 +01:00
Maxime Naturel
7cad30e6bb Use user color for pin background 2022-03-09 09:46:52 +01:00
Maxime Naturel
8f362d83cd Set option to user current location 2022-03-09 09:46:52 +01:00
Maxime Naturel
edc77b0c17 Fixing code syntax 2022-03-09 09:46:51 +01:00
Maxime Naturel
e4c9acde77 Setting colorSurface for background 2022-03-09 09:46:51 +01:00
Maxime Naturel
d36409d475 Renaming package to option 2022-03-09 09:46:51 +01:00
Maxime Naturel
b11f7f20e1 Adding user avatar and color for current location option 2022-03-09 09:46:51 +01:00
Maxime Naturel
42fca9bd6f Fixing ripple effect 2022-03-09 09:46:51 +01:00
Maxime Naturel
8bcc594320 Changing color of the live location icon 2022-03-09 09:46:47 +01:00
Maxime Naturel
fb764028c9 Adding dividers 2022-03-09 09:45:15 +01:00
Maxime Naturel
0707877b3c Using correct string resources for options 2022-03-09 09:45:15 +01:00
Maxime Naturel
3bbb7167f1 Adding icon for live location 2022-03-09 09:45:00 +01:00
Maxime Naturel
4586426958 Adding other options 2022-03-09 09:44:06 +01:00
Maxime Naturel
55c6383074 Set options method 2022-03-09 09:44:06 +01:00
Maxime Naturel
1c6b31001f Adding changelog entry 2022-03-09 09:44:06 +01:00
Maxime Naturel
a98f502c01 Adding custom view for options picker 2022-03-09 09:44:06 +01:00
Valere
96b51744b6 Fix ktlint 2022-03-08 23:19:21 +01:00
ericdecanini
31f300c724 Adds error print stack trace 2022-03-08 21:32:13 +01:00
sim
17d58f24d2 Add padding before our first message
Signed-off-by: sim <git@sgougeon.fr>
2022-03-08 17:26:23 +01:00
ericdecanini
54828f76cf Adds slash to v1 prefix path 2022-03-08 17:26:01 +01:00
ericdecanini
0892525c84 Adds debug logs 2022-03-08 17:19:11 +01:00
Weblate
14260e2ae2 Merge branch 'origin/develop' into Weblate. 2022-03-08 16:07:33 +00:00
ericdecanini
510206aa8a Adds changelog file 2022-03-08 16:54:05 +01:00
ericdecanini
82b5fc9557 Removes unused imports 2022-03-08 16:50:49 +01:00
ariskotsomitopoulos
a53d5bdba2 Remove eventType from /relations api for threads 2022-03-08 16:41:38 +02:00
ericdecanini
eb46067c08 Changes caught exception type to Throwable 2022-03-08 15:40:13 +01:00
Weblate
e487c44b70 Merge branch 'origin/develop' into Weblate. 2022-03-08 14:08:43 +00:00
Michael Kaye
67c9584215 Ignore flaky VerificationTest 2022-03-08 14:08:30 +00:00
Suguru Hirahara
2c0d281a7b Translated using Weblate (Japanese)
Currently translated at 62.7% (32 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/ja/
2022-03-08 14:01:58 +00:00
Ilan Feler
a7e44c81f2 Translated using Weblate (Hebrew)
Currently translated at 85.5% (1846 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
2022-03-08 14:01:57 +00:00
Edward Gera
bf619204e1 Translated using Weblate (Hebrew)
Currently translated at 85.5% (1846 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
2022-03-08 14:01:54 +00:00
notramo
d8f8d4a40c Translated using Weblate (Hungarian)
Currently translated at 98.0% (50 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/
2022-03-08 14:01:50 +00:00
Jozef Gaal
4d0cde6fb9 Translated using Weblate (Slovak)
Currently translated at 99.3% (2143 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2022-03-08 14:01:48 +00:00
Peter Vágner
37bbaa983f Translated using Weblate (Slovak)
Currently translated at 99.3% (2143 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2022-03-08 14:01:48 +00:00
Nícolas F. R. A. Prado
e1c61e3e30 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/
2022-03-08 14:01:48 +00:00
Suguru Hirahara
e6d81b3a77 Translated using Weblate (Japanese)
Currently translated at 98.6% (2128 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
2022-03-08 14:01:47 +00:00
notramo
8b7606bd20 Translated using Weblate (Hungarian)
Currently translated at 99.8% (2154 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/
2022-03-08 14:01:47 +00:00
Glandos
9e828822a7 Translated using Weblate (French)
Currently translated at 99.9% (2156 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/
2022-03-08 14:01:47 +00:00
Tuomas Hietala
cf6a717023 Translated using Weblate (Finnish)
Currently translated at 80.2% (1730 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/
2022-03-08 14:01:44 +00:00
Priit Jõerüüt
2e5bd0dda6 Translated using Weblate (Estonian)
Currently translated at 99.9% (2155 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/et/
2022-03-08 14:01:40 +00:00
Zet
a657bc6976 Translated using Weblate (Arabic)
Currently translated at 30.5% (659 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ar/
2022-03-08 14:01:40 +00:00
ariskotsomitopoulos
8c6902aa23 Fix reply within thread edition 2022-03-08 14:50:27 +02:00
ericdecanini
e5299d716c Fixes legal comments 2022-03-08 13:15:26 +01:00
ericdecanini
0af6ae6075 Adds logic for using stable and unstable hierarchy endpoints 2022-03-08 13:10:18 +01:00
Onuray Sahin
83ff898ce5 Changelog added. 2022-03-08 14:14:45 +03:00
Onuray Sahin
7a1d3aa3f2 Filter poll response events in latest event query. 2022-03-08 14:07:14 +03:00
Onuray Sahin
9fa285e6ca Support showing push notifications for poll start events. 2022-03-08 14:06:28 +03:00
ericdecanini
bc3b8d0a16 Adds testing for fallback api 2022-03-08 11:51:17 +01:00
SpiritCroc
768262094c Fix missing messages when forward paging with chunks > 50 messages
- offsets() was not limiting in the right direction when loading
  messages forwards
- after fixing offsets(), more recent messages would not be loaded due
  to the isLastForward() check, so better prioritize the SUCCESS
  LoadMoreResult over the REACHED_END here
2022-03-08 10:54:27 +01:00
ariskotsomitopoulos
557fd7eacf Replace thread timeline and thread summaries EventInsertType from INCREMENTAL_SYNC to PAGINATION 2022-03-08 10:13:56 +02:00
dependabot[bot]
9c18088128 Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 23:05:24 +00:00
Peter Vágner
e758f440c7 Translated using Weblate (Slovak)
Currently translated at 99.2% (2140 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2022-03-07 22:18:58 +00:00
ericdecanini
3b0a565822 Changes room hierarchy endpoint 2022-03-07 16:51:40 +01:00
NIkita Fedrunov
33246be2a5 change selected space highlight 2022-03-07 09:58:40 +01:00
NIkita Fedrunov
a3dcee55e4 include dms number in space unread number badge 2022-03-07 08:35:53 +01:00
NIkita Fedrunov
8732c6fe42 Amend spaces menu to have consistent copy 2022-03-07 08:32:31 +01:00
ariskotsomitopoulos
d19dd91d67 Format code 2022-03-05 20:49:11 +02:00
ariskotsomitopoulos
bce5bc8389 Fix wrong versioning regex pattern
Add MSC3440 support using /version/ and /capabilities
2022-03-05 17:13:02 +02:00
Valere
3c931d6f6d Save valid backup key before downloading keys 2022-03-04 19:21:19 +01:00
Valere
db84c679b4 Code review cleaning 2022-03-04 19:21:19 +01:00
Valere
99a07af9de Better comment
Co-authored-by: poljar <poljar@termina.org.uk>
2022-03-04 19:21:19 +01:00
Valere
31d3fe38aa Better comment
Co-authored-by: poljar <poljar@termina.org.uk>
2022-03-04 19:21:19 +01:00
Valere
7616e2d14c better comment
Co-authored-by: poljar <poljar@termina.org.uk>
2022-03-04 19:21:19 +01:00
Valere
5d952feef9 code review cleaning 2022-03-04 19:21:19 +01:00
Valere
ada83d0ba6 fix test 2022-03-04 19:21:19 +01:00
Valere
714e1d79b7 clean log level 2022-03-04 19:21:19 +01:00
Valere
6546f98858 use mutex on suspend and not synchronized 2022-03-04 19:21:19 +01:00
Valere
49d33f3a4b avoid duplicate userId on key download 2022-03-04 19:21:19 +01:00
Valere
87de51b184 Use loggerTag 2022-03-04 19:21:19 +01:00
Valere
b7bf39b99a resurrect unwedge test + cleaning 2022-03-04 19:21:19 +01:00
Valere
078ed1b2d1 dispatch network calls to io 2022-03-04 19:21:19 +01:00
Valere
f238739438 Clean ensure olm, fix unwedging, better logs 2022-03-04 19:21:19 +01:00
Valere
2d9beb67b4 extract test to dedicated class 2022-03-04 19:21:19 +01:00
Valere
122e785f14 clean test 2022-03-04 19:21:19 +01:00
Valere
2f665dd08f cleaning 2022-03-04 19:21:19 +01:00
Valere
11e888162c test forward better key 2022-03-04 19:21:19 +01:00
Valere
9eb0473d74 better logs 2022-03-04 19:21:19 +01:00
Valere
ade16a0aa1 protect race on prekey + logs 2022-03-04 19:21:19 +01:00
Valere
9b3c5d2153 Improve inbound group session cache + mutex 2022-03-04 19:21:19 +01:00
Valere
c97de48474 Added e2ee sanity tests 2022-03-04 19:21:19 +01:00
Valere
24c51ea41a Clean megolm import code 2022-03-04 19:21:19 +01:00
Valere
87d930819a Fix test compilation 2022-03-04 19:21:19 +01:00
Valere
9df5f17132 protect olm account access 2022-03-04 19:21:19 +01:00
Valere
33f9bc52cb Protect olm session from concurrent access 2022-03-04 19:21:19 +01:00
Valere
10ea166b2a Extract olm cache store 2022-03-04 19:21:19 +01:00
ariskotsomitopoulos
daafddbe71 fix Realm crash 2022-03-03 19:10:40 +02:00
ariskotsomitopoulos
39bd437f75 Temp fix Realm crash 2022-03-03 17:04:08 +02:00
Suguru Hirahara
a77637b751 Translated using Weblate (Japanese)
Currently translated at 98.3% (2121 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
2022-03-03 13:05:57 +00:00
ariskotsomitopoulos
e4282e5f29 Merge branch 'develop' into feature/aris/thread_live_thread_list
# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
2022-03-03 13:56:59 +02:00
ariskotsomitopoulos
719e254bb4 Format Code 2022-03-03 13:51:41 +02:00
ariskotsomitopoulos
33b170077e force refresh home server capabilities 2022-03-03 13:49:53 +02:00
ariskotsomitopoulos
623e9257a5 Merge branch 'feature/aris/thread_live_thread_list' into feature/aris/threads_analytics 2022-03-03 12:56:12 +02:00
Besnik Bleta
e8024688f4 Translated using Weblate (Albanian)
Currently translated at 100.0% (51 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/sq/
2022-03-03 10:27:00 +00:00
sagi korin
ed7d1927f5 Translated using Weblate (Hebrew)
Currently translated at 81.2% (1753 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
2022-03-03 10:26:59 +00:00
SPiRiT
6e805beeed Translated using Weblate (Hebrew)
Currently translated at 81.2% (1753 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
2022-03-03 10:26:59 +00:00
Glandos
ee8e369294 Translated using Weblate (French)
Currently translated at 100.0% (51 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/
2022-03-03 10:26:58 +00:00
libexus
cc7545b31f Translated using Weblate (German)
Currently translated at 100.0% (51 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/de/
2022-03-03 10:26:57 +00:00
Jeff Huang
0b6af0f2b2 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/
2022-03-03 10:26:55 +00:00
Besnik Bleta
a251545e90 Translated using Weblate (Albanian)
Currently translated at 99.4% (2145 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/
2022-03-03 10:26:55 +00:00
Jozef Gaal
abfd9b8fdc Translated using Weblate (Slovak)
Currently translated at 98.9% (2134 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2022-03-03 10:26:54 +00:00
Arusekk
84a27d23fd Translated using Weblate (Polish)
Currently translated at 93.8% (2025 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/
2022-03-03 10:26:54 +00:00
Suguru Hirahara
8f1cd11bbb Translated using Weblate (Japanese)
Currently translated at 98.3% (2121 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
2022-03-03 10:26:54 +00:00
random
a71cc5a942 Translated using Weblate (Italian)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/it/
2022-03-03 10:26:53 +00:00
libexus
4ca2a4e8b1 Translated using Weblate (German)
Currently translated at 99.8% (2153 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
2022-03-03 10:26:53 +00:00
ariskotsomitopoulos
214e0efcd9 Add Markdown support to thread summaries and thread list 2022-03-02 13:47:08 +02:00
Suguru Hirahara
2673f6715a Translated using Weblate (Japanese)
Currently translated at 98.0% (2116 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
2022-02-28 20:45:31 +00:00
oksya8and8
12ea262ebc Translated using Weblate (Japanese)
Currently translated at 98.0% (2116 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
2022-02-28 20:45:30 +00:00
ariskotsomitopoulos
f0f98ce019 Add changelog file 2022-02-28 17:44:53 +02:00
ariskotsomitopoulos
e59f2bba0a Add analytics to threads 2022-02-28 17:13:06 +02:00
ariskotsomitopoulos
eda723c230 Remove fetching thread summaries when homeserver do not support MSC3440 2022-02-28 12:35:27 +02:00
Suguru Hirahara
329ce7736c Translated using Weblate (Japanese)
Currently translated at 54.9% (28 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/ja/
2022-02-27 09:27:01 +00:00
LinAGKar
1f6275762e Translated using Weblate (Swedish)
Currently translated at 100.0% (51 of 51 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/
2022-02-27 09:27:00 +00:00
Ihor Hordiichuk
a4f04b704f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
2022-02-27 09:26:59 +00:00
LinAGKar
8c6b15a1ed Translated using Weblate (Swedish)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/
2022-02-27 09:26:58 +00:00
Jozef Gaal
8597d11442 Translated using Weblate (Slovak)
Currently translated at 98.8% (2132 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2022-02-27 09:26:58 +00:00
lvre
a4d9b4d5a8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/
2022-02-27 09:26:57 +00:00
Suguru Hirahara
91418493a6 Translated using Weblate (Japanese)
Currently translated at 97.4% (2103 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
2022-02-27 09:26:57 +00:00
Linerly
ff08ed322f Translated using Weblate (Indonesian)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/id/
2022-02-27 09:26:52 +00:00
Szimszon
f507c6c4a9 Translated using Weblate (Hungarian)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/
2022-02-27 09:26:52 +00:00
waclaw66
985007a1c1 Translated using Weblate (Czech)
Currently translated at 100.0% (2157 of 2157 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/
2022-02-27 09:26:51 +00:00
ariskotsomitopoulos
8b254212af Merge branch 'develop' into feature/aris/thread_live_thread_list 2022-02-24 12:45:08 +02:00
ariskotsomitopoulos
8788fb974d Add new use case about threads in the allScreensTest 2022-02-23 18:39:02 +02:00
ariskotsomitopoulos
a9b3882bf6 Add changelogs 2022-02-23 12:06:35 +02:00
ariskotsomitopoulos
79c97ac512 Formating code 2022-02-22 20:59:22 +02:00
ariskotsomitopoulos
f7f363ce25 Fix wrong copyrights 2022-02-22 20:52:01 +02:00
ariskotsomitopoulos
2054c577f3 Fix quality check errors 2022-02-22 17:41:54 +02:00
ariskotsomitopoulos
9953d0d0ed Resolve realm migration conflicts 2022-02-22 13:57:43 +02:00
ariskotsomitopoulos
deb86d2e87 Resolve real migration conflicts 2022-02-22 13:18:09 +02:00
ariskotsomitopoulos
79a231f1dc Merge branch 'develop' into feature/aris/thread_live_thread_list
# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
2022-02-22 12:52:55 +02:00
ariskotsomitopoulos
0f721d971c Ktlint format 2022-02-21 17:24:34 +02:00
ariskotsomitopoulos
2b740a1ab6 Implement permalink support for /relations live thread timeline 2022-02-21 17:23:17 +02:00
ariskotsomitopoulos
f4f48b919e Improve home server capabilities for threads 2022-02-21 12:14:51 +02:00
ariskotsomitopoulos
83088bbe5a Introduce live thread summaries using the enhanced /messages API from MSC 3440
Add capabilities to support local thread list to not supported servers
2022-02-18 17:21:10 +02:00
ariskotsomitopoulos
830c38f50b format ktlint 2022-02-14 16:53:29 +02:00
ariskotsomitopoulos
e9e5d680a1 Fix realm migration from 25 to 26 2022-02-14 16:51:56 +02:00
ariskotsomitopoulos
f98b595d85 Merge branch 'develop' into feature/aris/threads_live_timeline
# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt
2022-02-14 16:13:51 +02:00
ariskotsomitopoulos
27bc43c24c Fix realm migration 2022-02-14 15:33:51 +02:00
ariskotsomitopoulos
f1b11df781 Merge branch 'develop' into feature/aris/threads_live_timeline
# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
2022-02-14 15:17:55 +02:00
ariskotsomitopoulos
83d937b842 format ktlint 2022-02-14 15:10:30 +02:00
ariskotsomitopoulos
49b7726ac8 - Integrate /relations API to create a live thread timeline 2022-02-14 15:09:01 +02:00
431 changed files with 10170 additions and 3808 deletions

View File

@@ -25,7 +25,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/develop' && format('integration-tests-develop-{0}-{1}', matrix.target, github.sha) || format('build-debug-{0}-{1}', matrix.target, github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |
@@ -49,7 +49,7 @@ jobs:
if: github.ref == 'refs/heads/main'
# Only runs on main, no concurrency.
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |

View File

@@ -7,5 +7,5 @@ jobs:
runs-on: ubuntu-latest
# No concurrency required, this is a prerequisite to other actions and should run every time.
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1

View File

@@ -14,50 +14,6 @@ env:
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
jobs:
# Build Android Tests [Matrix SDK]
build-android-test-matrix-sdk:
name: Matrix SDK - Build Android Tests
runs-on: macos-latest
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests for matrix-sdk-android
run: ./gradlew clean matrix-sdk-android:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
# Build Android Tests [Matrix APP]
build-android-test-app:
name: App - Build Android Tests
runs-on: macos-latest
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests for vector
run: ./gradlew clean vector:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
# Run Android Tests
integration-tests:
name: Matrix SDK - Running Integration Tests
@@ -68,7 +24,7 @@ jobs:
api-level: [ 28 ]
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
with:
@@ -87,11 +43,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
run: |
pip install matrix-synapse
curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
chmod 777 start.sh
./start.sh --no-rate-limit
uses: michaelkaye/setup-matrix-synapse@v0.3.0
with:
uploadLogs: true
httpPort: 8080
disableRateLimiting: true
# package: org.matrix.android.sdk.session
- name: Run integration tests for Matrix SDK [org.matrix.android.sdk.session] API[${{ matrix.api-level }}]
uses: reactivecircus/android-emulator-runner@v2
@@ -260,7 +216,7 @@ jobs:
api-level: [ 28 ]
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
@@ -274,10 +230,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
run: |
pip install matrix-synapse
curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
| sed s/127.0.0.1/0.0.0.0/g | sed 's/http:\/\/localhost/http:\/\/10.0.2.2/g' | bash -s -- --no-rate-limit
uses: michaelkaye/setup-matrix-synapse@v0.3.0
with:
uploadLogs: true
httpPort: 8080
disableRateLimiting: true
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
@@ -308,9 +265,10 @@ jobs:
failure_screenshots/
codecov-units:
name: Unit tests with code coverage
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
@@ -333,12 +291,13 @@ jobs:
build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml
sonarqube:
name: Sonarqube upload
runs-on: macos-latest
if: always()
needs:
- codecov-units
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
@@ -362,13 +321,11 @@ jobs:
# Notify the channel about scheduled runs, do not notify for manually triggered runs
notify:
name: Notify matrix
runs-on: ubuntu-latest
needs:
- integration-tests
- ui-tests
# - unit-tests
- build-android-test-matrix-sdk
- build-android-test-app
- sonarqube
if: always() && github.event_name != 'workflow_dispatch'
# No concurrency required, runs every time on a schedule.
@@ -379,4 +336,4 @@ jobs:
matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
text_template: "Nightly test run: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
html_template: "Nightly test run results: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{name}} {{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a>{{/if}}{{/with}}{{/each}}"
html_template: "Nightly test run results: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{icon conclusion}} {{name}} <font color='{{color conclusion}}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"

View File

@@ -10,7 +10,7 @@ jobs:
name: Project Check Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Run code quality check suite
run: ./tools/check/check_code_quality.sh
@@ -23,7 +23,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('ktlint-develop-{0}', github.sha) || format('ktlint-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Run ktlint
run: |
./gradlew ktlintCheck --continue
@@ -96,7 +96,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('android-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('android-lint-develop-{0}', github.sha) || format('android-lint-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |
@@ -129,7 +129,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/develop' && format('apk-lint-develop-{0}-{1}', matrix.target, github.sha) || format('apk-lint-{0}-{1}', matrix.target, github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |

View File

@@ -11,7 +11,7 @@ jobs:
if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
@@ -38,7 +38,7 @@ jobs:
if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
@@ -64,7 +64,7 @@ jobs:
if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Run analytics import script
run: ./tools/import_analytic_plan.sh
- name: Create Pull Request for analytics plan

View File

@@ -12,6 +12,30 @@ env:
-Porg.gradle.parallel=false
jobs:
# Build Android Tests
build-android-tests:
name: Build Android Tests
runs-on: ubuntu-latest
concurrency:
group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests
run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
unit-tests:
name: Run Unit Tests
runs-on: ubuntu-latest
@@ -20,7 +44,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |
@@ -41,3 +65,20 @@ jobs:
( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository )
with:
files: ./**/build/test-results/**/*.xml
# Notify the channel about runs against develop or main that have failures, as PRs should have caught these first.
notify:
runs-on: ubuntu-latest
needs:
- unit-tests
- build-android-tests
if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' ) && failure() }}
steps:
- uses: michaelkaye/matrix-hookshot-action@v0.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
text_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}{{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
html_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{icon conclusion }} {{name}} <font color='{{color conclusion }}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v1

View File

@@ -11,6 +11,7 @@
<w>emoji</w>
<w>emojis</w>
<w>fdroid</w>
<w>ganfra</w>
<w>gplay</w>
<w>hmac</w>
<w>homeserver</w>
@@ -18,6 +19,7 @@
<w>ktlint</w>
<w>linkified</w>
<w>linkify</w>
<w>manu</w>
<w>megolm</w>
<w>msisdn</w>
<w>msisdns</w>

View File

@@ -1,3 +1,66 @@
Changes in Element v1.4.7 (2022-03-24)
======================================
Bugfixes 🐛
----------
- Fix inconsistencies between the arrow visibility and the collapse action on the room sections ([#5616](https://github.com/vector-im/element-android/issues/5616))
- Fix room list header count flickering
Changes in Element v1.4.6 (2022-03-23)
======================================
Features ✨
----------
- Thread timeline is now live and much faster especially for large or old threads ([#5230](https://github.com/vector-im/element-android/issues/5230))
- View all threads per room screen is now live when the home server supports threads ([#5232](https://github.com/vector-im/element-android/issues/5232))
- Add a custom view to display a picker for share location options ([#5395](https://github.com/vector-im/element-android/issues/5395))
- Add ability to pin a location on map for sharing ([#5417](https://github.com/vector-im/element-android/issues/5417))
- Poll Integration Tests ([#5522](https://github.com/vector-im/element-android/issues/5522))
- Live location sharing: adding build config field and show permission dialog ([#5536](https://github.com/vector-im/element-android/issues/5536))
- Live location sharing: Adding indicator view when enabled ([#5571](https://github.com/vector-im/element-android/issues/5571))
Bugfixes 🐛
----------
- Poll system notifications on Android are not user friendly ([#4780](https://github.com/vector-im/element-android/issues/4780))
- Add colors for shield vector drawable ([#4860](https://github.com/vector-im/element-android/issues/4860))
- Support both stable and unstable prefixes for Events about Polls and Location ([#5340](https://github.com/vector-im/element-android/issues/5340))
- Fix missing messages when loading messages forwards ([#5448](https://github.com/vector-im/element-android/issues/5448))
- Fix presence indicator being aligned to the center of the room image ([#5489](https://github.com/vector-im/element-android/issues/5489))
- Read receipt in wrong order ([#5514](https://github.com/vector-im/element-android/issues/5514))
- Fix mentions using matrix.to rather than client defined permalink base url ([#5521](https://github.com/vector-im/element-android/issues/5521))
- Fixes crash when tapping the timeline verification surround box instead of the buttons ([#5540](https://github.com/vector-im/element-android/issues/5540))
- [Notification mode] Wrong mode is displayed when the mention only is selected on the web client ([#5547](https://github.com/vector-im/element-android/issues/5547))
- Fix local echos not being shown when re-opening rooms ([#5551](https://github.com/vector-im/element-android/issues/5551))
- Fix crash when closing a room while decrypting timeline events ([#5552](https://github.com/vector-im/element-android/issues/5552))
- Fix sometimes read marker not properly updating ([#5564](https://github.com/vector-im/element-android/issues/5564))
In development 🚧
----------------
- Dynamically showing/hiding onboarding personalisation screens based on the users homeserver capabilities ([#5375](https://github.com/vector-im/element-android/issues/5375))
- Introduces FTUE personalisation complete screen along with confetti celebration ([#5389](https://github.com/vector-im/element-android/issues/5389))
SDK API changes ⚠️
------------------
- Adds support for MSC3440, additional threads homeserver capabilities ([#5271](https://github.com/vector-im/element-android/issues/5271))
Other changes
-------------
- Refactoring for safer olm and megolm session usage ([#5380](https://github.com/vector-im/element-android/issues/5380))
- Improve headers UI in Rooms/Messages lists ([#4533](https://github.com/vector-im/element-android/issues/4533))
- Number of unread messages on space badge now include number of unread DMs ([#5260](https://github.com/vector-im/element-android/issues/5260))
- Amend spaces menu to be consistent with iOS version ([#5270](https://github.com/vector-im/element-android/issues/5270))
- Selected space highlight changed in left panel ([#5346](https://github.com/vector-im/element-android/issues/5346))
- [Rooms list] Do not suggest collapse the unique section ([#5347](https://github.com/vector-im/element-android/issues/5347))
- Add analytics support for threads ([#5378](https://github.com/vector-im/element-android/issues/5378))
- Add top margin before our first message ([#5384](https://github.com/vector-im/element-android/issues/5384))
- Improved onboarding registration unit test coverage ([#5408](https://github.com/vector-im/element-android/issues/5408))
- Adds stable room hierarchy endpoint with a fallback to the unstable one ([#5443](https://github.com/vector-im/element-android/issues/5443))
- Use ColorPrimary for attachmentGalleryButton tint ([#5501](https://github.com/vector-im/element-android/issues/5501))
- Added online presence indicator attribute online to match offline styling ([#5513](https://github.com/vector-im/element-android/issues/5513))
- Add a presence sync enabling build config ([#5563](https://github.com/vector-im/element-android/issues/5563))
- Show stickers on click ([#5572](https://github.com/vector-im/element-android/issues/5572))
Changes in Element v1.4.4 (2022-03-09)
======================================

View File

@@ -58,6 +58,7 @@ ext.libs = [
'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle",
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle",
'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle",
'lifecycleRuntimeKtx' : "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle",
'datastore' : "androidx.datastore:datastore:1.0.0",
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
@@ -141,4 +142,4 @@ ext.libs = [
'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1",
'junit' : "junit:junit:4.13.2"
]
]
]

View File

@@ -0,0 +1,2 @@
Main changes in this version: Thread timeline are now live and faster. Various bug fixes and stability improvements.
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.6

View File

@@ -0,0 +1,2 @@
Main changes in this version: Various bug fixes and stability improvements.
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.7

View File

@@ -0,0 +1,2 @@
Principales cambios de esta versión: primera implementación de los hilos de mensajes. Burbujas de mensajes.
Todos los cambios en: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@@ -0,0 +1,2 @@
Principales cambios de esta versión: añadir @room, encuestas cerradas y muchos cambios menores más.
Todos los cambios en: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@@ -0,0 +1,2 @@
تغییرات اصلی در این نگارش: پیاده سازی نخستین پیام‌های رشته‌ای. حباب‌های پیام.
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@@ -0,0 +1,2 @@
تغییرات اصلی در این نگارش: افزودن پشتیبانی به @room و نظرسنجی‌های فاش نشده در کنار تغییرات کوچک دیگر.
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@@ -8,12 +8,13 @@ Az Element egy biztonságos üzenetküldő, és egy csapatmunka app, amely távo
- Videochat, VoIP, és képernyőmegosztási lehetőséggel
- Egyszerű integráció a kedvenc online kollaborációs eszközeiddel, projektkezelési eszközökkel, VoIP szolgáltatásokkal, és más csoportos üzenetküldő alkalmazásokkal
Element is completely different from other messaging and collaboration apps. It operates on Matrix, an open network for secure messaging and decentralized communication. It allows self-hosting to give users maximum ownership and control of their data and messages.
Az Element teljesen más, mint az összes többi üzenetküldő és kollaborációs alkalmazás. A biztonságos üzenetküldést és decentralizált kommunikációt biztosító Matrix platformot használja. Akár egyénileg üzemeltetett szervereket is lehet használni az adatok teljes kontrollálása érdekében.
<b>Privacy and encrypted messaging</b>
Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification.
<b>Magánszféra és titkosított csevegés</b>
Az Element megvéd a nemkívánatos hirdetésektől, adatbányászattól, és a zárt platformoktól. Ezeken felül biztonságban tartja az összes adatod és 1:1 hívásod a végponti titkosításnak és az eszközök-közti hitelesítésnek köszönhetően.
Az Element átadja neked az irányítást a magánszférád felett, miközben lehetővé teszi, hogy biztonságosan kommunikálj bárkivel a Matrix hálózatban, vagy a többi üzleti kommunikációs eszközt használókkal, az olyan appok integrálásának köszönhetően, mint például a Slack.
Element gives you control over your privacy while allowing you to communicate securely with anyone on the Matrix network, or other business collaboration tools by integrating with apps such as Slack.
<b>Element can be self-hosted</b>
To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility.

View File

@@ -0,0 +1,2 @@
Основные изменения в этой версии: Начальная реализация веток сообщений. Сообщения пузыри.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@@ -0,0 +1,2 @@
Основные изменения в этой версии: добавлена поддержка @room и нераскрытых опросов, а также множество других мелких изменений.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
distributionSha256Sum=a9a7b7baba105f6557c9dcf9c3c6e8f7e57e6b49889c5f1d133f015d0727e4be
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -59,7 +59,7 @@ dependencies {
implementation libs.jetbrains.coroutinesCore
implementation libs.jetbrains.coroutinesAndroid
testImplementation 'org.json:json:20211205'
testImplementation 'org.json:json:20220320'
testImplementation libs.tests.junit
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espressoCore

View File

@@ -20,7 +20,6 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.view.ContextMenu
import android.view.Menu
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
@@ -77,10 +76,7 @@ internal abstract class ValueItem : EpoxyModelWithHolder<ValueItem.Holder>() {
menuInfo: ContextMenu.ContextMenuInfo?
) {
if (copyValue != null) {
val menuItem = menu?.add(
Menu.NONE, R.id.copy_value,
Menu.NONE, R.string.copy_value
)
val menuItem = menu?.add(R.string.copy_value)
val clipService =
v?.context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
menuItem?.setOnMenuItemClickListener {

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/copy_value"
android:title="@string/copy_value" />
</menu>

View File

@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="copy_value">Copy Value</string>
</resources>

View File

@@ -71,19 +71,6 @@
android:enabled="false"
android:text="Destructive disabled" />
<Button
style="@style/Widget.Vector.Button.Unelevated.Bot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bot" />
<Button
style="@style/Widget.Vector.Button.Unelevated.Bot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
android:text="Bot disabled" />
<Button
style="@style/Widget.Vector.Button.Outlined"
android:layout_width="wrap_content"
@@ -98,19 +85,6 @@
android:enabled="false"
android:text="Outline disabled" />
<Button
style="@style/Widget.Vector.Button.Outlined.Poll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Poll " />
<Button
style="@style/Widget.Vector.Button.Outlined.Poll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
android:text="Poll disabled" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@@ -74,7 +74,7 @@
android:padding="16dp">
<Button
style="@style/Widget.Vector.Button.Outlined.SocialLogin.Google.Dark"
style="@style/Widget.Vector.Button.Outlined.SocialLogin.Google.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Continue with XXX" />

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#4285F4" android:state_enabled="true"/>
<item android:color="@color/vctr_disabled_view_color_light" android:state_enabled="false"/>
<item android:color="#3367D6" android:state_pressed="true"/>
<item android:color="#4285F4" android:state_focused="true"/>
</selector>

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="DialpadKeyNumberStyle">
<item name="android:textColor">?attr/vctr_content_primary</item>
<item name="android:textSize">@dimen/dialpad_key_numbers_default_size</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginBottom">@dimen/dialpad_key_number_default_margin_bottom</item>
<item name="android:gravity">center</item>
</style>
<style name="DialpadKeyLettersStyle">
<item name="android:textColor">?attr/vctr_content_secondary</item>
<item name="android:textSize">@dimen/dialpad_key_letters_size</item>
<item name="android:fontFamily">sans-serif-regular</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center_horizontal</item>
</style>
<style name="DialpadKeyPoundStyle" parent="DialpadKeyNumberStyle">
<item name="android:textSize">@dimen/dialpad_key_pound_size</item>
<item name="android:layout_marginBottom">@dimen/dialpad_symbol_margin_bottom</item>
</style>
<style name="DialpadKeyStarStyle" parent="DialpadKeyNumberStyle">
<item name="android:textSize">@dimen/dialpad_key_star_size</item>
<item name="android:layout_marginBottom">@dimen/dialpad_symbol_margin_bottom</item>
</style>
</resources>

View File

@@ -9,10 +9,6 @@
<color name="soft_resource_limit_exceeded">#2f9edb</color>
<color name="hard_resource_limit_exceeded">?colorError</color>
<!-- Button color -->
<color name="button_bot_background_color">#14368BD6</color>
<color name="button_bot_enabled_text_color">@color/palette_azure</color>
<!-- Notification (do not depends on theme) -->
<color name="notification_accent_color">@color/palette_azure</color>
<color name="key_share_req_accent_color">@color/palette_melon</color>
@@ -22,7 +18,6 @@
<color name="bg_call_screen_blur">#99000000</color>
<color name="bg_call_screen">#27303A</color>
<color name="vctr_notice_secondary">#FF61708B</color>
<color name="vctr_notice_secondary_alpha12">#1E61708B</color>
<!-- Other useful color -->
@@ -83,16 +78,6 @@
<color name="vctr_touch_guard_bg_dark">#BF000000</color>
<color name="vctr_touch_guard_bg_black">#BF000000</color>
<attr name="vctr_attachment_selector_background" format="color" />
<color name="vctr_attachment_selector_background_light">#FFFFFFFF</color>
<color name="vctr_attachment_selector_background_dark">#FF22262E</color>
<color name="vctr_attachment_selector_background_black">#FF090A0C</color>
<attr name="vctr_attachment_selector_border" format="color" />
<color name="vctr_attachment_selector_border_light">#FFE9EDF1</color>
<color name="vctr_attachment_selector_border_dark">#FF22262E</color>
<color name="vctr_attachment_selector_border_black">#FF090A0C</color>
<attr name="vctr_room_active_widgets_banner_bg" format="color" />
<color name="vctr_room_active_widgets_banner_bg_light">#EBEFF5</color>
<color name="vctr_room_active_widgets_banner_bg_dark">#27303A</color>
@@ -107,9 +92,7 @@
<color name="vctr_waiting_background_color_light">#AAAAAAAA</color>
<color name="vctr_waiting_background_color_dark">#55555555</color>
<attr name="vctr_disabled_view_color" format="color" />
<color name="vctr_disabled_view_color_light">#EEEEEE</color>
<color name="vctr_disabled_view_color_dark">#61708B</color>
<attr name="vctr_reaction_background_off" format="color" />
<color name="vctr_reaction_background_off_light">#FFF3F8FD</color>
@@ -139,4 +122,18 @@
<color name="vctr_presence_indicator_offline_light">@color/palette_gray_100</color>
<color name="vctr_presence_indicator_offline_dark">@color/palette_gray_450</color>
<attr name="vctr_presence_indicator_online" format="color" />
<color name="vctr_presence_indicator_online_light">@color/palette_element_green</color>
<color name="vctr_presence_indicator_online_dark">@color/palette_element_green</color>
<!-- Location sharing colors -->
<attr name="vctr_live_location" format="color" />
<color name="vctr_live_location_light">@color/palette_prune</color>
<color name="vctr_live_location_dark">@color/palette_prune</color>
<!-- Shield colors -->
<color name="shield_color_trust">#0DBD8B</color>
<color name="shield_color_black">#17191C</color>
<color name="shield_color_warning">#FF4B55</color>
</resources>

View File

@@ -9,20 +9,11 @@
<dimen name="layout_vertical_margin_big">32dp</dimen>
<dimen name="profile_avatar_size">50dp</dimen>
<dimen name="floating_action_button_margin">16dp</dimen>
<dimen name="navigation_view_height">196dp</dimen>
<dimen name="navigation_avatar_top_margin">44dp</dimen>
<dimen name="item_decoration_left_margin">72dp</dimen>
<dimen name="item_event_message_state_size">16dp</dimen>
<dimen name="item_event_message_media_button_size">32dp</dimen>
<dimen name="chat_avatar_size">40dp</dimen>
<dimen name="member_list_avatar_size">60dp</dimen>
<dimen name="quote_width">4dp</dimen>
<dimen name="quote_gap">8dp</dimen>
<dimen name="drawable_padding_small">8dp</dimen>
<item name="dialog_width_ratio" format="float" type="dimen">0.75</item>
@@ -70,4 +61,10 @@
<item name="ftue_auth_profile_picture_height" format="float" type="dimen">0.15</item>
<item name="ftue_auth_profile_picture_icon_height" format="float" type="dimen">0.05</item>
</resources>
<!-- Location sharing -->
<dimen name="location_sharing_option_default_padding">10dp</dimen>
<dimen name="location_sharing_locate_button_margin_vertical">16dp</dimen>
<dimen name="location_sharing_locate_button_margin_horizontal">12dp</dimen>
<dimen name="location_sharing_compass_button_margin_horizontal">8dp</dimen>
</resources>

View File

@@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="UnusedResources">
<!-- Define all the colors used across the Element Android project
Source: https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1338%3A17947 -->
<!--
Define all the colors used across the Element Android project
Source: https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1338%3A17947
Some colors are not used, but we want the palette to be complete so we ignore the lint error
UnusedResources for all the resources in this file
-->
<!-- For all themes -->
<color name="palette_azure">#368BD6</color>
@@ -15,6 +19,7 @@
<color name="palette_element_green">#0DBD8B</color>
<color name="palette_white">#FFFFFF</color>
<color name="palette_vermilion">#FF5B55</color>
<!-- (unused) -->
<color name="palette_ems">#7E69FF</color>
<color name="palette_aqua">#2DC2C5</color>
<color name="palette_prune">#5C56F5</color>
@@ -27,6 +32,7 @@
<color name="palette_gray_150">#8D97A5</color>
<color name="palette_gray_200">#737D8C</color>
<color name="palette_black_900">#17191C</color>
<!-- (unused) -->
<color name="palette_ice">#F4F9FD</color>
<!-- For dark themes -->

View File

@@ -35,7 +35,6 @@
<attr name="vctr_system" format="color" />
<color name="element_system_light">@color/palette_gray_25</color>
<color name="element_system_dark">@color/palette_black_950</color>
<color name="element_system_black">@color/palette_black_950</color>
<color name="element_background_light">@color/palette_white</color>
<color name="element_background_dark">@color/palette_black_800</color>
@@ -54,5 +53,4 @@
<color name="element_room_01">@color/palette_verde</color>
<color name="element_room_02">@color/palette_azure</color>
<color name="element_room_03">@color/palette_grape</color>
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LocationSharingOptionView">
<attr name="locShareIcon" format="reference" />
<attr name="locShareIconBackground" format="reference" />
<attr name="locShareIconBackgroundTint" format="color" />
<attr name="locShareIconPadding" format="dimension" />
<attr name="locShareIconDescription" format="string" />
<attr name="locShareTitle" format="string" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MapTilerMapView">
<attr name="showLocateButton" format="boolean" />
</declare-styleable>
</resources>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AttachmentTypeSelectorButton">
<item name="android:layout_width">56dp</item>
<item name="android:layout_height">56dp</item>
<item name="android:scaleType">center</item>
</style>
<style name="AttachmentTypeSelectorLabel">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">?vctr_content_primary</item>
<item name="android:textSize">14sp</item>
<item name="android:layout_marginTop">8dp</item>
</style>
</resources>

View File

@@ -33,24 +33,6 @@
<!-- Keep default colors from the theme -->
</style>
<style name="Widget.Vector.Button.Unelevated" parent="Widget.MaterialComponents.Button.UnelevatedButton">
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
<item name="android:minWidth">94dp</item>
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
<item name="cornerRadius">8dp</item>
<item name="lineHeight">24sp</item>
</style>
<style name="Widget.Vector.Button.Unelevated.Bot">
<item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayBot</item>
</style>
<style name="VectorMaterialThemeOverlayBot">
<item name="colorPrimary">@color/button_bot_background_color</item>
<item name="colorOnPrimary">@color/button_bot_enabled_text_color</item>
</style>
<style name="Widget.Vector.Button.Text" parent="Widget.MaterialComponents.Button.TextButton">
<item name="colorControlHighlight">?colorSecondary</item>
<item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayPositive</item>
@@ -83,9 +65,4 @@
<item name="colorPrimary">?colorOnPrimary</item>
</style>
<style name="Widget.Vector.Button.Outlined.Poll">
<item name="android:minHeight">44dp</item>
<item name="cornerRadius">10dp</item>
</style>
</resources>

View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="UnusedResources">
<!-- This file contain styles to override the style from the library android-dialer-1.2.5.aar
This is why we ignore the lint error UnusedResources -->
<style name="DialpadKeyNumberStyle">
<item name="android:textColor">?vctr_content_primary</item>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.Vector.Button.Text.OnPrimary.LocationLive">
<item name="android:background">?selectableItemBackground</item>
<item name="android:textSize">12sp</item>
<item name="android:padding">0dp</item>
<item name="android:gravity">center</item>
</style>
</resources>

View File

@@ -28,11 +28,6 @@
<item name="android:textColor">@color/black_54</item>
</style>
<style name="Widget.Vector.Button.Outlined.SocialLogin.Google.Dark">
<item name="android:backgroundTint">@color/button_social_google_background_selector_dark</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="Widget.Vector.Button.Outlined.SocialLogin.Github">
<item name="icon">@drawable/ic_social_github</item>
</style>

View File

@@ -11,8 +11,6 @@
<item name="vctr_fab_label_stroke">@color/vctr_fab_label_stroke_black</item>
<item name="vctr_fab_label_color">@color/vctr_fab_label_color_black</item>
<item name="vctr_touch_guard_bg">@color/vctr_touch_guard_bg_black</item>
<item name="vctr_attachment_selector_background">@color/vctr_attachment_selector_background_black</item>
<item name="vctr_attachment_selector_border">@color/vctr_attachment_selector_border_black</item>
<item name="vctr_room_active_widgets_banner_bg">@color/vctr_room_active_widgets_banner_bg_black</item>
<item name="vctr_room_active_widgets_banner_text">@color/vctr_room_active_widgets_banner_text_black</item>
<item name="vctr_reaction_background_off">@color/vctr_reaction_background_off_black</item>

View File

@@ -21,8 +21,6 @@
<item name="vctr_fab_label_color">@color/vctr_fab_label_color_dark</item>
<item name="vctr_touch_guard_bg">@color/vctr_touch_guard_bg_dark</item>
<item name="vctr_keys_backup_banner_accent_color">@color/vctr_keys_backup_banner_accent_color_dark</item>
<item name="vctr_attachment_selector_background">@color/vctr_attachment_selector_background_dark</item>
<item name="vctr_attachment_selector_border">@color/vctr_attachment_selector_border_dark</item>
<item name="vctr_room_active_widgets_banner_bg">@color/vctr_room_active_widgets_banner_bg_dark</item>
<item name="vctr_room_active_widgets_banner_text">@color/vctr_room_active_widgets_banner_text_dark</item>
<item name="vctr_reaction_background_off">@color/vctr_reaction_background_off_dark</item>
@@ -45,6 +43,7 @@
<!-- Presence Indicator colors -->
<item name="vctr_presence_indicator_offline">@color/vctr_presence_indicator_offline_dark</item>
<item name="vctr_presence_indicator_online">@color/vctr_presence_indicator_online_dark</item>
<!-- Some aliases -->
<item name="vctr_header_background">?vctr_system</item>
@@ -145,6 +144,8 @@
<item name="android:actionButtonStyle">@style/Widget.Vector.ActionButton</item>
<!-- Location sharing -->
<item name="vctr_live_location">@color/vctr_live_location_dark</item>
</style>
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />

View File

@@ -21,8 +21,6 @@
<item name="vctr_fab_label_color">@color/vctr_fab_label_color_light</item>
<item name="vctr_touch_guard_bg">@color/vctr_touch_guard_bg_light</item>
<item name="vctr_keys_backup_banner_accent_color">@color/vctr_keys_backup_banner_accent_color_light</item>
<item name="vctr_attachment_selector_background">@color/vctr_attachment_selector_background_light</item>
<item name="vctr_attachment_selector_border">@color/vctr_attachment_selector_border_light</item>
<item name="vctr_room_active_widgets_banner_bg">@color/vctr_room_active_widgets_banner_bg_light</item>
<item name="vctr_room_active_widgets_banner_text">@color/vctr_room_active_widgets_banner_text_light</item>
<item name="vctr_reaction_background_off">@color/vctr_reaction_background_off_light</item>
@@ -45,6 +43,7 @@
<!-- Presence Indicator colors -->
<item name="vctr_presence_indicator_offline">@color/vctr_presence_indicator_offline_light</item>
<item name="vctr_presence_indicator_online">@color/vctr_presence_indicator_online_light</item>
<!-- Some aliases -->
<item name="vctr_header_background">?vctr_system</item>
@@ -146,6 +145,8 @@
<item name="android:actionButtonStyle">@style/Widget.Vector.ActionButton</item>
<!-- Location sharing -->
<item name="vctr_live_location">@color/vctr_live_location_light</item>
</style>
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />

View File

@@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.session.room.send.UserDraft
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
@@ -101,13 +102,18 @@ class FlowRoom(private val room: Room) {
return room.getLiveRoomNotificationState().asFlow()
}
fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
return room.getAllThreadSummariesLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getAllThreadSummaries()
}
}
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
return room.getAllThreadsLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getAllThreads()
}
}
fun liveLocalUnreadThreadList(): Flow<List<ThreadRootEvent>> {
return room.getMarkedThreadNotificationsLive().asFlow()
.startWith(room.coroutineDispatchers.io) {

View File

@@ -31,12 +31,11 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
buildConfigField "String", "SDK_VERSION", "\"1.4.4\""
buildConfigField "String", "SDK_VERSION", "\"1.4.7\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
buildConfigField "String", "GIT_SDK_REVISION_DATE", "\"${gitRevisionDate()}\""
defaultConfig {
consumerProguardFiles 'proguard-rules.pro'
@@ -167,7 +166,7 @@ dependencies {
implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.44'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45'
testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.7.3'
@@ -175,7 +174,7 @@ dependencies {
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation libs.mockk.mockk
testImplementation libs.tests.kluent
implementation libs.jetbrains.coroutinesAndroid
testImplementation libs.jetbrains.coroutinesTest
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
// Transitively required for mocking realm as monarchy doesn't expose Rx

View File

@@ -23,7 +23,7 @@ object TestConstants {
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
// Time out to use when waiting for server response.
private const val AWAIT_TIME_OUT_MILLIS = 30_000
private const val AWAIT_TIME_OUT_MILLIS = 60_000
// Time out to use when waiting for server response, when the debugger is connected. 10 minutes
private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000

View File

@@ -0,0 +1,649 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.crypto
import android.util.Log
import androidx.test.filters.LargeTest
import kotlinx.coroutines.delay
import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals
import org.junit.Assert
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.common.TestMatrixCallback
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@LargeTest
class E2eeSanityTests : InstrumentedTest {
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
/**
* Simple test that create an e2ee room.
* Some new members are added, and a message is sent.
* We check that the message is e2e and can be decrypted.
*
* Additional users join, we check that they can't decrypt history
*
* Alice sends a new message, then check that the new one can be decrypted
*/
@Test
fun testSendingE2EEMessages() {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val e2eRoomID = cryptoTestData.roomId
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
// add some more users and invite them
val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
.map {
testHelper.createAccount(it, SessionTestParams(true))
}
Log.v("#E2E TEST", "All accounts created")
// we want to invite them in the room
otherAccounts.forEach {
testHelper.runBlockingTest {
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
aliceRoomPOV.invite(it.myUserId)
}
}
// All user should accept invite
otherAccounts.forEach { otherSession ->
waitForAndAcceptInviteInRoom(otherSession, e2eRoomID)
Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
}
// check that alice see them as joined (not really necessary?)
ensureMembersHaveJoined(aliceSession, otherAccounts, e2eRoomID)
Log.v("#E2E TEST", "All users have joined the room")
Log.v("#E2E TEST", "Alice is sending the message")
val text = "This is my message"
val sentEventId: String? = sendMessageInRoom(aliceRoomPOV, text)
// val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
Assert.assertTrue("Message should be sent", sentEventId != null)
// All should be able to decrypt
otherAccounts.forEach { otherSession ->
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
timelineEvent != null &&
timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE
}
}
}
// Add a new user to the room, and check that he can't decrypt
val newAccount = listOf("adam") // , "adam", "manu")
.map {
testHelper.createAccount(it, SessionTestParams(true))
}
newAccount.forEach {
testHelper.runBlockingTest {
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
aliceRoomPOV.invite(it.myUserId)
}
}
newAccount.forEach {
waitForAndAcceptInviteInRoom(it, e2eRoomID)
}
ensureMembersHaveJoined(aliceSession, newAccount, e2eRoomID)
// wait a bit
testHelper.runBlockingTest {
delay(3_000)
}
// check that messages are encrypted (uisi)
newAccount.forEach { otherSession ->
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
}
timelineEvent != null &&
timelineEvent.root.getClearType() == EventType.ENCRYPTED &&
timelineEvent.root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
}
}
}
// Let alice send a new message
Log.v("#E2E TEST", "Alice sends a new message")
val secondMessage = "2 This is my message"
val secondSentEventId: String? = sendMessageInRoom(aliceRoomPOV, secondMessage)
// new members should be able to decrypt it
newAccount.forEach { otherSession ->
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also {
Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
}
timelineEvent != null &&
timelineEvent.root.getClearType() == EventType.MESSAGE &&
secondMessage == timelineEvent.root.getClearContent().toModel<MessageContent>()?.body
}
}
}
otherAccounts.forEach {
testHelper.signOutAndClose(it)
}
newAccount.forEach { testHelper.signOutAndClose(it) }
cryptoTestData.cleanUp(testHelper)
}
/**
* Quick test for basic key backup
* 1. Create e2e between Alice and Bob
* 2. Alice sends 3 messages, using 3 different sessions
* 3. Ensure bob can decrypt
* 4. Create backup for bob and upload keys
*
* 5. Sign out alice and bob to ensure no gossiping will happen
*
* 6. Let bob sign in with a new session
* 7. Ensure history is UISI
* 8. Import backup
* 9. Check that new session can decrypt
*/
@Test
fun testBasicBackupImport() {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val e2eRoomID = cryptoTestData.roomId
Log.v("#E2E TEST", "Create and start key backup for bob ...")
val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
val keyBackupPassword = "FooBarBaz"
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
}
val version = testHelper.doSync<KeysVersion> {
bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
Log.v("#E2E TEST", "... Key backup started and enabled for bob")
// Bob session should now have
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
// let's send a few message to bob
val sentEventIds = mutableListOf<String>()
val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning")
messagesText.forEach { text ->
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
sentEventIds.add(it)
}
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
timelineEvent != null &&
timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE
}
}
// we want more so let's discard the session
aliceSession.cryptoService().discardOutboundSession(e2eRoomID)
testHelper.runBlockingTest {
delay(1_000)
}
}
Log.v("#E2E TEST", "Bob received all and can decrypt")
// Let's wait a bit to be sure that bob has backed up the session
Log.v("#E2E TEST", "Force key backup for Bob...")
testHelper.waitWithLatch { latch ->
bobKeysBackupService.backupAllGroupSessions(
null,
TestMatrixCallback(latch, true)
)
}
Log.v("#E2E TEST", "... Key backup done for Bob")
// Now lets logout both alice and bob to ensure that we won't have any gossiping
val bobUserId = bobSession.myUserId
Log.v("#E2E TEST", "Logout alice and bob...")
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSession)
Log.v("#E2E TEST", "..Logout alice and bob...")
testHelper.runBlockingTest {
delay(1_000)
}
// Create a new session for bob
Log.v("#E2E TEST", "Create a new session for Bob")
val newBobSession = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
// check that bob can't currently decrypt
Log.v("#E2E TEST", "check that bob can't currently decrypt")
sentEventIds.forEach { sentEventId ->
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}")
}
timelineEvent != null &&
timelineEvent.root.getClearType() == EventType.ENCRYPTED
}
}
}
// after initial sync events are not decrypted, so we have to try manually
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// Let's now import keys from backup
newBobSession.cryptoService().keysBackupService().let { keysBackupService ->
val keyVersionResult = testHelper.doSync<KeysVersionResult?> {
keysBackupService.getVersion(version.version, it)
}
val importedResult = testHelper.doSync<ImportRoomKeysResult> {
keysBackupService.restoreKeyBackupWithPassword(keyVersionResult!!,
keyBackupPassword,
null,
null,
null, it)
}
assertEquals(3, importedResult.totalNumberOfKeys)
}
// ensure bob can now decrypt
ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
testHelper.signOutAndClose(newBobSession)
}
/**
* Check that a new verified session that was not supposed to get the keys initially will
* get them from an older one.
*/
@Test
fun testSimpleGossip() {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val e2eRoomID = cryptoTestData.roomId
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
cryptoTestHelper.initializeCrossSigning(bobSession)
// let's send a few message to bob
val sentEventIds = mutableListOf<String>()
val messagesText = listOf("1. Hello", "2. Bob")
Log.v("#E2E TEST", "Alice sends some messages")
messagesText.forEach { text ->
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
sentEventIds.add(it)
}
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
timelineEvent != null &&
timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE
}
}
}
// Ensure bob can decrypt
ensureIsDecrypted(sentEventIds, bobSession, e2eRoomID)
// Let's now add a new bob session
// Create a new session for bob
Log.v("#E2E TEST", "Create a new session for Bob")
val newBobSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
// check that new bob can't currently decrypt
Log.v("#E2E TEST", "check that new bob can't currently decrypt")
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// Try to request
sentEventIds.forEach { sentEventId ->
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
newBobSession.cryptoService().requestRoomKeyForEvent(event)
}
// wait a bit
testHelper.runBlockingTest {
delay(10_000)
}
// Ensure that new bob still can't decrypt (keys must have been withheld)
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.KEYS_WITHHELD)
// Now mark new bob session as verified
bobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!)
newBobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(bobSession.myUserId, bobSession.sessionParams.deviceId!!)
// now let new session re-request
sentEventIds.forEach { sentEventId ->
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
}
// wait a bit
testHelper.runBlockingTest {
delay(10_000)
}
ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
cryptoTestData.cleanUp(testHelper)
testHelper.signOutAndClose(newBobSession)
}
/**
* Test that if a better key is forwarded (lower index, it is then used)
*/
@Test
fun testForwardBetterKey() {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val bobSessionWithBetterKey = cryptoTestData.secondSession!!
val e2eRoomID = cryptoTestData.roomId
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
cryptoTestHelper.initializeCrossSigning(bobSessionWithBetterKey)
// let's send a few message to bob
var firstEventId: String
val firstMessage = "1. Hello"
Log.v("#E2E TEST", "Alice sends some messages")
firstMessage.let { text ->
firstEventId = sendMessageInRoom(aliceRoomPOV, text)!!
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
timelineEvent != null &&
timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE
}
}
}
// Ensure bob can decrypt
ensureIsDecrypted(listOf(firstEventId), bobSessionWithBetterKey, e2eRoomID)
// Let's add a new unverified session from bob
val newBobSession = testHelper.logIntoAccount(bobSessionWithBetterKey.myUserId, SessionTestParams(true))
// check that new bob can't currently decrypt
Log.v("#E2E TEST", "check that new bob can't currently decrypt")
ensureCannotDecrypt(listOf(firstEventId), newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// Now let alice send a new message. this time the new bob session will be able to decrypt
var secondEventId: String
val secondMessage = "2. New Device?"
Log.v("#E2E TEST", "Alice sends some messages")
secondMessage.let { text ->
secondEventId = sendMessageInRoom(aliceRoomPOV, text)!!
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
timelineEvent != null &&
timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE
}
}
}
// check that both messages have same sessionId (it's just that we don't have index 0)
val firstEventNewBobPov = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
val secondEventNewBobPov = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
val firstSessionId = firstEventNewBobPov!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
val secondSessionId = secondEventNewBobPov!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
Assert.assertTrue("Should be the same session id", firstSessionId == secondSessionId)
// Confirm we can decrypt one but not the other
testHelper.runBlockingTest {
try {
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
fail("Should not be able to decrypt event")
} catch (error: MXCryptoError) {
val errorType = (error as? MXCryptoError.Base)?.errorType
assertEquals(MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX, errorType)
}
}
testHelper.runBlockingTest {
try {
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
} catch (error: MXCryptoError) {
fail("Should be able to decrypt event")
}
}
// Now let's verify bobs session, and re-request keys
bobSessionWithBetterKey.cryptoService()
.verificationService()
.markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!)
newBobSession.cryptoService()
.verificationService()
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!)
// now let new session request
newBobSession.cryptoService().requestRoomKeyForEvent(firstEventNewBobPov.root)
// wait a bit
testHelper.runBlockingTest {
delay(10_000)
}
// old session should have shared the key at earliest known index now
// we should be able to decrypt both
testHelper.runBlockingTest {
try {
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
} catch (error: MXCryptoError) {
fail("Should be able to decrypt first event now $error")
}
}
testHelper.runBlockingTest {
try {
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
} catch (error: MXCryptoError) {
fail("Should be able to decrypt event $error")
}
}
cryptoTestData.cleanUp(testHelper)
testHelper.signOutAndClose(newBobSession)
}
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
aliceRoomPOV.sendTextMessage(text)
var sentEventId: String? = null
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
timeline.start()
testHelper.retryPeriodicallyWithLatch(latch) {
val decryptedMsg = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE }
.also { list ->
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
Log.v("#E2E TEST", "Timeline snapshot is $message")
}
.filter { it.root.sendState == SendState.SYNCED }
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
sentEventId = decryptedMsg?.eventId
decryptedMsg != null
}
timeline.dispose()
}
return sentEventId
}
private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
otherAccounts.map {
aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership
}.all {
it == Membership.JOIN
}
}
}
}
private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val roomSummary = otherSession.getRoomSummary(e2eRoomID)
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
if (it) {
Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice")
}
}
}
}
testHelper.runBlockingTest(60_000) {
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
try {
otherSession.joinRoom(e2eRoomID)
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
// it's ok we will wait after
}
}
Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
val roomSummary = otherSession.getRoomSummary(e2eRoomID)
roomSummary != null && roomSummary.membership == Membership.JOIN
}
}
}
private fun ensureCanDecrypt(sentEventIds: MutableList<String>, session: Session, e2eRoomID: String, messagesText: List<String>) {
sentEventIds.forEachIndexed { index, sentEventId ->
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val event = session.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
testHelper.runBlockingTest {
try {
session.cryptoService().decryptEvent(event, "").let { result ->
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
}
} catch (error: MXCryptoError) {
// nop
}
}
event.getClearType() == EventType.MESSAGE &&
messagesText[index] == event.getClearContent()?.toModel<MessageContent>()?.body
}
}
}
}
private fun ensureIsDecrypted(sentEventIds: List<String>, session: Session, e2eRoomID: String) {
testHelper.waitWithLatch { latch ->
sentEventIds.forEach { sentEventId ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timelineEvent = session.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
timelineEvent != null &&
timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE
}
}
}
}
private fun ensureCannotDecrypt(sentEventIds: List<String>, newBobSession: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType?) {
sentEventIds.forEach { sentEventId ->
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
testHelper.runBlockingTest {
try {
newBobSession.cryptoService().decryptEvent(event, "")
fail("Should not be able to decrypt event")
} catch (error: MXCryptoError) {
val errorType = (error as? MXCryptoError.Base)?.errorType
if (expectedError == null) {
Assert.assertNotNull(errorType)
} else {
assertEquals(expectedError, errorType, "Message expected to be UISI")
}
}
}
}
}
}

View File

@@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -41,7 +40,6 @@ class PreShareKeysTest : InstrumentedTest {
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test
@Ignore("This test will be ignored until it is fixed")
fun ensure_outbound_session_happy_path() {
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val e2eRoomID = testData.roomId
@@ -92,7 +90,7 @@ class PreShareKeysTest : InstrumentedTest {
// Just send a real message as test
val sentEvent = testHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session")
assertEquals("Unexpected megolm session", megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId,)
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE

View File

@@ -21,7 +21,6 @@ import org.amshove.kluent.shouldBe
import org.junit.Assert
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -85,7 +84,6 @@ class UnwedgingTest : InstrumentedTest {
* -> This is automatically fixed after SDKs restarted the olm session
*/
@Test
@Ignore("This test will be ignored until it is fixed")
fun testUnwedging() {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
@@ -94,9 +92,7 @@ class UnwedgingTest : InstrumentedTest {
val bobSession = cryptoTestData.secondSession!!
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
// bobSession.cryptoService().setWarnOnUnknownDevices(false)
// aliceSession.cryptoService().setWarnOnUnknownDevices(false)
val olmDevice = (aliceSession.cryptoService() as DefaultCryptoService).olmDeviceForTest
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
@@ -175,6 +171,7 @@ class UnwedgingTest : InstrumentedTest {
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!)
olmDevice.clearOlmSessionCache()
Thread.sleep(6_000)
// Force new session, and key share
@@ -227,8 +224,10 @@ class UnwedgingTest : InstrumentedTest {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
// we should get back the key and be able to decrypt
val result = tryOrNull {
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
val result = testHelper.runBlockingTest {
tryOrNull {
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
}
}
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
result != null

View File

@@ -97,7 +97,9 @@ class KeyShareTests : InstrumentedTest {
assert(receivedEvent!!.isEncrypted())
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
commonTestHelper.runBlockingTest {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
fail("should fail")
} catch (failure: Throwable) {
}
@@ -152,7 +154,9 @@ class KeyShareTests : InstrumentedTest {
}
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
commonTestHelper.runBlockingTest {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
fail("should fail")
} catch (failure: Throwable) {
}
@@ -189,7 +193,9 @@ class KeyShareTests : InstrumentedTest {
}
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
commonTestHelper.runBlockingTest {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
} catch (failure: Throwable) {
fail("should have been able to decrypt")
}
@@ -384,7 +390,11 @@ class KeyShareTests : InstrumentedTest {
val roomRoomBobPov = aliceSession.getRoom(roomId)
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
var dRes = tryOrNull {
commonTestHelper.runBlockingTest {
bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "")
}
}
assert(dRes == null)
@@ -395,7 +405,11 @@ class KeyShareTests : InstrumentedTest {
Thread.sleep(3_000)
// With the bug the first session would have improperly reshare that key :/
dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
dRes = tryOrNull {
commonTestHelper.runBlockingTest {
bobSession.cryptoService().decryptEvent(beforeJoin.root, "")
}
}
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
assert(dRes?.clearEvent == null)
}

View File

@@ -93,7 +93,9 @@ class WithHeldTests : InstrumentedTest {
// Bob should not be able to decrypt because the keys is withheld
try {
// .. might need to wait a bit for stability?
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
testHelper.runBlockingTest {
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
}
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@@ -118,7 +120,9 @@ class WithHeldTests : InstrumentedTest {
// Previous message should still be undecryptable (partially withheld session)
try {
// .. might need to wait a bit for stability?
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
testHelper.runBlockingTest {
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
}
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@@ -165,7 +169,9 @@ class WithHeldTests : InstrumentedTest {
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
try {
// .. might need to wait a bit for stability?
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
testHelper.runBlockingTest {
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
}
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@@ -233,7 +239,11 @@ class WithHeldTests : InstrumentedTest {
testHelper.retryPeriodicallyWithLatch(latch) {
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)?.also {
// try to decrypt and force key request
tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
tryOrNull {
testHelper.runBlockingTest {
bobSecondSession.cryptoService().decryptEvent(it.root, "")
}
}
}
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
timeLineEvent != null

View File

@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -39,6 +40,7 @@ import kotlin.coroutines.resume
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@Ignore("This test is flaky ; see issue #5449")
class VerificationTest : InstrumentedTest {
data class ExpectedResult(

View File

@@ -60,7 +60,9 @@ class MarkdownParserTest : InstrumentedTest {
applicationFlavor = "TestFlavor",
roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
)
))
),
TestPermalinkService()
)
)
@Test

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.room.send
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.SpanTemplateType.HTML
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.SpanTemplateType.MARKDOWN
class TestPermalinkService : PermalinkService {
override fun createPermalink(event: Event, forceMatrixTo: Boolean): String? {
return null
}
override fun createPermalink(id: String, forceMatrixTo: Boolean): String? {
return ""
}
override fun createPermalink(roomId: String, eventId: String, forceMatrixTo: Boolean): String {
return ""
}
override fun createRoomPermalink(roomId: String, viaServers: List<String>?, forceMatrixTo: Boolean): String? {
return null
}
override fun getLinkedId(url: String): String? {
return null
}
override fun createMentionSpanTemplate(type: PermalinkService.SpanTemplateType, forceMatrixTo: Boolean): String {
return when (type) {
HTML -> "<a href=\"https://matrix.to/#/%1\$s\">%2\$s</a>"
MARKDOWN -> "[%2\$s](https://matrix.to/#/%1\$s)"
}
}
}

View File

@@ -62,7 +62,11 @@ internal class ChunkEntityTest : InstrumentedTest {
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealm(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(
roomId = ROOM_ID,
eventEntity = fakeEvent,
direction = PaginationDirection.FORWARDS,
roomMemberContentsByUser = emptyMap())
chunk.timelineEvents.size shouldBeEqualTo 1
}
}
@@ -74,8 +78,16 @@ internal class ChunkEntityTest : InstrumentedTest {
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealm(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(
roomId = ROOM_ID,
eventEntity = fakeEvent,
direction = PaginationDirection.FORWARDS,
roomMemberContentsByUser = emptyMap())
chunk.addTimelineEvent(
roomId = ROOM_ID,
eventEntity = fakeEvent,
direction = PaginationDirection.FORWARDS,
roomMemberContentsByUser = emptyMap())
chunk.timelineEvents.size shouldBeEqualTo 1
}
}
@@ -144,7 +156,11 @@ internal class ChunkEntityTest : InstrumentedTest {
val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealm(it)
}
addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
addTimelineEvent(
roomId = roomId,
eventEntity = fakeEvent,
direction = direction,
roomMemberContentsByUser = emptyMap())
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.session.room.timeline
import org.amshove.kluent.fail
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeGreaterThan
import org.amshove.kluent.shouldContain
import org.amshove.kluent.shouldContainAll
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class PollAggregationTest : InstrumentedTest {
@Test
fun testAllPollUseCases() {
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
val roomFromBobPOV = cryptoTestData.secondSession!!.getRoom(cryptoTestData.roomId)!!
// Bob creates a poll
roomFromBobPOV.sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions)
aliceSession.startSync(true)
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
aliceTimeline.start()
val TOTAL_TEST_COUNT = 7
val lock = CountDownLatch(TOTAL_TEST_COUNT)
val aliceEventsListener = object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
snapshot.firstOrNull { it.root.getClearType() in EventType.POLL_START }?.let { pollEvent ->
val pollEventId = pollEvent.eventId
val pollContent = pollEvent.root.content?.toModel<MessagePollContent>()
val pollSummary = pollEvent.annotations?.pollResponseSummary
if (pollContent == null) {
fail("Poll content is null")
return
}
when (lock.count.toInt()) {
TOTAL_TEST_COUNT -> {
// Poll has just been created.
testInitialPollConditions(pollContent, pollSummary)
lock.countDown()
roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
}
TOTAL_TEST_COUNT - 1 -> {
// Bob: Option 1
testBobVotesOption1(pollContent, pollSummary)
lock.countDown()
roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
}
TOTAL_TEST_COUNT - 2 -> {
// Bob: Option 2
testBobChangesVoteToOption2(pollContent, pollSummary)
lock.countDown()
roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
}
TOTAL_TEST_COUNT - 3 -> {
// Alice: Option 2, Bob: Option 2
testAliceAndBobVoteToOption2(pollContent, pollSummary)
lock.countDown()
roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
}
TOTAL_TEST_COUNT - 4 -> {
// Alice: Option 1, Bob: Option 2
testAliceVotesOption1AndBobVotesOption2(pollContent, pollSummary)
lock.countDown()
roomFromBobPOV.endPoll(pollEventId)
}
TOTAL_TEST_COUNT - 5 -> {
// Alice: Option 1, Bob: Option 2 [poll is ended]
testEndedPoll(pollSummary)
lock.countDown()
roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
}
TOTAL_TEST_COUNT - 6 -> {
// Alice: Option 1 (ignore change), Bob: Option 2 [poll is ended]
testAliceVotesOption1AndBobVotesOption2(pollContent, pollSummary)
testEndedPoll(pollSummary)
lock.countDown()
}
else -> {
fail("Lock count ${lock.count} didn't handled.")
}
}
}
}
}
aliceTimeline.addListener(aliceEventsListener)
commonTestHelper.await(lock)
aliceTimeline.removeAllListeners()
aliceSession.stopSync()
aliceTimeline.dispose()
cryptoTestData.cleanUp(commonTestHelper)
}
private fun testInitialPollConditions(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) {
// No votes yet, poll summary should be null
pollSummary shouldBe null
// Question should be the same as intended
pollContent.getBestPollCreationInfo()?.question?.getBestQuestion() shouldBeEqualTo pollQuestion
// Options should be the same as intended
pollContent.getBestPollCreationInfo()?.answers?.let { answers ->
answers.size shouldBeEqualTo pollOptions.size
answers.map { it.getBestAnswer() } shouldContainAll pollOptions
}
}
private fun testBobVotesOption1(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) {
if (pollSummary == null) {
fail("Poll summary shouldn't be null when someone votes")
return
}
val answerId = pollContent.getBestPollCreationInfo()?.answers?.first()?.id
// Check if the intended vote is in poll summary
pollSummary.aggregatedContent?.let { aggregatedContent ->
assertTotalVotesCount(aggregatedContent, 1)
aggregatedContent.votes?.first()?.option shouldBeEqualTo answerId
aggregatedContent.votesSummary?.get(answerId)?.total shouldBeEqualTo 1
aggregatedContent.votesSummary?.get(answerId)?.percentage shouldBeEqualTo 1.0
} ?: run { fail("Aggregated poll content shouldn't be null after someone votes") }
}
private fun testBobChangesVoteToOption2(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) {
if (pollSummary == null) {
fail("Poll summary shouldn't be null when someone votes")
return
}
val answerId = pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id
// Check if the intended vote is in poll summary
pollSummary.aggregatedContent?.let { aggregatedContent ->
assertTotalVotesCount(aggregatedContent, 1)
aggregatedContent.votes?.first()?.option shouldBeEqualTo answerId
aggregatedContent.votesSummary?.get(answerId)?.total shouldBeEqualTo 1
aggregatedContent.votesSummary?.get(answerId)?.percentage shouldBeEqualTo 1.0
} ?: run { fail("Aggregated poll content shouldn't be null after someone votes") }
}
private fun testAliceAndBobVoteToOption2(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) {
if (pollSummary == null) {
fail("Poll summary shouldn't be null when someone votes")
return
}
val answerId = pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id
// Check if the intended votes is in poll summary
pollSummary.aggregatedContent?.let { aggregatedContent ->
assertTotalVotesCount(aggregatedContent, 2)
aggregatedContent.votes?.first()?.option shouldBeEqualTo answerId
aggregatedContent.votes?.get(1)?.option shouldBeEqualTo answerId
aggregatedContent.votesSummary?.get(answerId)?.total shouldBeEqualTo 2
aggregatedContent.votesSummary?.get(answerId)?.percentage shouldBeEqualTo 1.0
} ?: run { fail("Aggregated poll content shouldn't be null after someone votes") }
}
private fun testAliceVotesOption1AndBobVotesOption2(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) {
if (pollSummary == null) {
fail("Poll summary shouldn't be null when someone votes")
return
}
val firstAnswerId = pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id
val secondAnswerId = pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id
// Check if the intended votes is in poll summary
pollSummary.aggregatedContent?.let { aggregatedContent ->
assertTotalVotesCount(aggregatedContent, 2)
aggregatedContent.votes!!.map { it.option } shouldContain firstAnswerId
aggregatedContent.votes!!.map { it.option } shouldContain secondAnswerId
aggregatedContent.votesSummary?.get(firstAnswerId)?.total shouldBeEqualTo 1
aggregatedContent.votesSummary?.get(secondAnswerId)?.total shouldBeEqualTo 1
aggregatedContent.votesSummary?.get(firstAnswerId)?.percentage shouldBeEqualTo 0.5
aggregatedContent.votesSummary?.get(secondAnswerId)?.percentage shouldBeEqualTo 0.5
} ?: run { fail("Aggregated poll content shouldn't be null after someone votes") }
}
private fun testEndedPoll(pollSummary: PollResponseAggregatedSummary?) {
pollSummary?.closedTime ?: 0 shouldBeGreaterThan 0
}
private fun assertTotalVotesCount(aggregatedContent: PollSummaryContent, expectedVoteCount: Int) {
aggregatedContent.totalVotes shouldBeEqualTo expectedVoteCount
aggregatedContent.votes?.size shouldBeEqualTo expectedVoteCount
}
companion object {
const val pollQuestion = "Do you like creating polls?"
val pollOptions = listOf("Yes", "Absolutely", "As long as tests pass")
}
}

View File

@@ -60,7 +60,15 @@ data class MatrixConfiguration(
/**
* RoomDisplayNameFallbackProvider to provide default room display name.
*/
val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider
val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider,
/**
* True to enable presence information sync (if available). False to disable regardless of server setting.
*/
val presenceSyncEnabled: Boolean = true,
/**
* Thread messages default enable/disabled value
*/
val threadMessagesEnabledDefault: Boolean = false,
) {
/**

View File

@@ -121,7 +121,7 @@ interface CryptoService {
fun discardOutboundSession(roomId: String)
@Throws(MXCryptoError::class)
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)

View File

@@ -49,5 +49,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AggregatedRelations(
@Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null,
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null,
@Json(name = RelationType.THREAD) val latestThread: LatestThreadUnsignedRelation? = null
)

View File

@@ -201,7 +201,11 @@ data class Event(
*/
fun getDecryptedTextSummary(): String? {
if (isRedacted()) return "Message Deleted"
val text = getDecryptedValue() ?: return null
val text = getDecryptedValue() ?: run {
if (isPoll()) { return getPollQuestion() ?: "created a poll." }
return null
}
return when {
isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text)
isFileMessage() -> "sent a file."
@@ -349,7 +353,7 @@ fun Event.isAttachmentMessage(): Boolean {
}
}
fun Event.isPoll(): Boolean = getClearType() == EventType.POLL_START || getClearType() == EventType.POLL_END
fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClearType() in EventType.POLL_END
fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
@@ -372,7 +376,7 @@ fun Event.getRelationContent(): RelationDefaultContent? {
* Returns the poll question or null otherwise
*/
fun Event.getPollQuestion(): String? =
getPollContent()?.pollCreationInfo?.question?.question
getPollContent()?.getBestPollCreationInfo()?.question?.getBestQuestion()
/**
* Returns the relation content for a specific type or null otherwise
@@ -385,12 +389,12 @@ fun Event.isReply(): Boolean {
}
fun Event.isReplyRenderedInThread(): Boolean {
return isReply() && getRelationContent()?.inReplyTo?.shouldRenderInThread() == true
return isReply() && getRelationContent()?.shouldRenderInThread() == true
}
fun Event.isThread(): Boolean = getRelationContentForType(RelationType.IO_THREAD)?.eventId != null
fun Event.isThread(): Boolean = getRelationContentForType(RelationType.THREAD)?.eventId != null
fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.IO_THREAD)?.eventId
fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.THREAD)?.eventId
fun Event.isEdition(): Boolean {
return getRelationContentForType(RelationType.REPLACE)?.eventId != null
@@ -406,3 +410,5 @@ fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER &&
fun Event.getPollContent(): MessagePollContent? {
return content.toModel<MessagePollContent>()
}
fun Event.supportsNotification() = this.getClearType() in EventType.MESSAGE + EventType.POLL_START

View File

@@ -103,9 +103,9 @@ object EventType {
const val REACTION = "m.reaction"
// Poll
const val POLL_START = "org.matrix.msc3381.poll.start"
const val POLL_RESPONSE = "org.matrix.msc3381.poll.response"
const val POLL_END = "org.matrix.msc3381.poll.end"
val POLL_START = listOf("org.matrix.msc3381.poll.start", "m.poll.start")
val POLL_RESPONSE = listOf("org.matrix.msc3381.poll.response", "m.poll.response")
val POLL_END = listOf("org.matrix.msc3381.poll.end", "m.poll.end")
// Unwedging
internal const val DUMMY = "m.dummy"

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.events.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class LatestThreadUnsignedRelation(
override val limited: Boolean? = false,
override val count: Int? = 0,
@Json(name = "latest_event")
val event: Event? = null,
@Json(name = "current_user_participated")
val isUserParticipating: Boolean? = false
) : UnsignedRelationInfo

View File

@@ -30,7 +30,6 @@ object RelationType {
/** Lets you define an event which is a thread reply to an existing event.*/
const val THREAD = "m.thread"
const val IO_THREAD = "io.element.thread"
/** Lets you define an event which adds a response to an existing event.*/
const val RESPONSE = "org.matrix.response"

View File

@@ -50,7 +50,11 @@ data class HomeServerCapabilities(
* This capability describes the default and available room versions a server supports, and at what level of stability.
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
*/
val roomVersions: RoomVersionCapabilities? = null
val roomVersions: RoomVersionCapabilities? = null,
/**
* True if the home server support threading
*/
var canUseThreading: Boolean = false
) {
enum class RoomCapabilitySupport {

View File

@@ -28,6 +28,11 @@ interface PermalinkService {
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
}
enum class SpanTemplateType {
HTML,
MARKDOWN
}
/**
* Creates a permalink for an event.
* Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org"
@@ -80,4 +85,15 @@ interface PermalinkService {
* @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink
*/
fun getLinkedId(url: String): String?
/**
* Creates a HTML or Markdown mention span template. Can be used to replace a mention with a permalink to mentioned user.
* Ex: "<a href=\"https://matrix.to/#/%1\$s\">%2\$s</a>" or "[%2\$s](https://matrix.to/#/%1\$s)"
*
* @param type: type of template to create
* @param forceMatrixTo whether we should force using matrix.to base URL
*
* @return the created template
*/
fun createMentionSpanTemplate(type: SpanTemplateType, forceMatrixTo: Boolean = false): String
}

View File

@@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.send.SendService
import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.session.room.tags.TagsService
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
@@ -47,6 +48,7 @@ import org.matrix.android.sdk.api.util.Optional
interface Room :
TimelineService,
ThreadsService,
ThreadsLocalService,
SendService,
DraftService,
ReadService,

View File

@@ -216,6 +216,12 @@ interface RoomService {
pagedListConfig: PagedList.Config = defaultPagedListConfig,
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
/**
* Return a LiveData on the number of rooms
* @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
*/
fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int>
/**
* TODO Doc
*/

View File

@@ -22,10 +22,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
interface UpdatableLivePageResult {
val livePagedList: LiveData<PagedList<RoomSummary>>
fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams)
val liveBoundaries: LiveData<ResultBoundaries>
var queryParams: RoomSummaryQueryParams
}
data class ResultBoundaries(

View File

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

View File

@@ -16,11 +16,20 @@
package org.matrix.android.sdk.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Define what particular asset is being referred to.
* We don't use enum type since it is not limited to a specific set of values.
* The way this type should be interpreted in client side is described in
* [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md)
*/
object LocationAssetType {
/**
* Used for user location sharing.
**/
const val SELF = "m.self"
@JsonClass(generateAdapter = false)
enum class LocationAssetType {
@Json(name = "m.self")
SELF
/**
* Used for pin drop location sharing.
**/
const val PIN = "m.pin"
}

View File

@@ -39,37 +39,47 @@ data class MessageLocationContent(
*/
@Json(name = "geo_uri") val geoUri: String,
/**
* See https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md
*/
@Json(name = "org.matrix.msc3488.location") val locationInfo: LocationInfo? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null,
/**
* m.asset defines a generic asset that can be used for location tracking but also in other places like
* inventories, geofencing, checkins/checkouts etc.
* It should contain a mandatory namespaced type key defining what particular asset is being referred to.
* For the purposes of user location tracking m.self should be used in order to avoid duplicating the mxid.
* See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md)
*/
@Json(name = "m.asset") val locationAsset: LocationAsset? = null,
@Json(name = "org.matrix.msc3488.location") val unstableLocationInfo: LocationInfo? = null,
@Json(name = "m.location") val locationInfo: LocationInfo? = null,
/**
* Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
*/
@Json(name = "org.matrix.msc3488.ts") val ts: Long? = null,
@Json(name = "org.matrix.msc1767.text") val text: String? = null
@Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null,
@Json(name = "m.ts") val ts: Long? = null,
@Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
@Json(name = "m.text") val text: String? = null,
/**
* Defines a generic asset that can be used for location tracking but also in other places like
* inventories, geofencing, checkins/checkouts etc.
* It should contain a mandatory namespaced type key defining what particular asset is being referred to.
* For the purposes of user location tracking m.self should be used in order to avoid duplicating the mxid.
* See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md)
*/
@Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset? = null,
@Json(name = "m.asset") val locationAsset: LocationAsset? = null
) : MessageContent {
fun getBestGeoUri() = locationInfo?.geoUri ?: geoUri
fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
fun getBestTs() = ts ?: unstableTs
fun getBestText() = text ?: unstableText
fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset
fun getBestGeoUri() = getBestLocationInfo()?.geoUri ?: geoUri
/**
* @return true if the location asset is a user location, not a generic one.
*/
fun isSelfLocation(): Boolean {
// Should behave like m.self if locationAsset is null
val locationAsset = getBestLocationAsset()
return locationAsset?.type == null || locationAsset.type == LocationAssetType.SELF
}
}

View File

@@ -31,5 +31,9 @@ data class MessagePollContent(
@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,
@Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
) : MessageContent
@Json(name = "org.matrix.msc3381.poll.start") val unstablePollCreationInfo: PollCreationInfo? = null,
@Json(name = "m.poll.start") val pollCreationInfo: PollCreationInfo? = null
) : MessageContent {
fun getBestPollCreationInfo() = pollCreationInfo ?: unstablePollCreationInfo
}

View File

@@ -31,5 +31,9 @@ data class MessagePollResponseContent(
@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,
@Json(name = "org.matrix.msc3381.poll.response") val response: PollResponse? = null
) : MessageContent
@Json(name = "org.matrix.msc3381.poll.response") val unstableResponse: PollResponse? = null,
@Json(name = "m.response") val response: PollResponse? = null
) : MessageContent {
fun getBestResponse() = response ?: unstableResponse
}

View File

@@ -22,5 +22,9 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PollAnswer(
@Json(name = "id") val id: String? = null,
@Json(name = "org.matrix.msc1767.text") val answer: String? = null
)
@Json(name = "org.matrix.msc1767.text") val unstableAnswer: String? = null,
@Json(name = "m.text") val answer: String? = null
) {
fun getBestAnswer() = answer ?: unstableAnswer
}

View File

@@ -21,8 +21,8 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PollCreationInfo(
@Json(name = "question") val question: PollQuestion? = null,
@Json(name = "kind") val kind: PollType? = PollType.DISCLOSED,
@Json(name = "max_selections") val maxSelections: Int = 1,
@Json(name = "answers") val answers: List<PollAnswer>? = null
@Json(name = "question") val question: PollQuestion? = null,
@Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE,
@Json(name = "max_selections") val maxSelections: Int = 1,
@Json(name = "answers") val answers: List<PollAnswer>? = null
)

View File

@@ -21,5 +21,9 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PollQuestion(
@Json(name = "org.matrix.msc1767.text") val question: String? = null
)
@Json(name = "org.matrix.msc1767.text") val unstableQuestion: String? = null,
@Json(name = "m.text") val question: String? = null
) {
fun getBestQuestion() = question ?: unstableQuestion
}

View File

@@ -25,11 +25,17 @@ enum class PollType {
* Voters should see results as soon as they have voted.
*/
@Json(name = "org.matrix.msc3381.poll.disclosed")
DISCLOSED_UNSTABLE,
@Json(name = "m.poll.disclosed")
DISCLOSED,
/**
* Results should be only revealed when the poll is ended.
*/
@Json(name = "org.matrix.msc3381.poll.undisclosed")
UNDISCLOSED_UNSTABLE,
@Json(name = "m.poll.undisclosed")
UNDISCLOSED
}

View File

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

View File

@@ -24,4 +24,10 @@ interface RelationContent {
val eventId: String?
val inReplyTo: ReplyToContent?
val option: Int?
/**
* This flag indicates that the message should be rendered as a reply
* fallback, when isFallingBack = false
*/
val isFallingBack: Boolean?
}

View File

@@ -23,5 +23,8 @@ 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 = "option") override val option: Int? = null
@Json(name = "option") override val option: Int? = null,
@Json(name = "is_falling_back") override val isFallingBack: Boolean? = null
) : RelationContent
fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false

View File

@@ -163,13 +163,4 @@ interface RelationService {
autoMarkdown: Boolean = false,
formattedText: String? = null,
eventReplied: TimelineEvent? = null): Cancelable?
/**
* Get all the thread replies for the specified rootThreadEventId
* The return list will contain the original root thread event and all the thread replies to that event
* Note: We will use a large limit value in order to avoid using pagination until it would be 100% ready
* from the backend
* @param rootThreadEventId the root thread eventId
*/
suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean
}

View File

@@ -21,8 +21,5 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ReplyToContent(
@Json(name = "event_id") val eventId: String? = null,
@Json(name = "render_in") val renderIn: List<String>? = null
@Json(name = "event_id") val eventId: String? = null
)
fun ReplyToContent.shouldRenderInThread(): Boolean = renderIn?.contains("m.thread") == true

View File

@@ -142,8 +142,9 @@ interface SendService {
* @param latitude required latitude of the location
* @param longitude required longitude of the location
* @param uncertainty Accuracy of the location in meters
* @param isUserLocation indicates whether the location data corresponds to the user location or not
*/
fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable
fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable
/**
* Remove this failed message from the timeline

View File

@@ -32,7 +32,6 @@ object RoomSummaryConstants {
EventType.CALL_ANSWER,
EventType.ENCRYPTED,
EventType.STICKER,
EventType.REACTION,
EventType.POLL_START
)
EventType.REACTION
) + EventType.POLL_START
}

View File

@@ -17,51 +17,43 @@
package org.matrix.android.sdk.api.session.room.threads
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
/**
* This interface defines methods to interact with threads related features.
* It's implemented at the room level within the main timeline.
* This interface defines methods to interact with thread related features.
* It's the dynamic threads implementation and the homeserver must return
* a capability entry for threads. If the server do not support m.thread
* then [ThreadsLocalService] should be used instead
*/
interface ThreadsService {
/**
* Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
* Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level
*/
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>
fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>>
/**
* Returns a list of all the thread root TimelineEvents that exists at the room level
* Returns a list of all the [ThreadSummary] that exists at the room level
*/
fun getAllThreads(): List<TimelineEvent>
fun getAllThreadSummaries(): List<ThreadSummary>
/**
* Returns a [LiveData] list of all the marked unread threads that exists at the room level
*/
fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>>
/**
* Returns a list of all the marked unread threads that exists at the room level
*/
fun getMarkedThreadNotifications(): List<TimelineEvent>
/**
* Returns whether or not the current user is participating in the thread
* @param rootThreadEventId the eventId of the current thread
*/
fun isUserParticipatingInThread(rootThreadEventId: String): Boolean
/**
* Enhance the provided root thread TimelineEvent [List] by adding the latest
* Enhance the provided ThreadSummary[List] by adding the latest
* message edition for that thread
* @return the enhanced [List] with edited updates
*/
fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent>
fun enhanceThreadWithEditions(threads: List<ThreadSummary>): List<ThreadSummary>
/**
* Marks the current thread as read in local DB.
* note: read receipts within threads are not yet supported with the API
* @param rootThreadEventId the root eventId of the current thread
* Fetch all thread replies for the specified thread using the /relations api
* @param rootThreadEventId the root thread eventId
* @param from defines the token that will fetch from that position
* @param limit defines the number of max results the api will respond with
*/
suspend fun markThreadAsRead(rootThreadEventId: String)
suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int)
/**
* Fetch all thread summaries for the current room using the enhanced /messages api
*/
suspend fun fetchThreadSummaries()
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.room.threads.local
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
/**
* This interface defines methods to interact with thread related features.
* It's the local threads implementation and assumes that the homeserver
* do not support threads
*/
interface ThreadsLocalService {
/**
* Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
*/
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>
/**
* Returns a list of all the thread root TimelineEvents that exists at the room level
*/
fun getAllThreads(): List<TimelineEvent>
/**
* Returns a [LiveData] list of all the marked unread threads that exists at the room level
*/
fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>>
/**
* Returns a list of all the marked unread threads that exists at the room level
*/
fun getMarkedThreadNotifications(): List<TimelineEvent>
/**
* Returns whether or not the current user is participating in the thread
* @param rootThreadEventId the eventId of the current thread
*/
fun isUserParticipatingInThread(rootThreadEventId: String): Boolean
/**
* Enhance the provided root thread TimelineEvent [List] by adding the latest
* message edition for that thread
* @return the enhanced [List] with edited updates
*/
fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent>
/**
* Marks the current thread as read in local DB.
* note: read receipts within threads are not yet supported with the API
* @param rootThreadEventId the root eventId of the current thread
*/
suspend fun markThreadAsRead(rootThreadEventId: String)
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.room.threads.model
data class ThreadEditions(var rootThreadEdition: String? = null,
var latestThreadEdition: String? = null)

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.room.threads.model
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
/**
* The main thread Summary model, mainly used to display the thread list
*/
data class ThreadSummary(val roomId: String,
val rootEvent: Event?,
val latestEvent: Event?,
val rootEventId: String,
val rootThreadSenderInfo: SenderInfo,
val latestThreadSenderInfo: SenderInfo,
val isUserParticipating: Boolean,
val numberOfThreads: Int,
val threadEditions: ThreadEditions = ThreadEditions())

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.room.threads.model
enum class ThreadSummaryUpdateType {
REPLACE,
ADD
}

View File

@@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.room.timeline
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.RelationType
@@ -54,6 +55,7 @@ data class TimelineEvent(
* It's not unique on the timeline as it's reset on each chunk.
*/
val displayIndex: Int,
var ownedByThreadChunk: Boolean = false,
val senderInfo: SenderInfo,
val annotations: EventAnnotationsSummary? = null,
val readReceipts: List<ReadReceipt> = emptyList()
@@ -134,9 +136,9 @@ fun TimelineEvent.getEditedEventId(): String? {
*/
fun TimelineEvent.getLastMessageContent(): MessageContent? {
return when (root.getClearType()) {
EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>()
EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>()
else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>()
in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>()
else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
}
}
@@ -158,6 +160,13 @@ fun TimelineEvent.isSticker(): Boolean {
return root.isSticker()
}
/**
* Returns whether or not the event is a root thread event
*/
fun TimelineEvent.isRootThread(): Boolean {
return root.threadDetails?.isRootThread.orFalse()
}
/**
* Get the latest message body, after a possible edition, stripping the reply prefix if necessary
*/

View File

@@ -38,7 +38,7 @@ interface TimelineService {
/**
* Returns a snapshot of TimelineEvent event with eventId.
* At the opposite of getTimeLineEventLive which will be updated when local echo event is synced, it will return null in this case.
* At the opposite of getTimelineEventLive which will be updated when local echo event is synced, it will return null in this case.
* @param eventId the eventId to get the TimelineEvent
*/
fun getTimelineEvent(eventId: String): TimelineEvent?

View File

@@ -16,6 +16,7 @@
package org.matrix.android.sdk.api.session.threads
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
/**
@@ -26,7 +27,7 @@ data class ThreadDetails(
val isRootThread: Boolean = false,
val numberOfThreads: Int = 0,
val threadSummarySenderInfo: SenderInfo? = null,
val threadSummaryLatestTextMessage: String? = null,
val threadSummaryLatestEvent: Event? = null,
val lastMessageTimestamp: Long? = null,
var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE,
val isThread: Boolean = false,

View File

@@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.util
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -199,6 +200,8 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName,
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
fun SenderInfo.toMatrixItemOrNull() = tryOrNull { MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) }
fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) {
MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl)
} else {

View File

@@ -38,7 +38,7 @@ internal data class HomeServerVersion(
}
companion object {
internal val pattern = Regex("""r(\d+)\.(\d+)\.(\d+)""")
internal val pattern = Regex("""[r|v](\d+)\.(\d+)\.(\d+)""")
internal fun parse(value: String): HomeServerVersion? {
val result = pattern.matchEntire(value) ?: return null
@@ -56,5 +56,6 @@ internal data class HomeServerVersion(
val r0_4_0 = HomeServerVersion(major = 0, minor = 4, patch = 0)
val r0_5_0 = HomeServerVersion(major = 0, minor = 5, patch = 0)
val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0)
val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0)
}
}

View File

@@ -51,6 +51,8 @@ private const val FEATURE_LAZY_LOAD_MEMBERS = "m.lazy_load_members"
private const val FEATURE_REQUIRE_IDENTITY_SERVER = "m.require_identity_server"
private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token"
private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440"
private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable"
/**
* Return true if the SDK supports this homeserver version
@@ -68,6 +70,14 @@ internal fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
doesServerSeparatesAddAndBind()
}
/**
* Indicate if the homeserver support MSC3440 for threads
*/
internal fun Versions.doesServerSupportThreads(): Boolean {
return getMaxVersion() >= HomeServerVersion.v1_3_0 ||
unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false
}
/**
* Return true if the server support the lazy loading of room members
*

View File

@@ -434,6 +434,14 @@ internal class DefaultCryptoService @Inject constructor(
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
// unwedge if needed
try {
eventDecryptor.unwedgeDevicesIfNeeded()
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).w("unwedgeDevicesIfNeeded failed")
}
// There is a limit of to_device events returned per sync.
// If we are in a case of such limited to_device sync we can't try to generate/upload
// new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate
@@ -723,7 +731,7 @@ internal class DefaultCryptoService @Inject constructor(
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return internalDecryptEvent(event, timeline)
}
@@ -746,7 +754,7 @@ internal class DefaultCryptoService @Inject constructor(
* @return the MXEventDecryptionResult data, or null in case of error
*/
@Throws(MXCryptoError::class)
private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return eventDecryptor.decryptEvent(event, timeline)
}
@@ -1364,6 +1372,9 @@ internal class DefaultCryptoService @Inject constructor(
@VisibleForTesting
val cryptoStoreForTesting = cryptoStore
@VisibleForTesting
val olmDeviceForTest = olmDevice
companion object {
const val CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS = 3_600_000 // one hour
}

View File

@@ -21,14 +21,13 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@@ -40,6 +39,8 @@ import javax.inject.Inject
private const val SEND_TO_DEVICE_RETRY_COUNT = 3
private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
@SessionScope
internal class EventDecryptor @Inject constructor(
private val cryptoCoroutineScope: CoroutineScope,
@@ -47,13 +48,22 @@ internal class EventDecryptor @Inject constructor(
private val roomDecryptorProvider: RoomDecryptorProvider,
private val messageEncrypter: MessageEncrypter,
private val sendToDeviceTask: SendToDeviceTask,
private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore
) {
// The date of the last time we forced establishment
// of a new session for each user:device.
private val lastNewSessionForcedDates = MXUsersDevicesMap<Long>()
/**
* Rate limit unwedge attempt, should we persist that?
*/
private val lastNewSessionForcedDates = mutableMapOf<WedgedDeviceInfo, Long>()
data class WedgedDeviceInfo(
val userId: String,
val senderKey: String?
)
private val wedgedDevices = mutableListOf<WedgedDeviceInfo>()
/**
* Decrypt an event
@@ -63,7 +73,7 @@ internal class EventDecryptor @Inject constructor(
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@Throws(MXCryptoError::class)
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return internalDecryptEvent(event, timeline)
}
@@ -91,38 +101,32 @@ internal class EventDecryptor @Inject constructor(
* @return the MXEventDecryptionResult data, or null in case of error
*/
@Throws(MXCryptoError::class)
private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val eventContent = event.content
if (eventContent == null) {
Timber.e("## CRYPTO | decryptEvent : empty event content")
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
} else {
val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
Timber.e("## CRYPTO | decryptEvent() : $reason")
Timber.tag(loggerTag.value).e("decryptEvent() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
} else {
try {
return alg.decryptEvent(event, timeline)
} catch (mxCryptoError: MXCryptoError) {
Timber.v("## CRYPTO | internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
Timber.tag(loggerTag.value).d("internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
if (algorithm == MXCRYPTO_ALGORITHM_OLM) {
if (mxCryptoError is MXCryptoError.Base &&
mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) {
// need to find sending device
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val olmContent = event.content.toModel<OlmEventContent>()
cryptoStore.getUserDevices(event.senderId ?: "")
?.values
?.firstOrNull { it.identityKey() == olmContent?.senderKey }
?.let {
markOlmSessionForUnwedging(event.senderId ?: "", it)
}
?: run {
Timber.i("## CRYPTO | internalDecryptEvent() : Failed to find sender crypto device for unwedging")
}
val olmContent = event.content.toModel<OlmEventContent>()
if (event.senderId != null && olmContent?.senderKey != null) {
markOlmSessionForUnwedging(event.senderId, olmContent.senderKey)
} else {
Timber.tag(loggerTag.value).d("Can't mark as wedge malformed")
}
}
}
@@ -132,53 +136,91 @@ internal class EventDecryptor @Inject constructor(
}
}
// coroutineDispatchers.crypto scope
private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
val deviceKey = deviceInfo.identityKey()
private fun markOlmSessionForUnwedging(senderId: String, senderKey: String) {
val info = WedgedDeviceInfo(senderId, senderKey)
if (!wedgedDevices.contains(info)) {
Timber.tag(loggerTag.value).d("Marking device from $senderId key:$senderKey as wedged")
wedgedDevices.add(info)
}
}
val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
// coroutineDispatchers.crypto scope
suspend fun unwedgeDevicesIfNeeded() {
// handle wedged devices
// Some olm decryption have failed and some device are wedged
// we should force start a new session for those
Timber.tag(loggerTag.value).v("Unwedging: ${wedgedDevices.size} are wedged")
// get the one that should be retried according to rate limit
val now = System.currentTimeMillis()
if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
Timber.w("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
val toUnwedge = wedgedDevices.filter {
val lastForcedDate = lastNewSessionForcedDates[it] ?: 0
if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
Timber.tag(loggerTag.value).d("Unwedging, New session for $it already forced with device at $lastForcedDate")
return@filter false
}
// let's already mark that we tried now
lastNewSessionForcedDates[it] = now
true
}
if (toUnwedge.isEmpty()) {
Timber.tag(loggerTag.value).v("Nothing to unwedge")
return
}
Timber.tag(loggerTag.value).d("Unwedging, trying to create new session for ${toUnwedge.size} devices")
Timber.i("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
// offload this from crypto thread (?)
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
runCatching { ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) }.fold(
onSuccess = { sendDummyToDevice(ensured = it, deviceInfo, senderId) },
onFailure = {
Timber.e("## CRYPTO | markOlmSessionForUnwedging() : failed to ensure device info ${senderId}${deviceInfo.deviceId}")
toUnwedge
.chunked(100) // safer to chunk if we ever have lots of wedged devices
.forEach { wedgedList ->
val groupedByUserId = wedgedList.groupBy { it.userId }
// lets download keys if needed
withContext(coroutineDispatchers.io) {
deviceListManager.downloadKeys(groupedByUserId.keys.toList(), false)
}
)
}
}
private suspend fun sendDummyToDevice(ensured: MXUsersDevicesMap<MXOlmSessionResult>, deviceInfo: CryptoDeviceInfo, senderId: String) {
Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}")
// find the matching devices
groupedByUserId
.map { groupedByUser ->
val userId = groupedByUser.key
val wedgeSenderKeysForUser = groupedByUser.value.map { it.senderKey }
val knownDevices = cryptoStore.getUserDevices(userId)?.values.orEmpty()
userId to wedgeSenderKeysForUser.mapNotNull { senderKey ->
knownDevices.firstOrNull { it.identityKey() == senderKey }
}
}
.toMap()
.let { deviceList ->
try {
// force creating new outbound session and mark them as most recent to
// be used for next encryption (dummy)
val sessionToUse = ensureOlmSessionsForDevicesAction.handle(deviceList, true)
Timber.tag(loggerTag.value).d("Unwedging, found ${sessionToUse.map.size} to send dummy to")
// Now send a blank message on that session so the other side knows about it.
// (The keyshare request is sent in the clear so that won't do)
// We send this first such that, as long as the toDevice messages arrive in the
// same order we sent them, the other end will get this first, set up the new session,
// then get the keyshare request and send the key over this new session (because it
// is the session it has most recently received a message on).
val payloadJson = mapOf<String, Any>("type" to EventType.DUMMY)
// Now send a dummy message on that session so the other side knows about it.
val payloadJson = mapOf(
"type" to EventType.DUMMY
)
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sessionToUse.map.values
.flatMap { it.values }
.map { it.deviceInfo }
.forEach { deviceInfo ->
Timber.tag(loggerTag.value).v("encrypting dummy to ${deviceInfo.deviceId}")
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
sendToDeviceMap.setObject(deviceInfo.userId, deviceInfo.deviceId, encodedPayload)
}
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}")
withContext(coroutineDispatchers.io) {
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
try {
sendToDeviceTask.executeRetry(sendToDeviceParams, remainingRetry = SEND_TO_DEVICE_RETRY_COUNT)
} catch (failure: Throwable) {
Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}")
}
}
// now let's send that
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
withContext(coroutineDispatchers.io) {
sendToDeviceTask.executeRetry(sendToDeviceParams, remainingRetry = SEND_TO_DEVICE_RETRY_COUNT)
}
} catch (failure: Throwable) {
deviceList.flatMap { it.value }.joinToString { it.shortDebugString() }.let {
Timber.tag(loggerTag.value).e(failure, "## Failed to unwedge devices: $it}")
}
}
}
}
}
}

View File

@@ -19,8 +19,10 @@ package org.matrix.android.sdk.internal.crypto
import android.util.LruCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import timber.log.Timber
@@ -28,6 +30,13 @@ import java.util.Timer
import java.util.TimerTask
import javax.inject.Inject
data class InboundGroupSessionHolder(
val wrapper: OlmInboundGroupSessionWrapper2,
val mutex: Mutex = Mutex()
)
private val loggerTag = LoggerTag("InboundGroupSessionStore", LoggerTag.CRYPTO)
/**
* Allows to cache and batch store operations on inbound group session store.
* Because it is used in the decrypt flow, that can be called quite rapidly
@@ -42,12 +51,13 @@ internal class InboundGroupSessionStore @Inject constructor(
val senderKey: String
)
private val sessionCache = object : LruCache<CacheKey, OlmInboundGroupSessionWrapper2>(30) {
override fun entryRemoved(evicted: Boolean, key: CacheKey?, oldValue: OlmInboundGroupSessionWrapper2?, newValue: OlmInboundGroupSessionWrapper2?) {
if (evicted && oldValue != null) {
private val sessionCache = object : LruCache<CacheKey, InboundGroupSessionHolder>(100) {
override fun entryRemoved(evicted: Boolean, key: CacheKey?, oldValue: InboundGroupSessionHolder?, newValue: InboundGroupSessionHolder?) {
if (oldValue != null) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
Timber.v("## Inbound: entryRemoved ${oldValue.roomId}-${oldValue.senderKey}")
store.storeInboundGroupSessions(listOf(oldValue))
Timber.tag(loggerTag.value).v("## Inbound: entryRemoved ${oldValue.wrapper.roomId}-${oldValue.wrapper.senderKey}")
store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper })
oldValue.wrapper.olmInboundGroupSession?.releaseSession()
}
}
}
@@ -59,27 +69,50 @@ internal class InboundGroupSessionStore @Inject constructor(
private val dirtySession = mutableListOf<OlmInboundGroupSessionWrapper2>()
@Synchronized
fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? {
synchronized(sessionCache) {
val known = sessionCache[CacheKey(sessionId, senderKey)]
Timber.v("## Inbound: getInboundGroupSession in cache ${known != null}")
return known ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
Timber.v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
sessionCache.put(CacheKey(sessionId, senderKey), it)
}
}
fun clear() {
sessionCache.evictAll()
}
@Synchronized
fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2, sessionId: String, senderKey: String) {
Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}")
fun getInboundGroupSession(sessionId: String, senderKey: String): InboundGroupSessionHolder? {
val known = sessionCache[CacheKey(sessionId, senderKey)]
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession $sessionId in cache ${known != null}")
return known
?: store.getInboundGroupSession(sessionId, senderKey)?.also {
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it))
}?.let {
InboundGroupSessionHolder(it)
}
}
@Synchronized
fun replaceGroupSession(old: InboundGroupSessionHolder, new: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
Timber.tag(loggerTag.value).v("## Replacing outdated session ${old.wrapper.roomId}-${old.wrapper.senderKey}")
dirtySession.remove(old.wrapper)
store.removeInboundGroupSession(sessionId, senderKey)
sessionCache.remove(CacheKey(sessionId, senderKey))
// release removed session
old.wrapper.olmInboundGroupSession?.releaseSession()
internalStoreGroupSession(new, sessionId, senderKey)
}
@Synchronized
fun storeInBoundGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
internalStoreGroupSession(holder, sessionId, senderKey)
}
private fun internalStoreGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession mark as dirty ${holder.wrapper.roomId}-${holder.wrapper.senderKey}")
// We want to batch this a bit for performances
dirtySession.add(wrapper)
dirtySession.add(holder.wrapper)
if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
// first time seen, put it in memory cache while waiting for batch insert
// If it's already known, no need to update cache it's already there
sessionCache.put(CacheKey(sessionId, senderKey), wrapper)
sessionCache.put(CacheKey(sessionId, senderKey), holder)
}
timerTask?.cancel()
@@ -96,7 +129,7 @@ internal class InboundGroupSessionStore @Inject constructor(
val toSave = mutableListOf<OlmInboundGroupSessionWrapper2>().apply { addAll(dirtySession) }
dirtySession.clear()
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
Timber.v("## Inbound: getInboundGroupSession batching save of ${dirtySession.size}")
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession batching save of ${toSave.size}")
tryOrNull {
store.storeInboundGroupSessions(toSave)
}

View File

@@ -16,6 +16,11 @@
package org.matrix.android.sdk.internal.crypto
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
import org.matrix.android.sdk.api.util.JsonDict
@@ -40,6 +45,8 @@ import timber.log.Timber
import java.net.URLEncoder
import javax.inject.Inject
private val loggerTag = LoggerTag("MXOlmDevice", LoggerTag.CRYPTO)
// The libolm wrapper.
@SessionScope
internal class MXOlmDevice @Inject constructor(
@@ -47,9 +54,12 @@ internal class MXOlmDevice @Inject constructor(
* The store where crypto data is saved.
*/
private val store: IMXCryptoStore,
private val olmSessionStore: OlmSessionStore,
private val inboundGroupSessionStore: InboundGroupSessionStore
) {
val mutex = Mutex()
/**
* @return the Curve25519 key for the account.
*/
@@ -93,26 +103,26 @@ internal class MXOlmDevice @Inject constructor(
try {
store.getOrCreateOlmAccount()
} catch (e: Exception) {
Timber.e(e, "MXOlmDevice : cannot initialize olmAccount")
Timber.tag(loggerTag.value).e(e, "MXOlmDevice : cannot initialize olmAccount")
}
try {
olmUtility = OlmUtility()
} catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : OlmUtility failed with error")
Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : OlmUtility failed with error")
olmUtility = null
}
try {
deviceCurve25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY]
deviceCurve25519Key = store.doWithOlmAccount { it.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY] }
} catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error")
Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error")
}
try {
deviceEd25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
deviceEd25519Key = store.doWithOlmAccount { it.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY] }
} catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error")
Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error")
}
}
@@ -121,9 +131,9 @@ internal class MXOlmDevice @Inject constructor(
*/
fun getOneTimeKeys(): Map<String, Map<String, String>>? {
try {
return store.getOlmAccount().oneTimeKeys()
return store.doWithOlmAccount { it.oneTimeKeys() }
} catch (e: Exception) {
Timber.e(e, "## getOneTimeKeys() : failed")
Timber.tag(loggerTag.value).e(e, "## getOneTimeKeys() : failed")
}
return null
@@ -133,7 +143,7 @@ internal class MXOlmDevice @Inject constructor(
* @return The maximum number of one-time keys the olm account can store.
*/
fun getMaxNumberOfOneTimeKeys(): Long {
return store.getOlmAccount().maxOneTimeKeys()
return store.doWithOlmAccount { it.maxOneTimeKeys() }
}
/**
@@ -143,9 +153,9 @@ internal class MXOlmDevice @Inject constructor(
*/
fun getFallbackKey(): MutableMap<String, MutableMap<String, String>>? {
try {
return store.getOlmAccount().fallbackKey()
return store.doWithOlmAccount { it.fallbackKey() }
} catch (e: Exception) {
Timber.e("## getFallbackKey() : failed")
Timber.tag(loggerTag.value).e("## getFallbackKey() : failed")
}
return null
}
@@ -158,12 +168,14 @@ internal class MXOlmDevice @Inject constructor(
fun generateFallbackKeyIfNeeded(): Boolean {
try {
if (!hasUnpublishedFallbackKey()) {
store.getOlmAccount().generateFallbackKey()
store.saveOlmAccount()
store.doWithOlmAccount {
it.generateFallbackKey()
store.saveOlmAccount()
}
return true
}
} catch (e: Exception) {
Timber.e("## generateFallbackKey() : failed")
Timber.tag(loggerTag.value).e("## generateFallbackKey() : failed")
}
return false
}
@@ -174,10 +186,12 @@ internal class MXOlmDevice @Inject constructor(
fun forgetFallbackKey() {
try {
store.getOlmAccount().forgetFallbackKey()
store.saveOlmAccount()
store.doWithOlmAccount {
it.forgetFallbackKey()
store.saveOlmAccount()
}
} catch (e: Exception) {
Timber.e("## forgetFallbackKey() : failed")
Timber.tag(loggerTag.value).e("## forgetFallbackKey() : failed")
}
}
@@ -190,6 +204,8 @@ internal class MXOlmDevice @Inject constructor(
it.groupSession.releaseSession()
}
outboundGroupSessionCache.clear()
inboundGroupSessionStore.clear()
olmSessionStore.clear()
}
/**
@@ -200,9 +216,9 @@ internal class MXOlmDevice @Inject constructor(
*/
fun signMessage(message: String): String? {
try {
return store.getOlmAccount().signMessage(message)
return store.doWithOlmAccount { it.signMessage(message) }
} catch (e: Exception) {
Timber.e(e, "## signMessage() : failed")
Timber.tag(loggerTag.value).e(e, "## signMessage() : failed")
}
return null
@@ -213,10 +229,12 @@ internal class MXOlmDevice @Inject constructor(
*/
fun markKeysAsPublished() {
try {
store.getOlmAccount().markOneTimeKeysAsPublished()
store.saveOlmAccount()
store.doWithOlmAccount {
it.markOneTimeKeysAsPublished()
store.saveOlmAccount()
}
} catch (e: Exception) {
Timber.e(e, "## markKeysAsPublished() : failed")
Timber.tag(loggerTag.value).e(e, "## markKeysAsPublished() : failed")
}
}
@@ -227,10 +245,12 @@ internal class MXOlmDevice @Inject constructor(
*/
fun generateOneTimeKeys(numKeys: Int) {
try {
store.getOlmAccount().generateOneTimeKeys(numKeys)
store.saveOlmAccount()
store.doWithOlmAccount {
it.generateOneTimeKeys(numKeys)
store.saveOlmAccount()
}
} catch (e: Exception) {
Timber.e(e, "## generateOneTimeKeys() : failed")
Timber.tag(loggerTag.value).e(e, "## generateOneTimeKeys() : failed")
}
}
@@ -243,12 +263,14 @@ internal class MXOlmDevice @Inject constructor(
* @return the session id for the outbound session.
*/
fun createOutboundSession(theirIdentityKey: String, theirOneTimeKey: String): String? {
Timber.v("## createOutboundSession() ; theirIdentityKey $theirIdentityKey theirOneTimeKey $theirOneTimeKey")
Timber.tag(loggerTag.value).d("## createOutboundSession() ; theirIdentityKey $theirIdentityKey theirOneTimeKey $theirOneTimeKey")
var olmSession: OlmSession? = null
try {
olmSession = OlmSession()
olmSession.initOutboundSession(store.getOlmAccount(), theirIdentityKey, theirOneTimeKey)
store.doWithOlmAccount { olmAccount ->
olmSession.initOutboundSession(olmAccount, theirIdentityKey, theirOneTimeKey)
}
val olmSessionWrapper = OlmSessionWrapper(olmSession, 0)
@@ -257,14 +279,14 @@ internal class MXOlmDevice @Inject constructor(
// this session
olmSessionWrapper.onMessageReceived()
store.storeSession(olmSessionWrapper, theirIdentityKey)
olmSessionStore.storeSession(olmSessionWrapper, theirIdentityKey)
val sessionIdentifier = olmSession.sessionIdentifier()
Timber.v("## createOutboundSession() ; olmSession.sessionIdentifier: $sessionIdentifier")
Timber.tag(loggerTag.value).v("## createOutboundSession() ; olmSession.sessionIdentifier: $sessionIdentifier")
return sessionIdentifier
} catch (e: Exception) {
Timber.e(e, "## createOutboundSession() failed")
Timber.tag(loggerTag.value).e(e, "## createOutboundSession() failed")
olmSession?.releaseSession()
}
@@ -281,34 +303,38 @@ internal class MXOlmDevice @Inject constructor(
* @return {{payload: string, session_id: string}} decrypted payload, and session id of new session.
*/
fun createInboundSession(theirDeviceIdentityKey: String, messageType: Int, ciphertext: String): Map<String, String>? {
Timber.v("## createInboundSession() : theirIdentityKey: $theirDeviceIdentityKey")
Timber.tag(loggerTag.value).d("## createInboundSession() : theirIdentityKey: $theirDeviceIdentityKey")
var olmSession: OlmSession? = null
try {
try {
olmSession = OlmSession()
olmSession.initInboundSessionFrom(store.getOlmAccount(), theirDeviceIdentityKey, ciphertext)
store.doWithOlmAccount { olmAccount ->
olmSession.initInboundSessionFrom(olmAccount, theirDeviceIdentityKey, ciphertext)
}
} catch (e: Exception) {
Timber.e(e, "## createInboundSession() : the session creation failed")
Timber.tag(loggerTag.value).e(e, "## createInboundSession() : the session creation failed")
return null
}
Timber.v("## createInboundSession() : sessionId: ${olmSession.sessionIdentifier()}")
Timber.tag(loggerTag.value).v("## createInboundSession() : sessionId: ${olmSession.sessionIdentifier()}")
try {
store.getOlmAccount().removeOneTimeKeys(olmSession)
store.saveOlmAccount()
store.doWithOlmAccount { olmAccount ->
olmAccount.removeOneTimeKeys(olmSession)
store.saveOlmAccount()
}
} catch (e: Exception) {
Timber.e(e, "## createInboundSession() : removeOneTimeKeys failed")
Timber.tag(loggerTag.value).e(e, "## createInboundSession() : removeOneTimeKeys failed")
}
Timber.v("## createInboundSession() : ciphertext: $ciphertext")
Timber.tag(loggerTag.value).v("## createInboundSession() : ciphertext: $ciphertext")
try {
val sha256 = olmUtility!!.sha256(URLEncoder.encode(ciphertext, "utf-8"))
Timber.v("## createInboundSession() :ciphertext: SHA256: $sha256")
Timber.tag(loggerTag.value).v("## createInboundSession() :ciphertext: SHA256: $sha256")
} catch (e: Exception) {
Timber.e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
Timber.tag(loggerTag.value).e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
}
val olmMessage = OlmMessage()
@@ -324,9 +350,9 @@ internal class MXOlmDevice @Inject constructor(
// This counts as a received message: set last received message time to now
olmSessionWrapper.onMessageReceived()
store.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
} catch (e: Exception) {
Timber.e(e, "## createInboundSession() : decryptMessage failed")
Timber.tag(loggerTag.value).e(e, "## createInboundSession() : decryptMessage failed")
}
val res = HashMap<String, String>()
@@ -343,7 +369,7 @@ internal class MXOlmDevice @Inject constructor(
return res
} catch (e: Exception) {
Timber.e(e, "## createInboundSession() : OlmSession creation failed")
Timber.tag(loggerTag.value).e(e, "## createInboundSession() : OlmSession creation failed")
olmSession?.releaseSession()
}
@@ -357,8 +383,8 @@ internal class MXOlmDevice @Inject constructor(
* @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
* @return a list of known session ids for the device.
*/
fun getSessionIds(theirDeviceIdentityKey: String): List<String>? {
return store.getDeviceSessionIds(theirDeviceIdentityKey)
fun getSessionIds(theirDeviceIdentityKey: String): List<String> {
return olmSessionStore.getDeviceSessionIds(theirDeviceIdentityKey)
}
/**
@@ -368,7 +394,7 @@ internal class MXOlmDevice @Inject constructor(
* @return the session id, or null if no established session.
*/
fun getSessionId(theirDeviceIdentityKey: String): String? {
return store.getLastUsedSessionId(theirDeviceIdentityKey)
return olmSessionStore.getLastUsedSessionId(theirDeviceIdentityKey)
}
/**
@@ -379,30 +405,30 @@ internal class MXOlmDevice @Inject constructor(
* @param payloadString the payload to be encrypted and sent
* @return the cipher text
*/
fun encryptMessage(theirDeviceIdentityKey: String, sessionId: String, payloadString: String): Map<String, Any>? {
var res: MutableMap<String, Any>? = null
val olmMessage: OlmMessage
suspend fun encryptMessage(theirDeviceIdentityKey: String, sessionId: String, payloadString: String): Map<String, Any>? {
val olmSessionWrapper = getSessionForDevice(theirDeviceIdentityKey, sessionId)
if (olmSessionWrapper != null) {
try {
Timber.v("## encryptMessage() : olmSession.sessionIdentifier: $sessionId")
// Timber.v("## encryptMessage() : payloadString: " + payloadString);
Timber.tag(loggerTag.value).v("## encryptMessage() : olmSession.sessionIdentifier: $sessionId")
olmMessage = olmSessionWrapper.olmSession.encryptMessage(payloadString)
store.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
res = HashMap()
res["body"] = olmMessage.mCipherText
res["type"] = olmMessage.mType
} catch (e: Exception) {
Timber.e(e, "## encryptMessage() : failed")
val olmMessage = olmSessionWrapper.mutex.withLock {
olmSessionWrapper.olmSession.encryptMessage(payloadString)
}
return mapOf(
"body" to olmMessage.mCipherText,
"type" to olmMessage.mType,
).also {
olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
}
} catch (e: Throwable) {
Timber.tag(loggerTag.value).e(e, "## encryptMessage() : failed to encrypt olm with device|session:$theirDeviceIdentityKey|$sessionId")
return null
}
} else {
Timber.e("## encryptMessage() : Failed to encrypt unknown session $sessionId")
Timber.tag(loggerTag.value).e("## encryptMessage() : Failed to encrypt unknown session $sessionId")
return null
}
return res
}
/**
@@ -414,7 +440,8 @@ internal class MXOlmDevice @Inject constructor(
* @param sessionId the id of the active session.
* @return the decrypted payload.
*/
fun decryptMessage(ciphertext: String, messageType: Int, sessionId: String, theirDeviceIdentityKey: String): String? {
@kotlin.jvm.Throws
suspend fun decryptMessage(ciphertext: String, messageType: Int, sessionId: String, theirDeviceIdentityKey: String): String? {
var payloadString: String? = null
val olmSessionWrapper = getSessionForDevice(theirDeviceIdentityKey, sessionId)
@@ -424,13 +451,13 @@ internal class MXOlmDevice @Inject constructor(
olmMessage.mCipherText = ciphertext
olmMessage.mType = messageType.toLong()
try {
payloadString = olmSessionWrapper.olmSession.decryptMessage(olmMessage)
olmSessionWrapper.onMessageReceived()
store.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
} catch (e: Exception) {
Timber.e(e, "## decryptMessage() : decryptMessage failed")
}
payloadString =
olmSessionWrapper.mutex.withLock {
olmSessionWrapper.olmSession.decryptMessage(olmMessage).also {
olmSessionWrapper.onMessageReceived()
}
}
olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
}
return payloadString
@@ -469,7 +496,7 @@ internal class MXOlmDevice @Inject constructor(
store.storeCurrentOutboundGroupSessionForRoom(roomId, session)
return session.sessionIdentifier()
} catch (e: Exception) {
Timber.e(e, "createOutboundGroupSession")
Timber.tag(loggerTag.value).e(e, "createOutboundGroupSession")
session?.releaseSession()
}
@@ -521,7 +548,7 @@ internal class MXOlmDevice @Inject constructor(
try {
return outboundGroupSessionCache[sessionId]!!.groupSession.sessionKey()
} catch (e: Exception) {
Timber.e(e, "## getSessionKey() : failed")
Timber.tag(loggerTag.value).e(e, "## getSessionKey() : failed")
}
}
return null
@@ -550,8 +577,8 @@ internal class MXOlmDevice @Inject constructor(
if (sessionId.isNotEmpty() && payloadString.isNotEmpty()) {
try {
return outboundGroupSessionCache[sessionId]!!.groupSession.encryptMessage(payloadString)
} catch (e: Exception) {
Timber.e(e, "## encryptGroupMessage() : failed")
} catch (e: Throwable) {
Timber.tag(loggerTag.value).e(e, "## encryptGroupMessage() : failed")
}
}
return null
@@ -578,52 +605,64 @@ internal class MXOlmDevice @Inject constructor(
forwardingCurve25519KeyChain: List<String>,
keysClaimed: Map<String, String>,
exportFormat: Boolean): Boolean {
val session = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
.fold(
{
// If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
val existingSession = existingSessionHolder?.wrapper
// If we have an existing one we should check if the new one is not better
if (existingSession != null) {
Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session")
try {
val existingFirstKnown = existingSession.firstKnownIndex ?: return false.also {
// This is quite unexpected, could throw if native was released?
Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session")
candidateSession.olmInboundGroupSession?.releaseSession()
// Probably should discard it?
}
val newKnownFirstIndex = candidateSession.firstKnownIndex
// If our existing session is better we keep it
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId")
candidateSession.olmInboundGroupSession?.releaseSession()
return false
}
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}")
candidateSession.olmInboundGroupSession?.releaseSession()
return false
}
}
val existingFirstKnown = it.firstKnownIndex!!
val newKnownFirstIndex = session.firstKnownIndex
Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Candidate session should be added $senderKey/$sessionId")
// If our existing session is better we keep it
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
session.olmInboundGroupSession?.releaseSession()
return false
}
},
{
// Nothing to do in case of error
}
)
// sanity check
if (null == session.olmInboundGroupSession) {
Timber.e("## addInboundGroupSession : invalid session")
// sanity check on the new session
val candidateOlmInboundSession = candidateSession.olmInboundGroupSession
if (null == candidateOlmInboundSession) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session <null>")
return false
}
try {
if (session.olmInboundGroupSession!!.sessionIdentifier() != sessionId) {
Timber.e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
session.olmInboundGroupSession!!.releaseSession()
if (candidateOlmInboundSession.sessionIdentifier() != sessionId) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
candidateOlmInboundSession.releaseSession()
return false
}
} catch (e: Exception) {
session.olmInboundGroupSession?.releaseSession()
Timber.e(e, "## addInboundGroupSession : sessionIdentifier() failed")
} catch (e: Throwable) {
candidateOlmInboundSession.releaseSession()
Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed")
return false
}
session.senderKey = senderKey
session.roomId = roomId
session.keysClaimed = keysClaimed
session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
candidateSession.senderKey = senderKey
candidateSession.roomId = roomId
candidateSession.keysClaimed = keysClaimed
candidateSession.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
// store.storeInboundGroupSessions(listOf(session))
if (existingSession != null) {
inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
} else {
inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
}
return true
}
@@ -638,57 +677,70 @@ internal class MXOlmDevice @Inject constructor(
val sessions = ArrayList<OlmInboundGroupSessionWrapper2>(megolmSessionsData.size)
for (megolmSessionData in megolmSessionsData) {
val sessionId = megolmSessionData.sessionId
val senderKey = megolmSessionData.senderKey
val sessionId = megolmSessionData.sessionId ?: continue
val senderKey = megolmSessionData.senderKey ?: continue
val roomId = megolmSessionData.roomId
var session: OlmInboundGroupSessionWrapper2? = null
var candidateSessionToImport: OlmInboundGroupSessionWrapper2? = null
try {
session = OlmInboundGroupSessionWrapper2(megolmSessionData)
candidateSessionToImport = OlmInboundGroupSessionWrapper2(megolmSessionData)
} catch (e: Exception) {
Timber.e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
}
// sanity check
if (session?.olmInboundGroupSession == null) {
Timber.e("## importInboundGroupSession : invalid session")
if (candidateSessionToImport?.olmInboundGroupSession == null) {
Timber.tag(loggerTag.value).e("## importInboundGroupSession : invalid session")
continue
}
val candidateOlmInboundGroupSession = candidateSessionToImport.olmInboundGroupSession
try {
if (session.olmInboundGroupSession?.sessionIdentifier() != sessionId) {
Timber.e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
if (session.olmInboundGroupSession != null) session.olmInboundGroupSession!!.releaseSession()
if (candidateOlmInboundGroupSession?.sessionIdentifier() != sessionId) {
Timber.tag(loggerTag.value).e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
candidateOlmInboundGroupSession?.releaseSession()
continue
}
} catch (e: Exception) {
Timber.e(e, "## importInboundGroupSession : sessionIdentifier() failed")
session.olmInboundGroupSession!!.releaseSession()
Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession : sessionIdentifier() failed")
candidateOlmInboundGroupSession?.releaseSession()
continue
}
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
.fold(
{
// If we already have this session, consider updating it
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
val existingSession = existingSessionHolder?.wrapper
// For now we just ignore updates. TODO: implement something here
if (it.firstKnownIndex!! <= session.firstKnownIndex!!) {
// Ignore this, keep existing
session.olmInboundGroupSession!!.releaseSession()
} else {
sessions.add(session)
}
Unit
},
{
// Session does not already exist, add it
sessions.add(session)
}
if (existingSession == null) {
// Session does not already exist, add it
Timber.tag(loggerTag.value).d("## importInboundGroupSession() : importing new megolm session $senderKey/$sessionId")
sessions.add(candidateSessionToImport)
} else {
Timber.tag(loggerTag.value).e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
val existingFirstKnown = tryOrNull { existingSession.firstKnownIndex }
val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.firstKnownIndex }
)
if (existingFirstKnown == null || candidateFirstKnownIndex == null) {
// should not happen?
candidateSessionToImport.olmInboundGroupSession?.releaseSession()
Timber.tag(loggerTag.value)
.w("## importInboundGroupSession() : Can't check session null index $existingFirstKnown/$candidateFirstKnownIndex")
} else {
if (existingFirstKnown <= candidateSessionToImport.firstKnownIndex!!) {
// Ignore this, keep existing
candidateOlmInboundGroupSession.releaseSession()
} else {
// update cache with better session
inboundGroupSessionStore.replaceGroupSession(
existingSessionHolder,
InboundGroupSessionHolder(candidateSessionToImport),
sessionId,
senderKey
)
sessions.add(candidateSessionToImport)
}
}
}
}
store.storeInboundGroupSessions(sessions)
@@ -696,18 +748,6 @@ internal class MXOlmDevice @Inject constructor(
return sessions
}
/**
* Remove an inbound group session
*
* @param sessionId the session identifier.
* @param sessionKey base64-encoded secret key.
*/
fun removeInboundGroupSession(sessionId: String?, sessionKey: String?) {
if (null != sessionId && null != sessionKey) {
store.removeInboundGroupSession(sessionId, sessionKey)
}
}
/**
* Decrypt a received message with an inbound group session.
*
@@ -719,19 +759,24 @@ internal class MXOlmDevice @Inject constructor(
* @return the decrypting result. Nil if the sessionId is unknown.
*/
@Throws(MXCryptoError::class)
fun decryptGroupMessage(body: String,
roomId: String,
timeline: String?,
sessionId: String,
senderKey: String): OlmDecryptionResult {
val session = getInboundGroupSession(sessionId, senderKey, roomId)
suspend fun decryptGroupMessage(body: String,
roomId: String,
timeline: String?,
sessionId: String,
senderKey: String): OlmDecryptionResult {
val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
val wrapper = sessionHolder.wrapper
val inboundGroupSession = wrapper.olmInboundGroupSession
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null")
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
if (roomId == session.roomId) {
if (roomId == wrapper.roomId) {
val decryptResult = try {
session.olmInboundGroupSession!!.decryptMessage(body)
sessionHolder.mutex.withLock {
inboundGroupSession.decryptMessage(body)
}
} catch (e: OlmException) {
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed")
throw MXCryptoError.OlmError(e)
}
@@ -742,32 +787,32 @@ internal class MXOlmDevice @Inject constructor(
if (timelineSet.contains(messageIndexKey)) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason")
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
}
timelineSet.add(messageIndexKey)
}
inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
}
return OlmDecryptionResult(
payload,
session.keysClaimed,
wrapper.keysClaimed,
senderKey,
session.forwardingCurve25519KeyChain
wrapper.forwardingCurve25519KeyChain
)
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason")
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
}
}
@@ -819,7 +864,7 @@ internal class MXOlmDevice @Inject constructor(
private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): OlmSessionWrapper? {
// sanity check
return if (theirDeviceIdentityKey.isEmpty() || sessionId.isEmpty()) null else {
store.getDeviceSession(sessionId, theirDeviceIdentityKey)
olmSessionStore.getDeviceSession(sessionId, theirDeviceIdentityKey)
}
}
@@ -832,25 +877,26 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return the inbound group session.
*/
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper2 {
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): InboundGroupSessionHolder {
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
}
val session = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey)
val holder = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey)
val session = holder?.wrapper
if (session != null) {
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
if (roomId != session.roomId) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## getInboundGroupSession() : $errorDescription")
Timber.tag(loggerTag.value).e("## getInboundGroupSession() : $errorDescription")
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)
} else {
return session
return holder
}
} else {
Timber.w("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
Timber.tag(loggerTag.value).w("## getInboundGroupSession() : UISI $sessionId")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
}
}
@@ -866,4 +912,9 @@ internal class MXOlmDevice @Inject constructor(
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
return runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }.isSuccess
}
@VisibleForTesting
fun clearOlmSessionCache() {
olmSessionStore.clear()
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.crypto
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.olm.OlmSession
import timber.log.Timber
import javax.inject.Inject
private val loggerTag = LoggerTag("OlmSessionStore", LoggerTag.CRYPTO)
/**
* Keep the used olm session in memory and load them from the data layer when needed
* Access is synchronized for thread safety
*/
internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoStore) {
/**
* map of device key to list of olm sessions (it is possible to have several active sessions with a device)
*/
private val olmSessions = HashMap<String, MutableList<OlmSessionWrapper>>()
/**
* Store a session between our own device and another device.
* This will be called after the session has been created but also every time it has been used
* in order to persist the correct state for next run
* @param olmSessionWrapper the end-to-end session.
* @param deviceKey the public key of the other device.
*/
@Synchronized
fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) {
// This could be a newly created session or one that was just created
// Anyhow we should persist ratchet state for future app lifecycle
addNewSessionInCache(olmSessionWrapper, deviceKey)
store.storeSession(olmSessionWrapper, deviceKey)
}
/**
* Get all the Olm Sessions we are sharing with the given device.
*
* @param deviceKey the public key of the other device.
* @return A set of sessionId, or empty if device is not known
*/
@Synchronized
fun getDeviceSessionIds(deviceKey: String): List<String> {
// we need to get the persisted ids first
val persistedKnownSessions = store.getDeviceSessionIds(deviceKey)
.orEmpty()
.toMutableList()
// Do we have some in cache not yet persisted?
olmSessions.getOrPut(deviceKey) { mutableListOf() }.forEach { cached ->
getSafeSessionIdentifier(cached.olmSession)?.let { cachedSessionId ->
if (!persistedKnownSessions.contains(cachedSessionId)) {
persistedKnownSessions.add(cachedSessionId)
}
}
}
return persistedKnownSessions
}
/**
* Retrieve an end-to-end session between our own device and another
* device.
*
* @param sessionId the session Id.
* @param deviceKey the public key of the other device.
* @return the session wrapper if found
*/
@Synchronized
fun getDeviceSession(sessionId: String, deviceKey: String): OlmSessionWrapper? {
// get from cache or load and add to cache
return internalGetSession(sessionId, deviceKey)
}
/**
* Retrieve the last used sessionId, regarding `lastReceivedMessageTs`, or null if no session exist
*
* @param deviceKey the public key of the other device.
* @return last used sessionId, or null if not found
*/
@Synchronized
fun getLastUsedSessionId(deviceKey: String): String? {
// We want to avoid to load in memory old session if possible
val lastPersistedUsedSession = store.getLastUsedSessionId(deviceKey)
var candidate = lastPersistedUsedSession?.let { internalGetSession(it, deviceKey) }
// we should check if we have one in cache with a higher last message received?
olmSessions[deviceKey].orEmpty().forEach { inCache ->
if (inCache.lastReceivedMessageTs > (candidate?.lastReceivedMessageTs ?: 0L)) {
candidate = inCache
}
}
return candidate?.olmSession?.sessionIdentifier()
}
/**
* Release all sessions and clear cache
*/
@Synchronized
fun clear() {
olmSessions.entries.onEach { entry ->
entry.value.onEach { it.olmSession.releaseSession() }
}
olmSessions.clear()
}
private fun internalGetSession(sessionId: String, deviceKey: String): OlmSessionWrapper? {
return getSessionInCache(sessionId, deviceKey)
?: // deserialize from store
return store.getDeviceSession(sessionId, deviceKey)?.also {
addNewSessionInCache(it, deviceKey)
}
}
private fun getSessionInCache(sessionId: String, deviceKey: String): OlmSessionWrapper? {
return olmSessions[deviceKey]?.firstOrNull {
getSafeSessionIdentifier(it.olmSession) == sessionId
}
}
private fun getSafeSessionIdentifier(session: OlmSession): String? {
return try {
session.sessionIdentifier()
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).w("Failed to load sessionId from loaded olm session")
null
}
}
private fun addNewSessionInCache(session: OlmSessionWrapper, deviceKey: String) {
val sessionId = getSafeSessionIdentifier(session.olmSession) ?: return
olmSessions.getOrPut(deviceKey) { mutableListOf() }.let {
val existing = it.firstOrNull { getSafeSessionIdentifier(it.olmSession) == sessionId }
it.add(session)
// remove and release if was there but with different instance
if (existing != null && existing.olmSession != session.olmSession) {
// mm not sure when this could happen
// anyhow we should remove and release the one known
it.remove(existing)
existing.olmSession.releaseSession()
}
}
}
}

View File

@@ -16,14 +16,18 @@
package org.matrix.android.sdk.internal.crypto.actions
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXKey
import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.toDebugString
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber
import javax.inject.Inject
@@ -31,90 +35,90 @@ private const val ONE_TIME_KEYS_RETRY_COUNT = 3
private val loggerTag = LoggerTag("EnsureOlmSessionsForDevicesAction", LoggerTag.CRYPTO)
@SessionScope
internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
private val olmDevice: MXOlmDevice,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
private val ensureMutex = Mutex()
/**
* We want to synchronize a bit here, because we are iterating to check existing olm session and
* also adding some
*/
suspend fun handle(devicesByUser: Map<String, List<CryptoDeviceInfo>>, force: Boolean = false): MXUsersDevicesMap<MXOlmSessionResult> {
val devicesWithoutSession = ArrayList<CryptoDeviceInfo>()
ensureMutex.withLock {
val results = MXUsersDevicesMap<MXOlmSessionResult>()
val deviceList = devicesByUser.flatMap { it.value }
Timber.tag(loggerTag.value)
.d("ensure olm forced:$force for ${deviceList.joinToString { it.shortDebugString() }}")
val devicesToCreateSessionWith = mutableListOf<CryptoDeviceInfo>()
if (force) {
// we take all devices and will query otk for them
devicesToCreateSessionWith.addAll(deviceList)
} else {
// only peek devices without active session
deviceList.forEach { deviceInfo ->
val deviceId = deviceInfo.deviceId
val userId = deviceInfo.userId
val key = deviceInfo.identityKey() ?: return@forEach Unit.also {
Timber.tag(loggerTag.value).w("Ignoring device ${deviceInfo.shortDebugString()} without identity key")
}
val results = MXUsersDevicesMap<MXOlmSessionResult>()
for ((userId, deviceList) in devicesByUser) {
for (deviceInfo in deviceList) {
val deviceId = deviceInfo.deviceId
val key = deviceInfo.identityKey()
if (key == null) {
Timber.w("## CRYPTO | Ignoring device (${deviceInfo.userId}|$deviceId) without identity key")
continue
}
val sessionId = olmDevice.getSessionId(key)
if (sessionId.isNullOrEmpty() || force) {
Timber.tag(loggerTag.value).d("Found no existing olm session (${deviceInfo.userId}|$deviceId) (force=$force)")
devicesWithoutSession.add(deviceInfo)
} else {
Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
}
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
results.setObject(userId, deviceId, olmSessionResult)
}
}
Timber.tag(loggerTag.value).d("Devices without olm session (count:${devicesWithoutSession.size}) :" +
" ${devicesWithoutSession.joinToString { "${it.userId}|${it.deviceId}" }}")
if (devicesWithoutSession.size == 0) {
return results
}
// Prepare the request for claiming one-time keys
val usersDevicesToClaim = MXUsersDevicesMap<String>()
val oneTimeKeyAlgorithm = MXKey.KEY_SIGNED_CURVE_25519_TYPE
for (device in devicesWithoutSession) {
usersDevicesToClaim.setObject(device.userId, device.deviceId, oneTimeKeyAlgorithm)
}
// TODO: this has a race condition - if we try to send another message
// while we are claiming a key, we will end up claiming two and setting up
// two sessions.
//
// That should eventually resolve itself, but it's poor form.
Timber.tag(loggerTag.value).i("claimOneTimeKeysForUsersDevices() : ${usersDevicesToClaim.toDebugString()}")
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, remainingRetry = ONE_TIME_KEYS_RETRY_COUNT)
Timber.tag(loggerTag.value).v("claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
for ((userId, deviceInfos) in devicesByUser) {
for (deviceInfo in deviceInfos) {
var oneTimeKey: MXKey? = null
val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
if (null != deviceIds) {
for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(userId, deviceId)
if (olmSessionResult?.sessionId != null && !force) {
// We already have a result for this device
continue
}
val key = oneTimeKeys.getObject(userId, deviceId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
if (oneTimeKey == null) {
Timber.tag(loggerTag.value).d("No one time key for $userId|$deviceId")
continue
}
// Update the result for this device in results
olmSessionResult?.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
// is there a session that as been already used?
val sessionId = olmDevice.getSessionId(key)
if (sessionId.isNullOrEmpty()) {
Timber.tag(loggerTag.value).d("Found no existing olm session ${deviceInfo.shortDebugString()} add to claim list")
devicesToCreateSessionWith.add(deviceInfo)
} else {
Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
results.setObject(userId, deviceId, olmSessionResult)
}
}
}
if (devicesToCreateSessionWith.isEmpty()) {
// no session to create
return results
}
val usersDevicesToClaim = MXUsersDevicesMap<String>().apply {
devicesToCreateSessionWith.forEach {
setObject(it.userId, it.deviceId, MXKey.KEY_SIGNED_CURVE_25519_TYPE)
}
}
// Let's now claim one time keys
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
val oneTimeKeys = withContext(coroutineDispatchers.io) {
oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT)
}
// let now start olm session using the new otks
devicesToCreateSessionWith.forEach { deviceInfo ->
val userId = deviceInfo.userId
val deviceId = deviceInfo.deviceId
// Did we get an OTK
val oneTimeKey = oneTimeKeys.getObject(userId, deviceId)
if (oneTimeKey == null) {
Timber.tag(loggerTag.value).d("No otk for ${deviceInfo.shortDebugString()}")
} else if (oneTimeKey.type != MXKey.KEY_SIGNED_CURVE_25519_TYPE) {
Timber.tag(loggerTag.value).d("Bad otk type (${oneTimeKey.type}) for ${deviceInfo.shortDebugString()}")
} else {
val olmSessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
if (olmSessionId != null) {
val olmSessionResult = MXOlmSessionResult(deviceInfo, olmSessionId)
results.setObject(userId, deviceId, olmSessionResult)
} else {
Timber
.tag(loggerTag.value)
.d("## CRYPTO | cant unwedge failed to create outbound ${deviceInfo.shortDebugString()}")
}
}
}
return results
}
return results
}
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: CryptoDeviceInfo): String? {

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