mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
1159 Commits
feature/sq
...
new_notifi
Author | SHA1 | Date | |
---|---|---|---|
|
92ecfafa0d | ||
|
f74ab2dfd4 | ||
|
a1dc383148 | ||
|
51e63c5d1d | ||
|
e9ba7342d5 | ||
|
c4ad90696f | ||
|
85ee183e4f | ||
|
586b0fe6e2 | ||
|
bdfce35d9a | ||
|
b8f88d323d | ||
|
b8f66a36d5 | ||
|
d4050a7b9d | ||
|
bfb8b6203c | ||
|
15223ecfe4 | ||
|
e098b87d0a | ||
|
e878821df2 | ||
|
596fcf94ba | ||
|
5008bfd6a9 | ||
|
2ef82f1b82 | ||
|
69eaf2695e | ||
|
542d11d7f1 | ||
|
1de819b0a3 | ||
|
5713fa4f59 | ||
|
93fb40f323 | ||
|
f4314ebdae | ||
|
c62a7c4051 | ||
|
3e97e03ccc | ||
|
01e42838ef | ||
|
192c6db03e | ||
|
a98bd0940f | ||
|
c0623726ae | ||
|
ba8ec97f6a | ||
|
fb1c01c37c | ||
|
f8e35da533 | ||
|
53053d8f4a | ||
|
9eab1acf1e | ||
|
310517ece4 | ||
|
8adb36c7c4 | ||
|
057f6fdf26 | ||
|
b8b79de91c | ||
|
f86fa6cb5d | ||
|
693c980414 | ||
|
faeeec0e37 | ||
|
73ce38c6a9 | ||
|
901cf15a79 | ||
|
4e3a948513 | ||
|
1ed8ff8711 | ||
|
4b74c7320b | ||
|
7ed9f535e6 | ||
|
f5c1ad8f2a | ||
|
174084a256 | ||
|
1e718bb44f | ||
|
f0fe10a11d | ||
|
ae1da6b9f5 | ||
|
04b6b3674d | ||
|
4254db5cc0 | ||
|
86e5a45621 | ||
|
397d4f0be5 | ||
|
01fdc6d1bb | ||
|
33698abfb2 | ||
|
80e8cd4191 | ||
|
2932e05851 | ||
|
19f16c9e56 | ||
|
cec5cd864c | ||
|
ba26aee54c | ||
|
6721e33c7e | ||
|
c14b226c92 | ||
|
4bb804fbf7 | ||
|
6cb82421e4 | ||
|
6e66c31911 | ||
|
a1ce245e3a | ||
|
668967546c | ||
|
5bd448405b | ||
|
fe235e0791 | ||
|
a9191b8fad | ||
|
63499c2f48 | ||
|
3fa2647e92 | ||
|
cdb1b8d8f8 | ||
|
c7c35399e5 | ||
|
102b8f88d0 | ||
|
4ca0c23e2a | ||
|
36de891451 | ||
|
f9d931960b | ||
|
dbe78f160b | ||
|
a6f4cd74d5 | ||
|
87a087c0b5 | ||
|
962e11a740 | ||
|
e658ef184d | ||
|
da472ea858 | ||
|
ff0b92272a | ||
|
cca6d0e967 | ||
|
e0ea0c195b | ||
|
b3d4d20195 | ||
|
5f788d962e | ||
|
2650453d4b | ||
|
4d6ba5a491 | ||
|
32721caf5a | ||
|
ad084e1fec | ||
|
512e4f0ce3 | ||
|
56f8e52352 | ||
|
a93cbf3548 | ||
|
e0e4cf3df1 | ||
|
16bd642ae8 | ||
|
a03f69fb98 | ||
|
8787f5d920 | ||
|
ef1ae4105b | ||
|
7f2ce91c82 | ||
|
1f30cf468a | ||
|
762dd1d0a5 | ||
|
f5790e5dc2 | ||
|
e1a12f4c77 | ||
|
52eec06110 | ||
|
a6e4a328b3 | ||
|
bfebaa5c6c | ||
|
05e848244e | ||
|
c23819bfcf | ||
|
b0034f91b0 | ||
|
caa7709090 | ||
|
51b7a0aeae | ||
|
51228a3a5c | ||
|
5c091339f0 | ||
|
6643923981 | ||
|
4ce2478e44 | ||
|
6131c10d31 | ||
|
8c5ec2c57f | ||
|
e046d17bf2 | ||
|
8e7166662b | ||
|
3aaa425714 | ||
|
a66010a1d8 | ||
|
e9706a3b64 | ||
|
a6e61f4de8 | ||
|
e758ede706 | ||
|
957fe189dc | ||
|
12a4f6f05b | ||
|
bcd78a96bf | ||
|
f3b464b88a | ||
|
369f40c804 | ||
|
bddd7f4005 | ||
|
5434c39453 | ||
|
e4ac28877c | ||
|
8df7797f6d | ||
|
aa2b62976e | ||
|
e3dc6e307f | ||
|
48a30a7b82 | ||
|
1365240f69 | ||
|
1cd27d7f67 | ||
|
84c8f9d351 | ||
|
7e703e4788 | ||
|
fb13b402af | ||
|
e599abc6ba | ||
|
5784c4c8b3 | ||
|
cec79fed44 | ||
|
31a7563de5 | ||
|
0ad2058a1a | ||
|
44365d70fe | ||
|
bbf0b1e3ba | ||
|
8972319a85 | ||
|
85f713c8c7 | ||
|
6e020cada2 | ||
|
d436a7bad7 | ||
|
16a275529d | ||
|
0ad493d860 | ||
|
30774957ba | ||
|
8a1a772ab7 | ||
|
0d9a10f5fa | ||
|
1c9907c5d2 | ||
|
050530a991 | ||
|
f984cff5b4 | ||
|
ad7297c7e3 | ||
|
17d90a32e1 | ||
|
89a20eafd8 | ||
|
a2be821d2f | ||
|
39022b3b66 | ||
|
fe8f79698c | ||
|
a0a8f95d37 | ||
|
7247b4471a | ||
|
6bce62a598 | ||
|
629c73e630 | ||
|
fb0d262336 | ||
|
28df09a8d4 | ||
|
2e93b7c4c9 | ||
|
4125baf066 | ||
|
1fb2569a39 | ||
|
19ed5a2d3d | ||
|
84272f5b5d | ||
|
90804f7625 | ||
|
824dafb525 | ||
|
3fcf77e214 | ||
|
c5d2a34ebd | ||
|
a04a0e7175 | ||
|
9aa0d38116 | ||
|
f8452429a4 | ||
|
4349331ee7 | ||
|
1d3dbdf989 | ||
|
60c873aa66 | ||
|
674fa0e5ce | ||
|
f28e3ca504 | ||
|
96d6a72b97 | ||
|
7b3fa501c6 | ||
|
16dbcc43b0 | ||
|
c0c300925d | ||
|
0a02a08ed4 | ||
|
95c05f6107 | ||
|
e24785015d | ||
|
554c37febe | ||
|
dbb9dc4458 | ||
|
c6dba21212 | ||
|
94b5e4e2f8 | ||
|
0d891b1c93 | ||
|
2f77deb0a8 | ||
|
fa0adb17b8 | ||
|
36bd2290e9 | ||
|
372a5ba1ec | ||
|
25f45da195 | ||
|
9d191a783d | ||
|
0dc3593660 | ||
|
f609cc7042 | ||
|
360666a758 | ||
|
3f44056243 | ||
|
d075cbf69b | ||
|
6b13c00d56 | ||
|
6e95c6c200 | ||
|
35fed2676a | ||
|
9754e26e5f | ||
|
af9295723c | ||
|
55993aff04 | ||
|
66c5a35f36 | ||
|
c1260dcb9b | ||
|
f4e7405d92 | ||
|
c15cc34bfd | ||
|
16f32da647 | ||
|
4c34d73501 | ||
|
df1bd62f47 | ||
|
3ced179fbb | ||
|
957ceff87c | ||
|
85fcc2eab7 | ||
|
e7143b53d5 | ||
|
b728e10616 | ||
|
8ffa0061e9 | ||
|
b767c2fa54 | ||
|
aeb41bc516 | ||
|
178bdff62a | ||
|
3e79da6a79 | ||
|
f762c4c7a2 | ||
|
8de0bdca93 | ||
|
ef2fcd60d7 | ||
|
55b61775e8 | ||
|
07e57b1498 | ||
|
b522c9f62f | ||
|
04a7c57d64 | ||
|
367da0c78f | ||
|
057c21f7d0 | ||
|
af8ab57e60 | ||
|
409b6b807e | ||
|
4a454f0817 | ||
|
6b806922ee | ||
|
64a67b57b8 | ||
|
76bcf9dcf7 | ||
|
234dfa18d3 | ||
|
ba0823f4d0 | ||
|
9d401512d3 | ||
|
464e99505b | ||
|
17cf3fd7ad | ||
|
60998c9146 | ||
|
bd19225219 | ||
|
2868d62185 | ||
|
2a3d20d300 | ||
|
30dee07a3b | ||
|
96ecb1d07e | ||
|
99056a7807 | ||
|
374790176f | ||
|
f3e2a55869 | ||
|
666f3ea152 | ||
|
25fe56116c | ||
|
b27eead016 | ||
|
77a01f0cd4 | ||
|
5dfa08ace6 | ||
|
4c61dfef62 | ||
|
9653f082a3 | ||
|
3e2d892fb5 | ||
|
30d47b4fa6 | ||
|
fd3f591541 | ||
|
c85ba51274 | ||
|
843da1d48d | ||
|
d8cf44fdc9 | ||
|
8662797cf8 | ||
|
eabb0bb41d | ||
|
4966bef9c3 | ||
|
0f625c27a1 | ||
|
248b9ff1e1 | ||
|
39f3a1c697 | ||
|
c6100fc26c | ||
|
84b474d070 | ||
|
c4b977c6e1 | ||
|
a1907aaddb | ||
|
56ed56a986 | ||
|
91f28bfb8a | ||
|
46d7db8214 | ||
|
b5cdb44642 | ||
|
cb964c6dcd | ||
|
e79a4771c1 | ||
|
9006acb66a | ||
|
435a6b2f1a | ||
|
4d288ddd55 | ||
|
24cea5110e | ||
|
79f804b2d4 | ||
|
4b85e39e3e | ||
|
8f5918de4d | ||
|
ae762aa928 | ||
|
928da82dde | ||
|
94ea857738 | ||
|
0bb92e9e91 | ||
|
df4aab1d73 | ||
|
d3f93984d4 | ||
|
125d61eb68 | ||
|
c0988ba6d9 | ||
|
03b9904b07 | ||
|
24a9931abd | ||
|
2581a3433e | ||
|
8c9ca1e0f2 | ||
|
dcae051e85 | ||
|
3d03bf6f91 | ||
|
54b154f85f | ||
|
37c926d178 | ||
|
f50f81d321 | ||
|
743ace7e60 | ||
|
5d476e7259 | ||
|
fb6bcc8470 | ||
|
bda1633979 | ||
|
4169f580b8 | ||
|
4a4edcf82a | ||
|
a1fc0db8a2 | ||
|
dc19652c2b | ||
|
d2f1488934 | ||
|
bbdf6c6eaf | ||
|
f532d28fb3 | ||
|
0135368328 | ||
|
af81a52746 | ||
|
86b4c9ac73 | ||
|
1df3b4e18f | ||
|
fa204eca3b | ||
|
2c34fe2dc3 | ||
|
9c34187391 | ||
|
10d78a3102 | ||
|
46378845e9 | ||
|
ff39b22686 | ||
|
601e11980f | ||
|
8cef299878 | ||
|
914bfb105c | ||
|
076ecca5a9 | ||
|
840648b750 | ||
|
29fd4c4bd2 | ||
|
905fa7dd86 | ||
|
e8af71f641 | ||
|
6901d368d2 | ||
|
e89a340fae | ||
|
c3d6135fb3 | ||
|
6611defd87 | ||
|
5c449a9e30 | ||
|
415ac6a748 | ||
|
7ddafa098f | ||
|
177f7b9cb3 | ||
|
fb1704b717 | ||
|
ade1f1b911 | ||
|
344a9836d2 | ||
|
2c1487d303 | ||
|
e1e505e695 | ||
|
c5cd68416f | ||
|
45f757e157 | ||
|
de77c8a210 | ||
|
54b3af2c88 | ||
|
171a945de9 | ||
|
d845d10d73 | ||
|
98d91a746e | ||
|
1be1b5b263 | ||
|
dafb0c8d5c | ||
|
6ca69a9947 | ||
|
ff171a39c6 | ||
|
8acee57118 | ||
|
d03ab989e6 | ||
|
4f5b1d9646 | ||
|
f8dca1237a | ||
|
c382f706a6 | ||
|
23cbed310a | ||
|
1786ba30f7 | ||
|
530ce0952c | ||
|
262f10af9a | ||
|
3430a8f3ea | ||
|
959be3a23d | ||
|
15306fc116 | ||
|
fb5013db88 | ||
|
ab4780c9b9 | ||
|
e6a71ab7de | ||
|
6aecf68098 | ||
|
dc54c6139f | ||
|
71f158c526 | ||
|
dc7067baff | ||
|
ced226777f | ||
|
579d4f7a5b | ||
|
60b91d4d50 | ||
|
917042c48c | ||
|
ed27c35bb4 | ||
|
c35c94b7b7 | ||
|
d8317f7439 | ||
|
9a3813f883 | ||
|
0a7f77ea16 | ||
|
d6329a1ab6 | ||
|
0ba120356a | ||
|
d402b49f07 | ||
|
d4ba9fa09a | ||
|
62f352243d | ||
|
1ad19b5e93 | ||
|
e7ba0a5c60 | ||
|
050407f7c7 | ||
|
dffe096c59 | ||
|
bf5ad2cf18 | ||
|
a8ae4ddde8 | ||
|
2596a9ef7e | ||
|
f74989ac1d | ||
|
d3bc4017a5 | ||
|
ab8480d983 | ||
|
50dcc24900 | ||
|
a66470f93b | ||
|
d2dba1826d | ||
|
7f02195377 | ||
|
0002cddd67 | ||
|
19e1683106 | ||
|
5c538c7865 | ||
|
ee23967afe | ||
|
86dd8f3fd8 | ||
|
f19e2a0995 | ||
|
afd1002fdb | ||
|
07c7ed0a4e | ||
|
b48eb6e9cc | ||
|
e5da5a34cb | ||
|
f3a2c467ed | ||
|
a1fd35aa67 | ||
|
ad8ed37ff6 | ||
|
088e8bc9f9 | ||
|
3714323d74 | ||
|
d70b19fa93 | ||
|
08693a6875 | ||
|
73eca2407b | ||
|
ae7a52cecf | ||
|
2e244dd448 | ||
|
1ad77530aa | ||
|
e44963728f | ||
|
9075371145 | ||
|
3b256a708e | ||
|
aea517515c | ||
|
f28889284d | ||
|
240f1f51b9 | ||
|
05efd7423e | ||
|
fce8a3bc18 | ||
|
2adb5af051 | ||
|
8b7e5e527a | ||
|
c3d8916802 | ||
|
532f5e58ea | ||
|
57a5714fb5 | ||
|
a3fd49499b | ||
|
3dc2cd4d7a | ||
|
0b738e3d9e | ||
|
b29c2b2de4 | ||
|
40f2d19f81 | ||
|
2e997f2c67 | ||
|
0fd50892af | ||
|
55bd346cb2 | ||
|
0724ac133b | ||
|
0507fa5b0e | ||
|
617e945afa | ||
|
870c4bf765 | ||
|
9a592e9c7e | ||
|
53592ac404 | ||
|
d8848a6062 | ||
|
173c1d3a6e | ||
|
c815c4080d | ||
|
c09626182c | ||
|
65c6ce3033 | ||
|
06cc2f527e | ||
|
9da5061689 | ||
|
82b4415f7d | ||
|
3f1e5b9b1e | ||
|
1b95d98ccd | ||
|
e2e1925796 | ||
|
52c8f24df5 | ||
|
ca855da8ae | ||
|
a39d35e54c | ||
|
3bd2b24b10 | ||
|
12448426d4 | ||
|
a043d7cac7 | ||
|
4d88111d48 | ||
|
34d6610bf8 | ||
|
4bfc52327b | ||
|
15d5f7ff55 | ||
|
91ecd1aeeb | ||
|
342f871916 | ||
|
18d51de0b9 | ||
|
4dc6cae854 | ||
|
5fb160e1ac | ||
|
f1589314c2 | ||
|
7fb9a550c8 | ||
|
46f7f4814c | ||
|
c002cc104c | ||
|
492128a621 | ||
|
fff0f09684 | ||
|
64054d13a3 | ||
|
ae8d7096c3 | ||
|
13e04b6362 | ||
|
89e557bcef | ||
|
fdf4749c1b | ||
|
a63608bf97 | ||
|
5c101e9466 | ||
|
4b25ebc731 | ||
|
8e88fcb462 | ||
|
fd19b345a1 | ||
|
4a7bba047b | ||
|
440c21e9f3 | ||
|
959b679086 | ||
|
64cfd4d81a | ||
|
c742ca3b41 | ||
|
2002252f72 | ||
|
7df8b3a9bf | ||
|
3fea2173f4 | ||
|
d125cb5c01 | ||
|
86c9264ed9 | ||
|
00f2d0249f | ||
|
4465e6eea3 | ||
|
b87fb8c396 | ||
|
d4706b38b8 | ||
|
31c82b4ba6 | ||
|
cb80d8d349 | ||
|
7be3434136 | ||
|
ec1422b0f0 | ||
|
b0a6eaaa96 | ||
|
d1c4d4b099 | ||
|
d35905787d | ||
|
9a972b2f73 | ||
|
1fe0c8a3e9 | ||
|
116bab5bc8 | ||
|
c76eb3bc98 | ||
|
81c1717384 | ||
|
0fd0500d30 | ||
|
34d638da4f | ||
|
e39b177b5b | ||
|
07aa3ee64c | ||
|
c94856cdf8 | ||
|
4dd0c04537 | ||
|
99c409b6d2 | ||
|
8cb2c2532f | ||
|
6f804cab4d | ||
|
cf3dbb378e | ||
|
e32716aa48 | ||
|
ba46f10e3f | ||
|
4b37ede8c2 | ||
|
dbe4c0c8e4 | ||
|
4f4afd6840 | ||
|
7409fde650 | ||
|
cfa31e6332 | ||
|
df973a6275 | ||
|
0eb36a607b | ||
|
0509e76f18 | ||
|
e379ccf086 | ||
|
7ae52d676d | ||
|
3d33018ffa | ||
|
860595520b | ||
|
ae318a835e | ||
|
7a3dbecc08 | ||
|
c52aae7c29 | ||
|
f0f3e8ddb9 | ||
|
a95102a78f | ||
|
2adafbeb03 | ||
|
f3a5fb7fe3 | ||
|
907a786b1a | ||
|
e3ed3e5b05 | ||
|
a2b366ebfe | ||
|
f7de2f0f13 | ||
|
919225bdfd | ||
|
88cba74cac | ||
|
e9ca876444 | ||
|
0992e76800 | ||
|
8a9498bae4 | ||
|
d2598480c8 | ||
|
6e57b06673 | ||
|
a036be6436 | ||
|
53c7ea2831 | ||
|
e117fec74f | ||
|
6e01b75b2f | ||
|
691e7fe616 | ||
|
e793a46576 | ||
|
fa48e8cffa | ||
|
69cf437a39 | ||
|
ebbc432570 | ||
|
2532724e92 | ||
|
e31693b4b7 | ||
|
3ed6452232 | ||
|
166aaa62f0 | ||
|
45e5fff622 | ||
|
8f2dba09ee | ||
|
2ea46c5e54 | ||
|
fc5f0f7673 | ||
|
188f4a2e72 | ||
|
cf97fc3b01 | ||
|
5267ba240a | ||
|
f185dcacd7 | ||
|
330a33a0e8 | ||
|
b75b299847 | ||
|
7c59bcc928 | ||
|
0e110b0794 | ||
|
e156a62e19 | ||
|
628439aa65 | ||
|
d49fcb80fc | ||
|
00fd067c6b | ||
|
ca37895619 | ||
|
43497e0da9 | ||
|
efa510c0a8 | ||
|
0828159ee4 | ||
|
90f21198c3 | ||
|
a0b0778fce | ||
|
d937fa67ad | ||
|
91d396fbca | ||
|
afba5b2b6c | ||
|
b579a1bc83 | ||
|
db49673fc7 | ||
|
bb0511e659 | ||
|
0e0763ec2d | ||
|
3faf42be53 | ||
|
dea903bcb5 | ||
|
e1c6542e03 | ||
|
01484978bd | ||
|
cad14c93d0 | ||
|
ee52b9185b | ||
|
861a7f791f | ||
|
f2fa57224b | ||
|
e0977dd97b | ||
|
f47bef71a4 | ||
|
92985fc8e7 | ||
|
243b0a7d82 | ||
|
92c719a803 | ||
|
f4108ae0eb | ||
|
ecf3fee709 | ||
|
e67e472025 | ||
|
a6541481bf | ||
|
85a4f83662 | ||
|
22955e6b34 | ||
|
9520aff848 | ||
|
6b09a78ece | ||
|
789bcc8d77 | ||
|
2914117a8e | ||
|
8049962a99 | ||
|
225b1c380e | ||
|
60d80ea0ba | ||
|
c8211098f3 | ||
|
e78fde4eca | ||
|
59d60813fb | ||
|
4c31e52892 | ||
|
c646fd2b36 | ||
|
6432859732 | ||
|
2beef7d816 | ||
|
623056455b | ||
|
7a4d9370e3 | ||
|
fe3138492e | ||
|
05a52164f3 | ||
|
d14f1dd1ab | ||
|
88e8c11ee5 | ||
|
7afc7bdb31 | ||
|
84a3754c9f | ||
|
4b2f8e9174 | ||
|
a17932e17e | ||
|
084c27a2bb | ||
|
ed2f62cbe7 | ||
|
38fb7185b6 | ||
|
ce42d2fb8a | ||
|
af3fc22e2d | ||
|
b659cb60a2 | ||
|
34cf9903dc | ||
|
062a21e39a | ||
|
4510aff00a | ||
|
d0953b8406 | ||
|
7822660ce7 | ||
|
bdfcf5c67c | ||
|
ae0d09a049 | ||
|
69759b7415 | ||
|
7e8e1ab9b7 | ||
|
b44f5d3b4a | ||
|
03f8b66993 | ||
|
e411f139c8 | ||
|
e962d1dadf | ||
|
756b0febe6 | ||
|
1535f3e2e5 | ||
|
3e808dec90 | ||
|
637f4a8350 | ||
|
d3bc9f52fd | ||
|
ffd8ac859d | ||
|
6e43e9b51c | ||
|
426171508e | ||
|
e86460b578 | ||
|
8dd5f88dba | ||
|
3aa6de7cf5 | ||
|
a75242c79d | ||
|
784918350b | ||
|
0199cf9a03 | ||
|
ab6e7a3b8a | ||
|
f489265ce7 | ||
|
6c9c3e5cb3 | ||
|
9b7c2599a7 | ||
|
25bbd7c526 | ||
|
e0c3f3638d | ||
|
d45653dbb3 | ||
|
f70623beea | ||
|
e542e4ba22 | ||
|
05d1e64cb5 | ||
|
adac80062c | ||
|
28f8d9500e | ||
|
538bda329e | ||
|
a00ddca188 | ||
|
8ac2cb0530 | ||
|
19d655ec41 | ||
|
4961bb0e08 | ||
|
f45040c405 | ||
|
9fed62e4a7 | ||
|
43b9e2cec9 | ||
|
3185b88fe5 | ||
|
c2906d9b06 | ||
|
cfb615f972 | ||
|
9c22c0952c | ||
|
5a834619c0 | ||
|
3b62f50f7b | ||
|
18e804d174 | ||
|
c105d82027 | ||
|
860921217d | ||
|
3fe2f2876a | ||
|
e84fd408be | ||
|
f361fd7355 | ||
|
458e3ee5e8 | ||
|
5fa247a0c5 | ||
|
48e58967b2 | ||
|
d1d79c0191 | ||
|
0a41a9f773 | ||
|
03fa0e6ad6 | ||
|
8eebcef4e9 | ||
|
ea1c75c16a | ||
|
7a2aefd8fb | ||
|
33ec1bbfb3 | ||
|
8883832b86 | ||
|
308828ef50 | ||
|
885dac4ad1 | ||
|
c03d61e09f | ||
|
b2bacdfa4e | ||
|
06defaf14e | ||
|
22e3b370e3 | ||
|
8f5589d3e1 | ||
|
03389fc040 | ||
|
4698cf7a9b | ||
|
5004fba986 | ||
|
8cc82fe5ba | ||
|
c9fb231714 | ||
|
0f22b55786 | ||
|
535148e68a | ||
|
878e093b6b | ||
|
0e5f741b6b | ||
|
36b1717fc1 | ||
|
37392b5495 | ||
|
84f2fc41b3 | ||
|
ebdf75091a | ||
|
ce304ace2b | ||
|
0d2acec73e | ||
|
0144764f69 | ||
|
aad4b3dc39 | ||
|
996aa9ef66 | ||
|
9778999a7f | ||
|
91301197ea | ||
|
040deea655 | ||
|
1e2b5dd428 | ||
|
8d32c27ce0 | ||
|
074a9e9f29 | ||
|
650b6bd9ea | ||
|
3dd74d6828 | ||
|
f717a37a4a | ||
|
d8b1372a0f | ||
|
678cf50dbd | ||
|
57fca80cbb | ||
|
cf7de8bb8b | ||
|
a70fdedce5 | ||
|
c173235ee3 | ||
|
f74b1e6c2e | ||
|
c9bc6f4a9e | ||
|
63c18e82c8 | ||
|
037b2e1d60 | ||
|
aea9c958bf | ||
|
7f185a729e | ||
|
d08b4e1ea0 | ||
|
4eaed945e2 | ||
|
14b1b10556 | ||
|
df762e40bb | ||
|
684972185f | ||
|
04dd13d03b | ||
|
700fd47f22 | ||
|
b36759deb4 | ||
|
25d224be6b | ||
|
fe013f803e | ||
|
01d6b52a60 | ||
|
ce884ac577 | ||
|
6abc51d05d | ||
|
a98916c985 | ||
|
9e29533aad | ||
|
7119403cde | ||
|
7f55e4fb1e | ||
|
82df62a600 | ||
|
d0c722eae1 | ||
|
40649e9c3c | ||
|
431b285806 | ||
|
75f84fe1f4 | ||
|
98bf02efa9 | ||
|
0eb68b531c | ||
|
9124844e3e | ||
|
f568553d21 | ||
|
92c9d4fc22 | ||
|
957d51cf3f | ||
|
54ecc25831 | ||
|
738a368a6f | ||
|
969f070175 | ||
|
d8d78b124d | ||
|
9a9f0c200e | ||
|
247ffc1270 | ||
|
690d05aeca | ||
|
c33f3b76fa | ||
|
8616c454e1 | ||
|
17db994d35 | ||
|
6c1c1ca8b0 | ||
|
f1613eacbb | ||
|
67d1c2dc80 | ||
|
c2b2b856a1 | ||
|
44f946513f | ||
|
1cafca6de6 | ||
|
35ee7f0b40 | ||
|
6e8e7164c6 | ||
|
6bbded1e65 | ||
|
0aa90c3eea | ||
|
b44b0ec998 | ||
|
07c6259734 | ||
|
1785d4d0b4 | ||
|
22d06928c8 | ||
|
d6fe6e44bd | ||
|
d51ee19f3f | ||
|
717e5161a6 | ||
|
be9fa268b1 | ||
|
14e8bbcec6 | ||
|
26105dc25f | ||
|
53ba1c2068 | ||
|
fa004c9d93 | ||
|
be94921918 | ||
|
2f5fe59aa6 | ||
|
89fb2cf391 | ||
|
f1d2abc9b1 | ||
|
2aa8512f6f | ||
|
2d31402cf0 | ||
|
6d61848ed6 | ||
|
0e0b724535 | ||
|
e13915b0c7 | ||
|
361f0415bb | ||
|
c3b662fa1f | ||
|
2d3e23ee11 | ||
|
fc8ab0d462 | ||
|
89629ffe93 | ||
|
e97d565809 | ||
|
7e3413eda7 | ||
|
7966f6e308 | ||
|
e1286a0ed4 | ||
|
1e2d267fec | ||
|
8b3403c115 | ||
|
da4b029093 | ||
|
b48113a353 | ||
|
e1884e7c73 | ||
|
daae030134 | ||
|
19e1da1216 | ||
|
e9bb95b3b3 | ||
|
a43ca5925c | ||
|
dd46798bda | ||
|
d70a09ded8 | ||
|
439aa7854c | ||
|
750550ad3e | ||
|
b6af2269d2 | ||
|
b047f36e86 | ||
|
afbda4ac28 | ||
|
f5fd0ac323 | ||
|
18de0ca951 | ||
|
b53c073b90 | ||
|
13ebef334f | ||
|
b1ba4e393e | ||
|
d898bc71f7 | ||
|
0afa7a706a | ||
|
da68212255 | ||
|
4fdd2f4eed | ||
|
583139d51e | ||
|
cee8ae3af4 | ||
|
182753e4ec | ||
|
c7c6cf70e4 | ||
|
1491bddb3b | ||
|
ac5db83880 | ||
|
b2f3ba220e | ||
|
deb783f797 | ||
|
5fcf54cd57 | ||
|
29c9d3070c | ||
|
4306cb7812 | ||
|
a4b8dc9400 | ||
|
be9393fabe | ||
|
c7a7ad7b57 | ||
|
78b7f03138 | ||
|
ffeae7ec83 | ||
|
db77e7b817 | ||
|
fcee85a682 | ||
|
17ddb5ce43 | ||
|
53583c691f | ||
|
2b9d3960b3 | ||
|
92befcde5d | ||
|
697eaec197 | ||
|
86fba28313 | ||
|
f3c3c07d46 | ||
|
8966e24925 | ||
|
becc5a7b54 | ||
|
a61434ae08 | ||
|
20b726819f | ||
|
bfd847179f | ||
|
7e955ef0e4 | ||
|
2697800deb | ||
|
2c47fe9f0d | ||
|
423f21b02e | ||
|
eb6546d81c | ||
|
4e2878300f | ||
|
4578b9df7f | ||
|
d679c9d5d8 | ||
|
dc7b3dfc9d | ||
|
6843ea113b | ||
|
0a8c954397 | ||
|
358e10a093 | ||
|
c0b7ea6dd1 | ||
|
0b5e618c1c | ||
|
1f528ee428 | ||
|
bbd8b89589 | ||
|
a5d2d65131 | ||
|
b45504d97a | ||
|
0598ecaca3 | ||
|
0d9749a515 | ||
|
836766f978 | ||
|
93851d0ab2 | ||
|
5fff637bee | ||
|
eae015caa1 | ||
|
80a356c7e2 | ||
|
3a0eed795a | ||
|
c1c0c6f2c6 | ||
|
f04868ba19 | ||
|
b052884912 | ||
|
7fb7729af6 | ||
|
2f5d824c65 | ||
|
fbc46b3c8b | ||
|
e986c9d343 | ||
|
3100473305 | ||
|
5eb9f32acb | ||
|
0d12a80832 | ||
|
077c166c09 | ||
|
5d26b6a7cb | ||
|
68c1e8fc6d | ||
|
1ffd7dbb9f | ||
|
0cc48a190f | ||
|
8206a78156 | ||
|
6a1e38ca04 | ||
|
779f380d2f | ||
|
55f7461747 | ||
|
7665aba22c | ||
|
e96c5f7305 | ||
|
c5ba34d619 | ||
|
c13439eeb9 | ||
|
d27b73f6be | ||
|
bb427700d2 | ||
|
4589aaa11c | ||
|
b3dbcd7936 | ||
|
cac246aa15 | ||
|
d2f0957eba | ||
|
db18272ef2 | ||
|
cf5d89ea9b | ||
|
0aeb327062 | ||
|
5dc50195b3 | ||
|
83db9b34d4 | ||
|
a43df43642 | ||
|
57a87ba620 | ||
|
f6cbc15cf7 | ||
|
7322144dc8 | ||
|
8e357c6b7f | ||
|
7b20db64a5 | ||
|
429c634ed9 | ||
|
05230a6afa | ||
|
43eb804b23 | ||
|
5840248ffa | ||
|
6ea38c7eb0 | ||
|
9586fa9f90 | ||
|
0d0af6906e | ||
|
93070f3524 | ||
|
7cf7b7e10e | ||
|
1de4869cde | ||
|
a4eba653a3 | ||
|
21d0db8382 | ||
|
269d6e4d08 | ||
|
3cf341c3bf | ||
|
f0a9be2ec7 | ||
|
8955e5461c | ||
|
087ff1c041 | ||
|
1a307a0c4d | ||
|
071a43c8d4 | ||
|
7b46c49ded | ||
|
da5672d229 | ||
|
dcfd9ee7a7 | ||
|
35a6f90ed6 | ||
|
d463e5e500 | ||
|
0f00597444 | ||
|
a806f70b35 | ||
|
67f07bd1bb | ||
|
39e18446ae | ||
|
4dc0b00569 | ||
|
87979ccadd | ||
|
7c2a5af8f2 | ||
|
dc6d4c6789 | ||
|
db3d5e2677 | ||
|
6dc8bdde04 | ||
|
c02cfb2f4f | ||
|
947c46d7b5 | ||
|
8942ce964a | ||
|
a05c401892 | ||
|
f25c981173 | ||
|
43055964ba | ||
|
a4192a0761 | ||
|
9c8ff7de7f | ||
|
b4247c89e4 | ||
|
cdabca6def | ||
|
2d6f0205a4 | ||
|
4e8177f738 | ||
|
798e9e4fde | ||
|
8871390167 | ||
|
fc86e7e1f6 | ||
|
21912c290a | ||
|
df335c7aa3 | ||
|
8bd4cc8f54 | ||
|
4e3df99e42 | ||
|
b1e1b4a7dc | ||
|
a233e9b0a0 | ||
|
ebecb9bb9a | ||
|
35962c3cb5 | ||
|
0ac6a26b6e | ||
|
0a887c0926 | ||
|
54c0239969 | ||
|
8559254593 | ||
|
626eb4d06b | ||
|
aa16ba88ae | ||
|
a633c11c1d | ||
|
a4931e21ae | ||
|
996fabb327 | ||
|
6c4e71d7d4 | ||
|
ad0ad502aa | ||
|
42b47c25aa | ||
|
7ef1970a0b | ||
|
409d751612 | ||
|
bdce71abfd | ||
|
114bce5f64 | ||
|
20e5ebc88b | ||
|
52aa57ac7c | ||
|
8daf72a4b0 | ||
|
51eb2cda95 | ||
|
57779c99c2 | ||
|
02e02ed691 | ||
|
af0b798ef1 | ||
|
51be8d5ed5 | ||
|
270bed5013 | ||
|
20b3c33fb0 | ||
|
b2aaf1cca1 | ||
|
5f6969e2cc | ||
|
f0648ee52a | ||
|
88c70a2c10 | ||
|
22c3ed6bb9 | ||
|
b0d25fa84f | ||
|
57636207d2 | ||
|
eac9133bb1 | ||
|
f7e7659750 | ||
|
e719541b5e | ||
|
bd7acfbb1a | ||
|
25b42cb4f3 | ||
|
928149fe35 | ||
|
a80181da9e | ||
|
72de5d6adc | ||
|
ed4154d763 | ||
|
4ee13b6fa1 | ||
|
33fb1dd147 | ||
|
736905edf8 | ||
|
e8a91eab88 | ||
|
b951af0116 | ||
|
c3299845c1 | ||
|
54644db587 | ||
|
cb0e93c43e | ||
|
4c4ec6cfe8 | ||
|
449be02f53 | ||
|
25d2c2e2c6 | ||
|
ec2ba7c0b2 | ||
|
06a13d5c20 | ||
|
7e0591ffee | ||
|
1363100f94 | ||
|
06cf59bca7 | ||
|
e37dd547b8 | ||
|
3d07ccd98e | ||
|
03b9774c56 | ||
|
0f1ddee71c | ||
|
855efa93cc | ||
|
d0f776a9cf | ||
|
da66e38c68 | ||
|
a4ba8c152d | ||
|
9b320ed3c7 | ||
|
c854491248 | ||
|
5755d5bfaa | ||
|
ff320fec55 | ||
|
8c8a84b039 | ||
|
045e3d7bae | ||
|
3163bc8b80 | ||
|
eca3bf0817 | ||
|
c39a0e4fd5 | ||
|
59280ed18e | ||
|
c1acb1af66 | ||
|
a6368c473e | ||
|
3615ca6b95 | ||
|
ddb00ba23a | ||
|
91cf4b647d | ||
|
f989eed8b0 | ||
|
4d296ddc09 | ||
|
6186c22e02 | ||
|
13cd13a42f | ||
|
a42eb42178 | ||
|
7924ef207c | ||
|
5900245018 | ||
|
00c239bc42 | ||
|
0cb43eef51 | ||
|
41a8f40241 | ||
|
a8641ef879 | ||
|
2e4d30ef29 | ||
|
367f793929 | ||
|
a2367ef14f | ||
|
dec591517c | ||
|
128f3493b7 | ||
|
56677f0908 | ||
|
c498416075 | ||
|
007fbf8ed3 | ||
|
cfee2f93f2 | ||
|
97aca28c0d | ||
|
a35302eae0 | ||
|
637eba277f | ||
|
f471d9cff8 | ||
|
24a7ce7d98 | ||
|
358fcb6b34 | ||
|
902a9aa243 | ||
|
f9c0256afd | ||
|
8e9ac8198d | ||
|
eb32c5455f | ||
|
01452efd8d | ||
|
ec0974f72c |
5
.idea/dictionaries/bmarty.xml
generated
5
.idea/dictionaries/bmarty.xml
generated
@@ -7,17 +7,21 @@
|
||||
<w>ciphertext</w>
|
||||
<w>coroutine</w>
|
||||
<w>decryptor</w>
|
||||
<w>displayname</w>
|
||||
<w>emoji</w>
|
||||
<w>emojis</w>
|
||||
<w>fdroid</w>
|
||||
<w>gplay</w>
|
||||
<w>hmac</w>
|
||||
<w>homeserver</w>
|
||||
<w>ktlint</w>
|
||||
<w>linkified</w>
|
||||
<w>linkify</w>
|
||||
<w>megolm</w>
|
||||
<w>msisdn</w>
|
||||
<w>msisdns</w>
|
||||
<w>pbkdf</w>
|
||||
<w>pids</w>
|
||||
<w>pkcs</w>
|
||||
<w>riotx</w>
|
||||
<w>signin</w>
|
||||
@@ -25,6 +29,7 @@
|
||||
<w>signup</w>
|
||||
<w>ssss</w>
|
||||
<w>threepid</w>
|
||||
<w>unwedging</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
171
CHANGES.md
171
CHANGES.md
@@ -1,4 +1,158 @@
|
||||
Changes in RiotX 0.19.0 (2020-XX-XX)
|
||||
Changes in Riot.imX 0.91.4 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
|
||||
Improvements 🙌:
|
||||
-
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix crash when coming from a notification (#1601)
|
||||
- Fix Exception when importing keys (#1576)
|
||||
- File isn't downloaded when another file with the same name already exists (#1578)
|
||||
- saved images don't show up in gallery (#1324)
|
||||
- Fix reply fallback leaking sender locale (#429)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
SDK API changes ⚠️:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
- Fix lint false-positive about WorkManger (#1012)
|
||||
- Upgrade build-tools from 3.5.3 to 3.6.6
|
||||
- Upgrade gradle from 5.4.1 to 5.6.4
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Changes in Riot.imX 0.91.3 (2020-07-01)
|
||||
===================================================
|
||||
|
||||
Notes:
|
||||
- This version is the third beta version of RiotX codebase published as Riot-Android on the PlayStore.
|
||||
- Changelog below includes changes of v0.91.0, v0.91.1, and v0.91.2, because the first beta versions have been tagged and
|
||||
published from the branch feature/migration_from_legacy.
|
||||
- This version uses temporary name `Riot.imX`, to distinguish the app with RiotX app.
|
||||
|
||||
Features ✨:
|
||||
- Call with WebRTC support (##611)
|
||||
- Add capability to change the display name (#1529)
|
||||
|
||||
Improvements 🙌:
|
||||
- "Add Matrix app" menu is now always visible (#1495)
|
||||
- Handle `/op`, `/deop`, and `/nick` commands (#12)
|
||||
- Prioritising Recovery key over Recovery passphrase (#1463)
|
||||
- Room Settings: Name, Topic, Photo, Aliases, History Visibility (#1455)
|
||||
- Update user avatar (#1054)
|
||||
- Allow self-signed certificate (#1564)
|
||||
- Improve file download and open in timeline
|
||||
- Catchup tab is removed temporarily (#1565)
|
||||
- Render room avatar change (#1319)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix dark theme issue on login screen (#1097)
|
||||
- Incomplete predicate in RealmCryptoStore#getOutgoingRoomKeyRequest (#1519)
|
||||
- User could not redact message that they have sent (#1543)
|
||||
- Use vendor prefix for non merged MSC (#1537)
|
||||
- Compress images before sending (#1333)
|
||||
- Searching by displayname is case sensitive (#1468)
|
||||
- Fix layout overlap issue (#1407)
|
||||
|
||||
Build 🧱:
|
||||
- Enable code optimization (Proguard)
|
||||
- SDK is now API level 21 minimum, and so RiotX (#405)
|
||||
|
||||
Other changes:
|
||||
- Use `SharedPreferences#edit` extension function consistently (#1545)
|
||||
- Use `retrofit2.Call.awaitResponse` extension provided by Retrofit 2. (#1526)
|
||||
- Fix minor typo in contribution guide (#1512)
|
||||
- Fix self-assignment of callback in `DefaultRoomPushRuleService#setRoomNotificationState` (#1520)
|
||||
- Random housekeeping clean-ups indicated by Lint (#1520, #1541)
|
||||
- Keys Backup API now use the unstable prefix (#1503)
|
||||
- Remove deviceId from /keys/upload/{deviceId} as not spec-compliant (#1502)
|
||||
|
||||
Changes in RiotX 0.22.0 (2020-06-15)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Integration Manager and Widget support (#48)
|
||||
- Send stickers (#51)
|
||||
|
||||
Improvements 🙌:
|
||||
- New wording for notice when current user is the sender
|
||||
- Hide "X made no changes" event by default in timeline (#1430)
|
||||
- Hide left rooms in breadcrumbs (#766)
|
||||
- Handle PowerLevel properly (#627)
|
||||
- Correctly handle SSO login redirection
|
||||
- SSO login is now performed in the default browser, or in Chrome Custom tab if available (#1400)
|
||||
- Improve checking of homeserver version support (#1442)
|
||||
- Add capability to add and remove a room from the favorites (#1217)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Switch theme is not fully taken into account without restarting the app
|
||||
- Temporary fix to show error when user is creating an account on matrix.org with userId containing only digits (#1410)
|
||||
- Reply composer overlay stays on screen too long after send (#1169)
|
||||
- Fix navigation bar icon contrast on API in [21,27[ (#1342)
|
||||
- Fix status bar icon contrast on API in [21,23[
|
||||
- Wrong /query request (#1444)
|
||||
- Make Credentials.homeServer optional because it is deprecated (#1443)
|
||||
- Fix issue on dark themes, after alert popup dismiss
|
||||
|
||||
Other changes:
|
||||
- Send plain text in the body of events containing formatted body, as per https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
||||
- Update link to Modular url from "https://modular.im/" to "https://modular.im/services/matrix-hosting-riot" and open it using ChromeCustomTab
|
||||
|
||||
Changes in RiotX 0.21.0 (2020-05-28)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Identity server support (#607)
|
||||
- Switch language support (#41)
|
||||
- Display list of attachments of a room (#860)
|
||||
|
||||
Improvements 🙌:
|
||||
- Better connectivity lost indicator when airplane mode is on
|
||||
- Add a setting to hide redacted events (#951)
|
||||
- Render formatted_body for m.notice and m.emote (#1196)
|
||||
- Change icon to magnifying-glass to filter room (#1384)
|
||||
|
||||
Bugfix 🐛:
|
||||
- After jump to unread, newer messages are never loaded (#1008)
|
||||
- Fix issues with FontScale switch (#69, #645)
|
||||
- "Seen by" uses 12h time (#1378)
|
||||
- Enable markdown (if active) when sending emote (#734)
|
||||
- Screenshots for Rageshake now includes Dialogs such as BottomSheet (#1349)
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- initialize with proxy configuration
|
||||
|
||||
Other changes:
|
||||
- support new key agreement method for SAS (#1374)
|
||||
|
||||
Changes in RiotX 0.20.0 (2020-05-15)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Add Direct Shortcuts (#652)
|
||||
|
||||
Improvements 🙌:
|
||||
- Invite member(s) to an existing room (#1276)
|
||||
- Improve notification accessibility with ticker text (#1226)
|
||||
- Support homeserver discovery from MXID (DISABLED: waiting for design) (#476)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix | Verify Manually by Text crashes if private SSK not known (#1337)
|
||||
- Sometimes the same device appears twice in the list of devices of a user (#1329)
|
||||
- Random Crashes while doing sth with cross signing keys (#1364)
|
||||
- Crash | crash while restoring key backup (#1366)
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- excludedUserIds parameter added to the UserService.getPagedUsersLive() function
|
||||
|
||||
Changes in RiotX 0.19.0 (2020-05-04)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
@@ -7,6 +161,7 @@ Features ✨:
|
||||
- Cross-Signing | Verify new session from existing session (#1134)
|
||||
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
|
||||
- Save media files to Gallery (#973)
|
||||
- Account deactivation (with password only) (#35)
|
||||
|
||||
Improvements 🙌:
|
||||
- Verification DM / Handle concurrent .start after .ready (#794)
|
||||
@@ -22,6 +177,14 @@ Improvements 🙌:
|
||||
- Emoji Verification | It's not the same butterfly! (#1220)
|
||||
- Cross-Signing | Composer decoration: shields (#1077)
|
||||
- Cross-Signing | Migrate existing keybackup to cross signing with 4S from mobile (#1197)
|
||||
- Show a warning dialog if the text of the clicked link does not match the link target (#922)
|
||||
- Cross-Signing | Consider not using a spinner on the 'complete security' prompt (#1271)
|
||||
- Restart broken Olm sessions ([MSC1719](https://github.com/matrix-org/matrix-doc/pull/1719))
|
||||
- Cross-Signing | Hide Use recovery key when 4S is not setup (#1007)
|
||||
- Cross-Signing | Trust account xSigning keys by entering Recovery Key (select file or copy) #1199
|
||||
- E2E timeline decoration (#1279)
|
||||
- Manage Session Settings / Cross Signing update (#1295)
|
||||
- Cross-Signing | Review sessions toast update old vs new (#1293, #1306)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix summary notification staying after "mark as read"
|
||||
@@ -36,9 +199,13 @@ Bugfix 🐛:
|
||||
- Render image event even if thumbnail_info does not have mimetype defined (#1209)
|
||||
- RiotX now uses as many threads as it needs to do work and send messages (#1221)
|
||||
- Fix issue with media path (#1227)
|
||||
- Add user to direct chat by user id (#1065)
|
||||
- Use correct URL for SSO connection (#1178)
|
||||
- Emoji completion :tada: does not completes to 🎉 like on web (#1285)
|
||||
- Fix bad Shield Logic for DM (#963)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
- Weblate now create PR directly to RiotX GitHub project
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- Increase targetSdkVersion to 29
|
||||
|
@@ -13,6 +13,28 @@ Dedicated room for RiotX: [![RiotX Android Matrix room #riot-android:matrix.org]
|
||||
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).
|
||||
Please ensure that your using the project formatting rules (which are in the project at .idea/codeStyles/), and format the file before committing them.
|
||||
|
||||
### Template
|
||||
|
||||
An Android Studio template has been added to the project to help creating all files needed when adding a new screen to the application. Fragment, ViewModel, Activity, etc.
|
||||
|
||||
To install the template (to be done only once):
|
||||
- Go to folder `./tools/template`.
|
||||
- Mac OSX: Run the script `./configure.sh`.
|
||||
|
||||
Linux: Run `ANDROID_STUDIO=/path/to/android-studio ./configure`
|
||||
- e.g. `ANDROID_STUDIO=/usr/local/android-studio ./configure`
|
||||
|
||||
- Restart Android Studio.
|
||||
|
||||
To create a new screen:
|
||||
- First create a new package in your code.
|
||||
- Then right click on the package, and select `New/New Vector/RiotX Feature`.
|
||||
- Follow the Wizard, especially replace `Main` by something more relevant to your feature.
|
||||
- Click on `Finish`.
|
||||
- Remaining steps are described as TODO in the generated files, or will be pointed out by the compilator, or at runtime :)
|
||||
|
||||
Note that if the templates are modified, the only things to do is to restart Android Studio for the change to take effect.
|
||||
|
||||
## Compilation
|
||||
|
||||
For now, the Matrix SDK and the RiotX application are in the same project. So there is no specific thing to do, this project should compile without any special action.
|
||||
|
@@ -11,8 +11,8 @@ RiotX is an Android Matrix Client currently in beta but in active development.
|
||||
|
||||
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.riotx)
|
||||
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.riotx)
|
||||
|
||||
Nightly build: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
|
||||
|
@@ -10,7 +10,7 @@ buildscript {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.android.tools.build:gradle:3.6.3'
|
||||
classpath 'com.google.gms:google-services:4.3.2'
|
||||
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
92
docs/identity_server.md
Normal file
92
docs/identity_server.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Identity server
|
||||
|
||||
Issue: #607
|
||||
PR: #1354
|
||||
|
||||
## Introduction
|
||||
Identity Servers support contact discovery on Matrix by letting people look up Third Party Identifiers to see if the owner has publicly linked them with their Matrix ID.
|
||||
|
||||
## Implementation
|
||||
|
||||
The current implementation was Inspired by the code from Riot-Android.
|
||||
|
||||
Difference though (list not exhaustive):
|
||||
- Only API v2 is supported (see https://matrix.org/docs/spec/identity_service/latest)
|
||||
- Homeserver has to be up to date to support binding (Versions.isLoginAndRegistrationSupportedBySdk() has to return true)
|
||||
- The SDK managed the session and client secret when binding ThreePid. Those data are not exposed to the client.
|
||||
- The SDK supports incremental sendAttempt (this is not used by RiotX)
|
||||
- The "Continue" button is now under the information, and not as the same place that the checkbox
|
||||
- The app can cancel a binding. Current data are erased from DB.
|
||||
- The API (IdentityService) is improved.
|
||||
- A new DB to store data related to the identity server management.
|
||||
|
||||
Missing features (list not exhaustive):
|
||||
- Invite by 3Pid (will be in a dedicated PR)
|
||||
- Add email or phone to account (not P1, can be done on Riot-Web)
|
||||
- List email and phone of the account (could be done in a dedicated PR)
|
||||
- Search contact (not P1)
|
||||
- Logout from identity server when user sign out or deactivate his account.
|
||||
|
||||
## Related MSCs
|
||||
The list can be found here: https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4
|
||||
|
||||
## Steps and requirements
|
||||
|
||||
- Only one identity server by account can be set. The user's choice is stored in account data with key `m.identity_server`. But every clients will managed its own token to log in to the identity server
|
||||
```json
|
||||
{
|
||||
"type": "m.identity_server",
|
||||
"content": {
|
||||
"base_url": "https://matrix.org"
|
||||
}
|
||||
}
|
||||
```
|
||||
- The accepted terms are stored in the account data:
|
||||
```json
|
||||
{
|
||||
"type": "m.accepted_terms",
|
||||
"content": {
|
||||
"accepted": [
|
||||
"https://vector.im/identity-server-privacy-notice-1"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Default identity server URL, from Wellknown data is proposed to the user.
|
||||
- Identity server can be set
|
||||
- Identity server can be changed on another user's device, so when the change is detected (thanks to account data sync) RiotX should properly disconnect from a previous identity server (I think it was not the case in Riot-Android, where we keep the token forever)
|
||||
- Registration to the identity server is managed with an openId token
|
||||
- Terms of service can be accepted when configuring the identity server.
|
||||
- Terms of service can be accepted after, if they change.
|
||||
- Identity server can be modified
|
||||
- Identity server can be disconnected with a warning dialog, with special content if there are current bound 3pid on this identity server.
|
||||
- Email can be bound
|
||||
- Email can be unbound
|
||||
- Phone can be bound
|
||||
- Phone can be unbound
|
||||
- Look up can be performed, to get matrixIds from local contact book (phone and email): Android permission correctly handled (not done yet)
|
||||
- Look up pepper can be updated if it is rotated on the identity server
|
||||
- Invitation using 3PID can be done (See #548) (not done yet)
|
||||
- Homeserver access-token will never be sent to an identity server
|
||||
- When user sign-out: logout from the identity server if any.
|
||||
- When user deactivate account: logout from the identity server if any.
|
||||
|
||||
## Screens
|
||||
|
||||
### Settings
|
||||
|
||||
Identity server settings can be accessed from the internal setting of the application, both from "Discovery" section and from identity detail section.
|
||||
|
||||
### Discovery screen
|
||||
|
||||
This screen displays the identity server configuration and the binding of the user's ThreePid (email and msisdn). This is the main screen of the feature.
|
||||
|
||||
### Set identity server screen
|
||||
|
||||
This screen is a form to set a new identity server URL
|
||||
|
||||
## Ref:
|
||||
- https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4 is a good summary of the role of an Identity server and the proper way to configure and use it in respect to the privacy and the consent of the user.
|
||||
- API documentation: https://matrix.org/docs/spec/identity_service/latest
|
||||
- vector.im TOS: https://vector.im/identity-server-privacy-notice
|
@@ -59,6 +59,12 @@ It's recommended to run tests using an Android Emulator and not a real device. F
|
||||
|
||||
You can run all the tests in the `androidTest` folders.
|
||||
|
||||
It can be done using this command:
|
||||
|
||||
```bash
|
||||
./gradlew vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
|
||||
```
|
||||
|
||||
## Stop Synapse
|
||||
|
||||
To stop Synapse, you can run the following commands:
|
||||
|
@@ -38,10 +38,10 @@ When the client receives the new information, it immediately sends another reque
|
||||
This effectively emulates a server push feature.
|
||||
|
||||
The HTTP long Polling can be fine tuned in the **SDK** using two parameters:
|
||||
* timout (Sync request timeout)
|
||||
* timeout (Sync request timeout)
|
||||
* delay (Delay between each sync)
|
||||
|
||||
**timeout** is a server paramter, defined by:
|
||||
**timeout** is a server parameter, defined by:
|
||||
```
|
||||
The maximum time to wait, in milliseconds, before returning this request.`
|
||||
If no events (or other data) become available before this time elapses, the server will return a response with empty fields.
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver.
|
||||
|
||||
## Sign up flows
|
||||
## Sign in flows
|
||||
|
||||
### Get the flow
|
||||
|
||||
@@ -57,8 +57,8 @@ We get credential (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@benoit0816:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg",
|
||||
"user_id": "@alice:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lr",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "GTVREDALBF",
|
||||
"well_known": {
|
||||
@@ -117,7 +117,7 @@ We get the credentials (200)
|
||||
```json
|
||||
{
|
||||
"user_id": "@alice:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNjtDY0MwRlNPSFFoOC5wOgowMDJmc2lnbmF0dXJlIGiTRm1mYLLxQywxOh3qzQVT8HoEorSokEP2u-bAwtnYCg",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3Jnfrfdegfszsefddvf",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "WBSREDASND",
|
||||
"well_known": {
|
||||
@@ -128,6 +128,8 @@ We get the credentials (200)
|
||||
}
|
||||
```
|
||||
|
||||
It's worth noting that the response from the homeserver contains the userId of Alice.
|
||||
|
||||
### Login with Msisdn
|
||||
|
||||
Not supported yet in RiotX
|
||||
@@ -143,12 +145,59 @@ Not supported yet in RiotX
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.sso"
|
||||
},
|
||||
{
|
||||
"type": "m.login.token"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In this case, the user can click on "Sign in with SSO" and the web screen will be displayed on the page `https://homeserver.with.sso/_matrix/static/client/login/` and the credentials will be passed back to the native code through the JS bridge
|
||||
In this case, the user can click on "Sign in with SSO" and the native web browser, or a ChromeCustomTab if the device supports it, will be launched on the page
|
||||
|
||||
> https://homeserver.with.sso/_matrix/client/r0/login/sso/redirect?redirectUrl=riotx%3A%2F%2Friotx
|
||||
|
||||
The parameter `redirectUrl` is set to `riotx://riotx`.
|
||||
|
||||
ChromeCustomTabs are an intermediate way to display a WebPage, between a WebView and using the external browser. More info can be found [here](https://developer.chrome.com/multidevice/android/customtabs)
|
||||
|
||||
The browser will then take care of the SSO login, which may include creating a third party account, entering an email, settings a display name, or any other possibilities.
|
||||
|
||||
During the process, user may be asked to validate an email by clicking on a link it contains. The link has to be opened in the browser which initiates the authentication. This is why we cannot use WebView anymore.
|
||||
|
||||
Once the process is finished, the web page will call the `redirectUrl` with an extra parameter `loginToken`
|
||||
|
||||
> riotx://riotx?loginToken=MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy
|
||||
|
||||
This navigation is intercepted by RiotX by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
|
||||
|
||||
> curl -X POST --data $'{"type":"m.login.token","token":"MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"}' 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "m.login.token",
|
||||
"token": "MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"
|
||||
}
|
||||
```
|
||||
|
||||
We get the credentials (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@alice:homeserver.with.sso",
|
||||
"access_token": "MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVyIGtleQowMDEwY2lkIGdlbiA9IDEKMDAyY2NpZCB1c2",
|
||||
"home_server": "homeserver.with.sso",
|
||||
"device_id": "DETBTVAHCH",
|
||||
"well_known": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https:\/\/homeserver.with.sso\/"
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": "https:\/\/vector.im"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reset password
|
||||
|
||||
|
424
docs/voip_signaling.md
Normal file
424
docs/voip_signaling.md
Normal file
@@ -0,0 +1,424 @@
|
||||
Useful links:
|
||||
- https://codelabs.developers.google.com/codelabs/webrtc-web/#0
|
||||
|
||||
|
||||
╔════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║A] Placing a call offer ║
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════╝
|
||||
|
||||
|
||||
|
||||
┌───────────────┐
|
||||
│ Matrix │
|
||||
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
│
|
||||
│
|
||||
│
|
||||
┌─────────────────┐ │ ┌───────────────────────────┐ ┌─────────────────┐
|
||||
│ Caller │ │ Signaling Room │ │ │ Callee │
|
||||
└─────────────────┘ │ ├───────────────────────────┤ └─────────────────┘
|
||||
┌────┐ │ │ │
|
||||
│ 3 │ │ │ ┌────────────────────┐ │
|
||||
┌─────────────────┐──────┴────┴──────────────────────────┼─▶│ m.call.invite │ │ │ ┌─────────────────┐
|
||||
│ │ │ │ │ mx event │ │ │ │
|
||||
│ │ │ └────────────────────┘ │ │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ Riot.im │ │ │ │ │ Riot.im │
|
||||
┌──│ App │ │ │ │ │ App │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ └─────────────────┘ │ │ │ └─────────────────┘
|
||||
┌────┤ ▲ │ │ │
|
||||
│ 1 │ ├────┐ │ └───────────────────────────┘
|
||||
└────┤ │ 2 │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||
│ ┌──┴────┴─────────┐ ┌─────────────────┐
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ WebRtc │ │ WebRtc │
|
||||
└─▶│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
┌────┐
|
||||
│ 1 │ The Caller app get access to system resources (camera, mic), eventually stun/turn servers, define some
|
||||
└────┘ constrains (video quality, format) and pass it to WebRtc in order to create a Peer Call offer
|
||||
|
||||
┌────┐
|
||||
│ 2 │ The WebRtc layer creates a call Offer (sdp) that needs to be sent to callee
|
||||
└────┘
|
||||
|
||||
┌────┐ The app layer, takes the webrtc offer, encapsulate it in a matrix event adds a callId and send it to the other
|
||||
│ 3 │ user via the room
|
||||
└────┘
|
||||
┌──────────────┐
|
||||
│ mx event │
|
||||
├──────────────┴────────┐
|
||||
│ type: m.call.invite │
|
||||
│ + callId │
|
||||
│ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ webrtc sdp │ │
|
||||
│ └──────────────────┘ │
|
||||
└───────────────────────┘
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╔════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║B] Sending connection establishment info ║
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════╝
|
||||
|
||||
|
||||
|
||||
┌───────────────┐
|
||||
│ Matrix │
|
||||
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
│
|
||||
│
|
||||
│
|
||||
┌─────────────────┐ │ ┌───────────────────────────┐ ┌─────────────────┐
|
||||
│ Caller │ │ Signaling Room │ │ │ Callee │
|
||||
└─────────────────┘ │ ├───────────────────────────┤ └─────────────────┘
|
||||
│ ┌────────────────────┐ │ │
|
||||
│ │ │ m.call.invite │ │
|
||||
┌─────────────────┐ │ │ mx event │ │ │ ┌─────────────────┐
|
||||
│ │ ┌────┐ │ │ └────────────────────┘ │ │ │
|
||||
│ │ │ 3 │ │ ┌────────────────────┐ │ │ │ │
|
||||
│ │──────┴────┴───────┼──────────────────┼─▶│ m.call.candidates │ │ │ │
|
||||
│ Riot.im │ │ │ mx event │ │ │ │ Riot.im │
|
||||
│ App │ │ │ └────────────────────┘ │ │ App │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
└─────────────────┘ │ │ │ └─────────────────┘
|
||||
▲ │ │ │
|
||||
├────┐ │ └───────────────────────────┘
|
||||
│ 2 │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||
┌───────┴────┴────┐ ┌─────────────────┐
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ WebRtc │ ┌───────────────┐ │ WebRtc │
|
||||
│ │ │ Stun / Turn │ │ │
|
||||
│ │ ├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │
|
||||
│ │ │ │ │
|
||||
└─────────────────┘ │ └─────────────────┘
|
||||
▲ │
|
||||
│ │
|
||||
└──────────┬────┬───────────▶ │
|
||||
┌───────────────┐ │ 1 │ │
|
||||
│ │ └────┘ │
|
||||
│ Network Stack │ │
|
||||
│ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||
│ │
|
||||
└───────────────┘
|
||||
|
||||
|
||||
|
||||
|
||||
┌────┐
|
||||
│ 1 │ The WebRtc layer gathers information on how it can be reach by the other peer directly (Ice candidates)
|
||||
└────┘
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│candidate:1 1 tcp 1518149375 127.0.0.1 35990 typ host │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│candidate:2 1 UDP 2130706431 192.168.1.102 1816 typ host │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌────┐
|
||||
│ 2 │ The WebRTC layer notifies the App layer when it finds new Ice Candidates
|
||||
└────┘
|
||||
|
||||
|
||||
|
||||
┌────┐ The app layer, takes the ice candidates, encapsulate them in one or several matrix event adds the callId and
|
||||
│ 3 │ send it to the other user via the room
|
||||
└────┘
|
||||
┌──────────────┐
|
||||
│ mx event │
|
||||
├──────────────┴────────────────────────┐
|
||||
│ type: m.call.candidates │
|
||||
│ │
|
||||
│ +CallId │
|
||||
│ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ice candidate sdp │ │
|
||||
│ └──────────────────┘ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ice candidate sdp │ │
|
||||
│ └──────────────────┘ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ice candidate sdp │ │
|
||||
│ └──────────────────┘ │
|
||||
└───────────────────────────────────────┘
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╔════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║C] Receiving a call offer ║
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════╝
|
||||
|
||||
|
||||
|
||||
┌───────────────┐
|
||||
│ Matrix │
|
||||
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
│
|
||||
│ ┌─────────────────┐
|
||||
│ │ Callee │
|
||||
┌─────────────────┐ │ ┌───────────────────────────┐ └─────────────────┘
|
||||
│ Caller │ │ Signaling Room │ │
|
||||
└─────────────────┘ │ ├───────────────────────────┤
|
||||
│ ┌────────────────────┐ │ │ ┌─────────────────┐
|
||||
│ │ │ m.call.invite │───┼────────────────────────────┬────┬───▶│ │
|
||||
┌─────────────────┐ │ │ mx event │ │ │ │ 1 │ │ │
|
||||
│ │ │ │ └────────────────────┘ │ └────┘ │ │
|
||||
│ │ │ ┌────────────────────┐ │ │ │ Riot.im │
|
||||
│ │ │ │ │ m.call.candidates │ │ │ App │
|
||||
│ Riot.im │ │ │ mx event │ │ │ │ │
|
||||
│ App │ │ │ └────────────────────┘ │ │ │
|
||||
│ │ │ ┌────────────────────┐◀──┼─────────────────┼───┬────┬───────────┤ │
|
||||
│ │◀──────────────────┼──────────────────┼──│ m.call.answer │ │ │ 4 │ └──┬──────────────┘
|
||||
│ │ │ │ mx event │ │ │ └────┘ ├────┐ ▲
|
||||
└────┬────────────┘ │ │ └────────────────────┘ │ │ 2 │ ├────┐
|
||||
│ │ │ │ ├────┘ │ 3 │
|
||||
│ │ └───────────────────────────┘ ┌──▼─────────┴────┤
|
||||
┌────▼────────────┐ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │
|
||||
│ │ │ │
|
||||
│ │ │ WebRtc │
|
||||
│ WebRtc │ │ ┌──┴─────────────────┐
|
||||
│ │ │ │ caller offer │
|
||||
┌──────────┴─────────┐ │ │ └──┬─────────────────┘
|
||||
│ callee answer │ │ └─────────────────┘
|
||||
└────────────────────┴───────┘
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
┌────┐
|
||||
│ 1 │ Bob receives a call.invite event in a room, then creates a WebRTC peer connection object
|
||||
└────┘
|
||||
|
||||
┌────┐
|
||||
│ 2 │ The encapsulated call offer sdp from the mx event is transmitted to WebRTC
|
||||
└────┘
|
||||
|
||||
┌────┐
|
||||
│ 3 │ WebRTC then creates a call answer for the offer and send it back to app layer
|
||||
└────┘
|
||||
|
||||
|
||||
┌────┐ The app layer, takes the webrtc answer, encapsulate it in a matrix event adds a callId and send it to the
|
||||
│ 3 │ other user via the room
|
||||
└────┘
|
||||
┌──────────────┐
|
||||
│ mx event │
|
||||
├──────────────┴────────┐
|
||||
│ type: m.call.answer │
|
||||
│ + callId │
|
||||
│ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ webrtc sdp │ │
|
||||
│ └──────────────────┘ │
|
||||
└───────────────────────┘
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╔════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║D] Callee sends connection establishment info ║
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════╝
|
||||
|
||||
|
||||
|
||||
┌───────────────┐
|
||||
│ Matrix │
|
||||
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
│
|
||||
│
|
||||
│
|
||||
┌─────────────────┐ │ ┌───────────────────────────┐ ┌─────────────────┐
|
||||
│ Caller │ │ Signaling Room │ │ │ Callee │
|
||||
└─────────────────┘ │ ├───────────────────────────┤ └─────────────────┘
|
||||
│ ┌────────────────────┐ │ │
|
||||
│ │ │ m.call.invite │ │
|
||||
┌─────────────────┐ │ │ mx event │ │ │ ┌─────────────────┐
|
||||
│ │ │ │ └────────────────────┘ │ │ │
|
||||
│ │ │ ┌────────────────────┐ │ │ │ │
|
||||
│ │ │ │ │ m.call.candidates │ │ │ │
|
||||
│ Riot.im │ │ │ mx event │ │ │ │ Riot.im │
|
||||
│ App │ │ │ └────────────────────┘ │ ┌────┐ │ App │
|
||||
│ │ │ ┌────────────────────┐ │ │ │ 3 │ │ │
|
||||
│ │◀──────────────────┼┐ │ │ m.call.answer │ │ ┌───────┴────┴────────│ │
|
||||
│ │ │ │ │ mx event │ │ ││ │ │
|
||||
└─────────────────┘ ││ │ └────────────────────┘ │ │ └─────────────────┘
|
||||
│ │ │ ┌────────────────────┐ │ ││ ▲
|
||||
│ │└─────────────────┼──│ m.call.candidates │ │ │ ├────┐
|
||||
▼ │ │ mx event │◀──┼────────────────┘│ │ 2 │
|
||||
┌─────────────────┐ │ │ └────────────────────┘ │ ┌────┴────┴───────┐
|
||||
│ │ └───────────────────────────┘ │ │ │
|
||||
│ │ │ │ │
|
||||
│ WebRtc │ │ │ WebRtc │
|
||||
│ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ┌───┴────────────────┐
|
||||
│ │ │ │ caller offer │
|
||||
┌────────┴───────────┐ │ │ └───┬────────────────┘
|
||||
│ callee answer ├─────┘ ┌───────────────┐ └─────────────────┘
|
||||
├────────────────────┤ │ Stun / Turn │ ▲
|
||||
│ callee ice │ ├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ┌────┐ │
|
||||
│ candidates │ │ │ 1 │ │
|
||||
└────────────────────┘ │ ├────┴──┴───────┐
|
||||
│ │ │
|
||||
│ │ Network Stack │
|
||||
│◀─────────────────────┤ │
|
||||
│ │ │
|
||||
│ └───────────────┘
|
||||
│
|
||||
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||
|
||||
|
||||
┌────┐
|
||||
│ 1 │ The WebRtc layer gathers information on how it can be reach by the other peer directly (Ice candidates)
|
||||
└────┘
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│candidate:1 1 tcp 1518149375 127.0.0.1 35990 typ host │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│candidate:2 1 UDP 2130706431 192.168.1.102 1816 typ host │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌────┐
|
||||
│ 2 │ The WebRTC layer notifies the App layer when it finds new Ice Candidates
|
||||
└────┘
|
||||
|
||||
|
||||
|
||||
┌────┐ The app layer, takes the ice candidates, encapsulate them in one or several matrix event adds the callId and
|
||||
│ 3 │ send it to the other user via the room
|
||||
└────┘
|
||||
┌──────────────┐
|
||||
│ mx event │
|
||||
├──────────────┴────────────────────────┐
|
||||
│ type: m.call.candidates │
|
||||
│ │
|
||||
│ +CallId │
|
||||
│ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ice candidate sdp │ │
|
||||
│ └──────────────────┘ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ice candidate sdp │ │
|
||||
│ └──────────────────┘ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ice candidate sdp │ │
|
||||
│ └──────────────────┘ │
|
||||
└───────────────────────────────────────┘
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╔════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║D] Caller Callee connection ║
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════╝
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
┌───────────────┐
|
||||
┌─────────────────┐ │ Matrix │ ┌─────────────────┐
|
||||
│ Caller │ ├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Callee │
|
||||
└─────────────────┘ │ └─────────────────┘
|
||||
│
|
||||
│
|
||||
┌─────────────────┐ │ ┌─────────────────┐
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ Riot.im │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Riot.im │
|
||||
│ App │ │ App │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
┌───────────────┐
|
||||
│ Internet │
|
||||
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
┌─────────────────┐ │ ┌─────────────────┐
|
||||
│ │ │ │ │
|
||||
│ ├───────────────────────────────────────────────────────────────────────────────────┴─────────────────────┤ │
|
||||
│ WebRtc │█████████████████████████████████████████████████████████████████████████████████████████████████████████│ WebRtc │
|
||||
┌─────────────┴──────┐ ├────────────────────────────────────────┬──────────────────────────┬───────────────┬─────────────────────┤ ┌─────┴──────────────┐
|
||||
│ callee answer │ │ │ │ Video / Audio Stream │ │ │ caller offer │
|
||||
├────────────────────┤ │ └──────────────────────────┘ │ │ ├────────────────────┤
|
||||
│ callee ice ├──────────┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ └───────────┤ caller ice │
|
||||
│ candidates │ │ candidates │
|
||||
└────────────────────┘ └────────────────────┘
|
||||
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ │░
|
||||
│ If connection is impossible (firewall), and a turn │░
|
||||
│server is available, connection could happen through │░
|
||||
│ a relay │░
|
||||
│ │░
|
||||
└─────────────────────────────────────────────────────┘░
|
||||
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
||||
|
||||
|
||||
┌───────────────┐
|
||||
│ Internet │
|
||||
└─┬─────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
┌─────────────────┐ │ ┌─────────────────┐
|
||||
│ │ │ ┌─────────────────────────┐ │ │
|
||||
│ ├───────────────────────────────────────┐│ │ │ │ │
|
||||
│ WebRtc │███████████████████████████████████████││ │ │ WebRtc │
|
||||
│ ├───────────────────────────────────────┘│ │ │ │ │
|
||||
│ │ ┌────────┴─────────────────┐ │ Relay │┌─────────────────────────────────────┤ │
|
||||
┌───────────────┴────┐ │ │ Video / Audio Stream │ │ ││█████████████████████████████████████│ ┌───────┴────────────┐
|
||||
│ callee answer ├────────────┘ └────────┬─────────────────┘ │ │└─────────────────────────────────────┴─────────┤ caller offer │
|
||||
├────────────────────┤ │ │ │ ├────────────────────┤
|
||||
│ callee ice │ │ │ │ │ caller ice │
|
||||
│ candidates │ └─────────────────────────┘ │ │ candidates │
|
||||
└────────────────────┘ │ └────────────────────┘
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
@@ -8,7 +8,7 @@
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
org.gradle.jvmargs=-Xmx8192m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Fri Sep 27 10:10:35 CEST 2019
|
||||
#Thu Jul 02 12:33:07 CEST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
|
@@ -6,7 +6,7 @@ android {
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
@@ -16,11 +16,14 @@
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import android.net.Uri
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||
@@ -28,6 +31,7 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
@@ -60,7 +64,7 @@ class RxRoom(private val room: Room) {
|
||||
}
|
||||
}
|
||||
|
||||
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
|
||||
fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||
.startWithCallable {
|
||||
room.getStateEvent(eventType, stateKey).toOptional()
|
||||
@@ -95,6 +99,34 @@ class RxRoom(private val room: Room) {
|
||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||
return room.getLiveRoomNotificationState().asObservable()
|
||||
}
|
||||
|
||||
fun invite(userId: String, reason: String? = null): Completable = completableBuilder<Unit> {
|
||||
room.invite(userId, reason, it)
|
||||
}
|
||||
|
||||
fun updateTopic(topic: String): Completable = completableBuilder<Unit> {
|
||||
room.updateTopic(topic, it)
|
||||
}
|
||||
|
||||
fun updateName(name: String): Completable = completableBuilder<Unit> {
|
||||
room.updateName(name, it)
|
||||
}
|
||||
|
||||
fun addRoomAlias(alias: String): Completable = completableBuilder<Unit> {
|
||||
room.addRoomAlias(alias, it)
|
||||
}
|
||||
|
||||
fun updateCanonicalAlias(alias: String): Completable = completableBuilder<Unit> {
|
||||
room.updateCanonicalAlias(alias, it)
|
||||
}
|
||||
|
||||
fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder<Unit> {
|
||||
room.updateHistoryReadability(readability, it)
|
||||
}
|
||||
|
||||
fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder<Unit> {
|
||||
room.updateAvatar(avatarUri, fileName, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun Room.rx(): RxRoom {
|
||||
|
@@ -17,20 +17,25 @@
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
@@ -51,10 +56,17 @@ class RxSession(private val session: Session) {
|
||||
}
|
||||
}
|
||||
|
||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||
return session.getBreadcrumbsLive().asObservable()
|
||||
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||
return session.getBreadcrumbsLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
session.getBreadcrumbs()
|
||||
session.getBreadcrumbs(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveMyDeviceInfo(): Observable<List<DeviceInfo>> {
|
||||
return session.cryptoService().getLiveMyDevicesInfo().asObservable()
|
||||
.startWithCallable {
|
||||
session.cryptoService().getMyDevicesInfo()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +93,13 @@ class RxSession(private val session: Session) {
|
||||
return session.getIgnoredUsersLive().asObservable()
|
||||
}
|
||||
|
||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||
return session.getPagedUsersLive(filter).asObservable()
|
||||
fun livePagedUsers(filter: String? = null, excludedUserIds: Set<String>? = null): Observable<PagedList<User>> {
|
||||
return session.getPagedUsersLive(filter, excludedUserIds).asObservable()
|
||||
}
|
||||
|
||||
fun liveThreePIds(refreshData: Boolean): Observable<List<ThreePid>> {
|
||||
return session.getThreePidsLive(refreshData).asObservable()
|
||||
.startWithCallable { session.getThreePids() }
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||
@@ -123,12 +140,31 @@ class RxSession(private val session: Session) {
|
||||
}
|
||||
}
|
||||
|
||||
fun liveCrossSigningPrivateKeys(): Observable<Optional<PrivateKeysInfo>> {
|
||||
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asObservable()
|
||||
.startWithCallable {
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
|
||||
return session.getLiveAccountDataEvents(types).asObservable()
|
||||
.startWithCallable {
|
||||
session.getAccountDataEvents(types)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveRoomWidgets(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue,
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): Observable<List<Widget>> {
|
||||
return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asObservable()
|
||||
.startWithCallable {
|
||||
session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Session.rx(): RxSession {
|
||||
|
@@ -23,7 +23,7 @@ android {
|
||||
testOptions.unitTests.includeAndroidResources = true
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "0.0.1"
|
||||
@@ -31,10 +31,23 @@ android {
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// The following argument makes the Android Test Orchestrator run its
|
||||
// "pm clear" command after each test invocation. This command ensures
|
||||
// that the app's state is completely cleared between tests.
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
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()}\""
|
||||
|
||||
defaultConfig {
|
||||
consumerProguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -49,9 +62,6 @@ android {
|
||||
release {
|
||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
||||
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +81,15 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
androidTest {
|
||||
java.srcDirs += "src/sharedTest/java"
|
||||
}
|
||||
test {
|
||||
java.srcDirs += "src/sharedTest/java"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static def gitRevision() {
|
||||
@@ -104,6 +123,7 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.core:core-ktx:1.1.0"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
@@ -149,6 +169,13 @@ dependencies {
|
||||
// Bus
|
||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
|
||||
|
||||
// Web RTC
|
||||
// TODO meant for development purposes only. See http://webrtc.github.io/webrtc-org/native-code/android/
|
||||
implementation 'org.webrtc:google-webrtc:1.0.+'
|
||||
|
||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||
@@ -160,7 +187,10 @@ dependencies {
|
||||
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
|
||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
// Plant Timber tree for test
|
||||
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||
|
||||
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
@@ -171,5 +201,9 @@ dependencies {
|
||||
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
|
||||
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
|
||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
// Plant Timber tree for test
|
||||
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.2.0'
|
||||
|
||||
}
|
||||
|
61
matrix-sdk-android/proguard-rules.pro
vendored
61
matrix-sdk-android/proguard-rules.pro
vendored
@@ -19,3 +19,64 @@
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
|
||||
### EVENT BUS ###
|
||||
|
||||
-keepattributes *Annotation*
|
||||
-keepclassmembers class * {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
### MOSHI ###
|
||||
|
||||
# JSR 305 annotations are for embedding nullability information.
|
||||
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@com.squareup.moshi.* <methods>;
|
||||
}
|
||||
|
||||
-keep @com.squareup.moshi.JsonQualifier interface *
|
||||
|
||||
# Enum field names are used by the integrated EnumJsonAdapter.
|
||||
# values() is synthesized by the Kotlin compiler and is used by EnumJsonAdapter indirectly
|
||||
# Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi.
|
||||
-keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum {
|
||||
<fields>;
|
||||
**[] values();
|
||||
}
|
||||
|
||||
-keep class kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoaderImpl
|
||||
|
||||
-keepclassmembers class kotlin.Metadata {
|
||||
public <methods>;
|
||||
}
|
||||
|
||||
### OKHTTP for Android Studio ###
|
||||
-keep class okhttp3.Headers { *; }
|
||||
-keep interface okhttp3.Interceptor.* { *; }
|
||||
|
||||
### OLM JNI ###
|
||||
-keep class org.matrix.olm.** { *; }
|
||||
|
||||
### Webrtc
|
||||
-keep class org.webrtc.** { *; }
|
||||
|
||||
### Serializable persisted classes
|
||||
# https://www.guardsquare.com/en/products/proguard/manual/examples#serializable
|
||||
-keepnames class * implements java.io.Serializable
|
||||
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||
!static !transient <fields>;
|
||||
!private <fields>;
|
||||
!private <methods>;
|
||||
private void writeObject(java.io.ObjectOutputStream);
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
@@ -18,10 +18,15 @@ package im.vector.matrix.android
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import im.vector.matrix.android.test.shared.createTimberTestRule
|
||||
import org.junit.Rule
|
||||
import java.io.File
|
||||
|
||||
interface InstrumentedTest {
|
||||
|
||||
@Rule
|
||||
fun timberTestRule() = createTimberTestRule()
|
||||
|
||||
fun context(): Context {
|
||||
return ApplicationProvider.getApplicationContext()
|
||||
}
|
||||
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.account
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.failure.isInvalidPassword
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class ChangePasswordTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
companion object {
|
||||
private const val NEW_PASSWORD = "this is a new password"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun changePasswordTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
|
||||
|
||||
// Change password
|
||||
commonTestHelper.doSync<Unit> {
|
||||
session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD, it)
|
||||
}
|
||||
|
||||
// Try to login with the previous password, it will fail
|
||||
val throwable = commonTestHelper.logAccountWithError(session.myUserId, TestConstants.PASSWORD)
|
||||
throwable.isInvalidPassword().shouldBeTrue()
|
||||
|
||||
// Try to login with the new password, should work
|
||||
val session2 = commonTestHelper.logIntoAccount(session.myUserId, NEW_PASSWORD, SessionTestParams(withInitialSync = false))
|
||||
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
commonTestHelper.signOutAndClose(session2)
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.account
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.common.TestMatrixCallback
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class DeactivateAccountTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
@Test
|
||||
fun deactivateAccountTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
|
||||
|
||||
// Deactivate the account
|
||||
commonTestHelper.doSync<Unit> {
|
||||
session.deactivateAccount(TestConstants.PASSWORD, false, it)
|
||||
}
|
||||
|
||||
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
|
||||
val throwable = commonTestHelper.logAccountWithError(session.myUserId, TestConstants.PASSWORD)
|
||||
|
||||
// Test the error
|
||||
assertTrue(throwable is Failure.ServerError
|
||||
&& throwable.error.code == MatrixError.M_USER_DEACTIVATED
|
||||
&& throwable.error.message == "This account has been deactivated")
|
||||
|
||||
// Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE)
|
||||
val hs = commonTestHelper.createHomeServerConfig()
|
||||
|
||||
commonTestHelper.doSync<LoginFlowResult> {
|
||||
commonTestHelper.matrix.authenticationService.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
var accountCreationError: Throwable? = null
|
||||
commonTestHelper.waitWithLatch {
|
||||
commonTestHelper.matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(session.myUserId.substringAfter("@").substringBefore(":"),
|
||||
TestConstants.PASSWORD,
|
||||
null,
|
||||
object : TestMatrixCallback<RegistrationResult>(it, false) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
accountCreationError = failure
|
||||
super.onFailure(failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test the error
|
||||
accountCreationError.let {
|
||||
assertTrue(it is Failure.ServerError
|
||||
&& it.error.code == MatrixError.M_USER_IN_USE)
|
||||
}
|
||||
|
||||
// No need to close the session, it has been deactivated
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.common.DaggerTestMatrixComponent
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This is the main entry point to the matrix sdk.
|
||||
* To get the singleton instance, use getInstance static method.
|
||||
*/
|
||||
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@Inject internal lateinit var olmManager: OlmManager
|
||||
@Inject internal lateinit var sessionManager: SessionManager
|
||||
|
||||
init {
|
||||
Monarchy.init(context)
|
||||
DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
||||
if (context.applicationContext !is Configuration.Provider) {
|
||||
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
|
||||
}
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
}
|
||||
|
||||
fun getUserAgent() = userAgentHolder.userAgent
|
||||
|
||||
fun authenticationService(): AuthenticationService {
|
||||
return authenticationService
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private lateinit var instance: Matrix
|
||||
private val isInit = AtomicBoolean(false)
|
||||
|
||||
fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
instance = Matrix(context.applicationContext, matrixConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstance(context: Context): Matrix {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
val appContext = context.applicationContext
|
||||
if (appContext is MatrixConfiguration.Provider) {
|
||||
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
|
||||
instance = Matrix(appContext, matrixConfiguration)
|
||||
} else {
|
||||
throw IllegalStateException("Matrix is not initialized properly." +
|
||||
" You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
|
||||
}
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
fun getSdkVersion(): String {
|
||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
}
|
||||
|
||||
fun decryptStream(inputStream: InputStream?, elementToDecrypt: ElementToDecrypt): InputStream? {
|
||||
return MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
}
|
||||
}
|
||||
}
|
@@ -28,10 +28,10 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
@@ -57,9 +57,10 @@ class CommonTestHelper(context: Context) {
|
||||
|
||||
val matrix: Matrix
|
||||
|
||||
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
|
||||
|
||||
init {
|
||||
Matrix.initialize(context, MatrixConfiguration("TestFlavor"))
|
||||
|
||||
matrix = Matrix.getInstance(context)
|
||||
}
|
||||
|
||||
@@ -88,7 +89,8 @@ class CommonTestHelper(context: Context) {
|
||||
fun syncSession(session: Session) {
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
session.open()
|
||||
GlobalScope.launch(Dispatchers.Main) { session.open() }
|
||||
|
||||
session.startSync(true)
|
||||
|
||||
val syncLiveData = runBlocking(Dispatchers.Main) {
|
||||
@@ -115,8 +117,9 @@ class CommonTestHelper(context: Context) {
|
||||
* @param nbOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(nbOfMessages)
|
||||
val latch = CountDownLatch(1)
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
@@ -127,28 +130,29 @@ class CommonTestHelper(context: Context) {
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val newMessages = snapshot
|
||||
.filter { LocalEcho.isLocalEchoId(it.eventId).not() }
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
|
||||
if (newMessages.size == nbOfMessages) {
|
||||
sentEvents.addAll(newMessages)
|
||||
// Remove listener now, if not at the next update sendEvents could change
|
||||
timeline.removeListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
timeline.start()
|
||||
timeline.addListener(timelineListener)
|
||||
for (i in 0 until nbOfMessages) {
|
||||
room.sendTextMessage(message + " #" + (i + 1))
|
||||
}
|
||||
await(latch)
|
||||
timeline.removeListener(timelineListener)
|
||||
// Wait 3 second more per message
|
||||
await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages)
|
||||
timeline.dispose()
|
||||
|
||||
// Check that all events has been created
|
||||
assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong())
|
||||
assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong())
|
||||
|
||||
return sentEvents
|
||||
}
|
||||
@@ -183,9 +187,9 @@ class CommonTestHelper(context: Context) {
|
||||
* @param testParams test params about the session
|
||||
* @return the session associated with the existing account
|
||||
*/
|
||||
private fun logIntoAccount(userId: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams): Session {
|
||||
fun logIntoAccount(userId: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams): Session {
|
||||
val session = logAccountAndSync(userId, password, testParams)
|
||||
assertNotNull(session)
|
||||
return session
|
||||
@@ -260,14 +264,63 @@ class CommonTestHelper(context: Context) {
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Log into the account and expect an error
|
||||
*
|
||||
* @param userName the account username
|
||||
* @param password the password
|
||||
*/
|
||||
fun logAccountWithError(userName: String,
|
||||
password: String): Throwable {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
var requestFailure: Throwable? = null
|
||||
waitWithLatch { latch ->
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice", object : TestMatrixCallback<Session>(latch, onlySuccessful = false) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
requestFailure = failure
|
||||
super.onFailure(failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
assertNotNull(requestFailure)
|
||||
return requestFailure!!
|
||||
}
|
||||
|
||||
fun createEventListener(latch: CountDownLatch, predicate: (List<TimelineEvent>) -> Boolean): Timeline.Listener {
|
||||
return object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
if (predicate(snapshot)) {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Await for a latch and ensure the result is true
|
||||
*
|
||||
* @param latch
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
fun await(latch: CountDownLatch, timout: Long? = TestConstants.timeOutMillis) {
|
||||
assertTrue(latch.await(timout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||
fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) {
|
||||
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
|
||||
fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
|
||||
@@ -282,10 +335,10 @@ class CommonTestHelper(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun waitWithLatch(timout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
|
||||
fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
|
||||
val latch = CountDownLatch(1)
|
||||
block(latch)
|
||||
await(latch, timout)
|
||||
await(latch, timeout)
|
||||
}
|
||||
|
||||
// Transform a method with a MatrixCallback to a synchronous method
|
||||
@@ -318,3 +371,13 @@ class CommonTestHelper(context: Context) {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun List<TimelineEvent>.checkSendOrder(baseTextMessage: String, numberOfMessages: Int, startIndex: Int): Boolean {
|
||||
return drop(startIndex)
|
||||
.take(numberOfMessages)
|
||||
.foldRightIndexed(true) { index, timelineEvent, acc ->
|
||||
val body = timelineEvent.root.content.toModel<MessageContent>()?.body
|
||||
val currentMessageSuffix = numberOfMessages - index
|
||||
acc && (body == null || body.startsWith(baseTextMessage) && body.endsWith("#$currentMessageSuffix"))
|
||||
}
|
||||
}
|
||||
|
@@ -17,11 +17,17 @@
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
@@ -33,6 +39,7 @@ import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -41,7 +48,7 @@ import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import java.util.HashMap
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
@@ -54,17 +61,19 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
/**
|
||||
* @return alice session
|
||||
*/
|
||||
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
||||
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
|
||||
}
|
||||
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
if (encryptedRoom) {
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.enableEncryption(callback = it)
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.enableEncryption(callback = it)
|
||||
}
|
||||
}
|
||||
|
||||
return CryptoTestData(aliceSession, roomId)
|
||||
@@ -73,8 +82,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
/**
|
||||
* @return alice and bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom()
|
||||
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
@@ -140,64 +149,38 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
* @return Alice, Bob and Sam session
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData {
|
||||
val statuses = HashMap<String, String>()
|
||||
|
||||
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
val room = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
||||
|
||||
val lock1 = CountDownLatch(2)
|
||||
|
||||
// val samEventListener = object : MXEventListener() {
|
||||
// override fun onNewRoom(roomId: String) {
|
||||
// if (TextUtils.equals(roomId, aliceRoomId)) {
|
||||
// if (!statuses.containsKey("onNewRoom")) {
|
||||
// statuses["onNewRoom"] = "onNewRoom"
|
||||
// lock1.countDown()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// samSession.dataHandler.addListener(samEventListener)
|
||||
|
||||
room.invite(samSession.myUserId, null, object : TestMatrixCallback<Unit>(lock1) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
statuses["invite"] = "invite"
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
|
||||
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
|
||||
|
||||
// samSession.dataHandler.removeListener(samEventListener)
|
||||
|
||||
val lock2 = CountDownLatch(1)
|
||||
|
||||
samSession.joinRoom(aliceRoomId, null, object : TestMatrixCallback<Unit>(lock2) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
statuses["joinRoom"] = "joinRoom"
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock2)
|
||||
assertTrue(statuses.containsKey("joinRoom"))
|
||||
val samSession = createSamAccountAndInviteToTheRoom(room)
|
||||
|
||||
// wait the initial sync
|
||||
SystemClock.sleep(1000)
|
||||
|
||||
// samSession.dataHandler.removeListener(samEventListener)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Sam account and invite him in the room. He will accept the invitation
|
||||
* @Return Sam session
|
||||
*/
|
||||
fun createSamAccountAndInviteToTheRoom(room: Room): Session {
|
||||
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.invite(samSession.myUserId, null, it)
|
||||
}
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
samSession.joinRoom(room.roomId, null, it)
|
||||
}
|
||||
|
||||
return samSession
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alice and Bob sessions
|
||||
*/
|
||||
@@ -266,14 +249,14 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
val eventWireContent = event.content.toContent()
|
||||
assertNotNull(eventWireContent)
|
||||
|
||||
assertNull(eventWireContent.get("body"))
|
||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent.get("algorithm"))
|
||||
assertNull(eventWireContent["body"])
|
||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent["algorithm"])
|
||||
|
||||
assertNotNull(eventWireContent.get("ciphertext"))
|
||||
assertNotNull(eventWireContent.get("session_id"))
|
||||
assertNotNull(eventWireContent.get("sender_key"))
|
||||
assertNotNull(eventWireContent["ciphertext"])
|
||||
assertNotNull(eventWireContent["session_id"])
|
||||
assertNotNull(eventWireContent["sender_key"])
|
||||
|
||||
assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id"))
|
||||
assertEquals(senderSession.sessionParams.deviceId, eventWireContent["device_id"])
|
||||
|
||||
assertNotNull(event.eventId)
|
||||
assertEquals(roomId, event.roomId)
|
||||
@@ -282,7 +265,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
|
||||
val eventContent = event.toContent()
|
||||
assertNotNull(eventContent)
|
||||
assertEquals(clearMessage, eventContent.get("body"))
|
||||
assertEquals(clearMessage, eventContent["body"])
|
||||
assertEquals(senderSession.myUserId, event.senderId)
|
||||
}
|
||||
|
||||
@@ -299,4 +282,141 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
authData = createFakeMegolmBackupAuthData()
|
||||
)
|
||||
}
|
||||
|
||||
fun createDM(alice: Session, bob: Session): String {
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
alice.createRoom(
|
||||
CreateRoomParams(invitedUserIds = listOf(bob.myUserId))
|
||||
.setDirectMessage()
|
||||
.enableEncryptionIfInvitedUsersSupportIt(),
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||
bob.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
}
|
||||
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
val indexOfFirst = t?.indexOfFirst { it.roomId == roomId } ?: -1
|
||||
if (indexOfFirst != -1) {
|
||||
latch.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||
bob.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
}
|
||||
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (bob.getRoom(roomId)
|
||||
?.getRoomMember(bob.myUserId)
|
||||
?.membership == Membership.JOIN) {
|
||||
latch.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
|
||||
mTestHelper.doSync<Unit> { bob.joinRoom(roomId, callback = it) }
|
||||
}
|
||||
|
||||
return roomId
|
||||
}
|
||||
|
||||
fun initializeCrossSigning(session: Session) {
|
||||
mTestHelper.doSync<Unit> {
|
||||
session.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = session.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), it)
|
||||
}
|
||||
}
|
||||
|
||||
fun verifySASCrossSign(alice: Session, bob: Session, roomId: String) {
|
||||
assertTrue(alice.cryptoService().crossSigningService().canCrossSign())
|
||||
assertTrue(bob.cryptoService().crossSigningService().canCrossSign())
|
||||
|
||||
val requestID = UUID.randomUUID().toString()
|
||||
val aliceVerificationService = alice.cryptoService().verificationService()
|
||||
val bobVerificationService = bob.cryptoService().verificationService()
|
||||
|
||||
aliceVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID,
|
||||
roomId,
|
||||
bob.myUserId,
|
||||
bob.sessionParams.credentials.deviceId!!,
|
||||
null)
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
var alicePovTx: OutgoingSasVerificationTransaction? = null
|
||||
var bobPovTx: IncomingSasVerificationTransaction? = null
|
||||
|
||||
// wait for alice to get the ready
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
}
|
||||
// wait for alice to get the ready
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
}
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
|
||||
|
||||
bobPovTx!!.userHasVerifiedShortCode()
|
||||
alicePovTx!!.userHasVerifiedShortCode()
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import im.vector.matrix.android.internal.session.TestInterceptor
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Protocol
|
||||
import okhttp3.Request
|
||||
@@ -37,7 +38,7 @@ import javax.net.ssl.HttpsURLConnection
|
||||
* AutoDiscovery().findClientConfig("matrix.org", <callback>)
|
||||
* </code>
|
||||
*/
|
||||
class MockOkHttpInterceptor : Interceptor {
|
||||
class MockOkHttpInterceptor : TestInterceptor {
|
||||
|
||||
private var rules: ArrayList<Rule> = ArrayList()
|
||||
|
||||
@@ -45,6 +46,12 @@ class MockOkHttpInterceptor : Interceptor {
|
||||
rules.add(rule)
|
||||
}
|
||||
|
||||
fun clearRules() {
|
||||
rules.clear()
|
||||
}
|
||||
|
||||
override var sessionId: String? = null
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import android.content.Context
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
import im.vector.matrix.android.internal.auth.AuthModule
|
||||
import im.vector.matrix.android.internal.di.MatrixComponent
|
||||
import im.vector.matrix.android.internal.di.MatrixModule
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import im.vector.matrix.android.internal.di.NetworkModule
|
||||
|
||||
@Component(modules = [TestModule::class, MatrixModule::class, NetworkModule::class, AuthModule::class, TestNetworkModule::class])
|
||||
@MatrixScope
|
||||
internal interface TestMatrixComponent : MatrixComponent {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance context: Context,
|
||||
@BindsInstance matrixConfiguration: MatrixConfiguration): TestMatrixComponent
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import im.vector.matrix.android.internal.di.MatrixComponent
|
||||
|
||||
@Module
|
||||
internal abstract class TestModule {
|
||||
@Binds
|
||||
abstract fun providesMatrixComponent(testMatrixComponent: TestMatrixComponent): MatrixComponent
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.matrix.android.internal.session.MockHttpInterceptor
|
||||
import im.vector.matrix.android.internal.session.TestInterceptor
|
||||
|
||||
@Module
|
||||
internal object TestNetworkModule {
|
||||
|
||||
val interceptors = ArrayList<TestInterceptor>()
|
||||
|
||||
fun interceptorForSession(sessionId: String): TestInterceptor? = interceptors.firstOrNull { it.sessionId == sessionId }
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@MockHttpInterceptor
|
||||
fun providesTestInterceptor(): TestInterceptor? {
|
||||
return MockOkHttpInterceptor().also {
|
||||
interceptors.add(it)
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,6 +20,8 @@ import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||
import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -31,6 +33,7 @@ internal class CryptoStoreHelper {
|
||||
.name("test.realm")
|
||||
.modules(RealmCryptoStoreModule())
|
||||
.build(),
|
||||
crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()),
|
||||
credentials = createCredential())
|
||||
}
|
||||
|
||||
|
@@ -22,10 +22,8 @@ import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import io.realm.Realm
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -45,22 +43,22 @@ class CryptoStoreTest : InstrumentedTest {
|
||||
Realm.init(context())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_metadata_realm_ok() {
|
||||
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||
|
||||
assertFalse(cryptoStore.hasData())
|
||||
|
||||
cryptoStore.open()
|
||||
|
||||
assertEquals("deviceId_sample", cryptoStore.getDeviceId())
|
||||
|
||||
assertTrue(cryptoStore.hasData())
|
||||
|
||||
// Cleanup
|
||||
cryptoStore.close()
|
||||
cryptoStore.deleteStore()
|
||||
}
|
||||
// @Test
|
||||
// fun test_metadata_realm_ok() {
|
||||
// val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||
//
|
||||
// assertFalse(cryptoStore.hasData())
|
||||
//
|
||||
// cryptoStore.open()
|
||||
//
|
||||
// assertEquals("deviceId_sample", cryptoStore.getDeviceId())
|
||||
//
|
||||
// assertTrue(cryptoStore.hasData())
|
||||
//
|
||||
// // Cleanup
|
||||
// cryptoStore.close()
|
||||
// cryptoStore.deleteStore()
|
||||
// }
|
||||
|
||||
@Test
|
||||
fun test_lastSessionUsed() {
|
||||
|
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.tryThis
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
||||
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.olm.OlmSession
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* Ref:
|
||||
* - https://github.com/matrix-org/matrix-doc/pull/1719
|
||||
* - https://matrix.org/docs/spec/client_server/latest#recovering-from-undecryptable-messages
|
||||
* - https://github.com/matrix-org/matrix-js-sdk/pull/780
|
||||
* - https://github.com/matrix-org/matrix-ios-sdk/pull/778
|
||||
* - https://github.com/matrix-org/matrix-ios-sdk/pull/784
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class UnwedgingTest : InstrumentedTest {
|
||||
|
||||
private lateinit var messagesReceivedByBob: List<TimelineEvent>
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
messagesReceivedByBob = emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* - Alice & Bob in a e2e room
|
||||
* - Alice sends a 1st message with a 1st megolm session
|
||||
* - Store the olm session between A&B devices
|
||||
* - Alice sends a 2nd message with a 2nd megolm session
|
||||
* - Simulate Alice using a backup of her OS and make her crypto state like after the first message
|
||||
* - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
|
||||
*
|
||||
* What Bob must see:
|
||||
* -> No issue with the 2 first messages
|
||||
* -> The third event must fail to decrypt at first because Bob the olm session is wedged
|
||||
* -> This is automatically fixed after SDKs restarted the olm session
|
||||
*/
|
||||
@Test
|
||||
fun testUnwedging() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||
|
||||
// bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
// aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
|
||||
bobTimeline.start()
|
||||
|
||||
val bobFinalLatch = CountDownLatch(1)
|
||||
val bobHasThreeDecryptedEventsListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val decryptedEventReceivedByBob = snapshot.filter { it.root.type == EventType.ENCRYPTED }
|
||||
Timber.d("Bob can now decrypt ${decryptedEventReceivedByBob.size} messages")
|
||||
if (decryptedEventReceivedByBob.size == 3) {
|
||||
if (decryptedEventReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||
bobFinalLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bobTimeline.addListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
var latch = CountDownLatch(1)
|
||||
var bobEventsListener = createEventListener(latch, 1)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
// - Alice sends a 1st message with a 1st megolm session
|
||||
roomFromAlicePOV.sendTextMessage("First message")
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.await(latch)
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 1
|
||||
val firstMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
||||
// - Store the olm session between A&B devices
|
||||
// Let us pickle our session with bob here so we can later unpickle it
|
||||
// and wedge our session.
|
||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
sessionIdsForBob!!.size shouldBe 1
|
||||
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
|
||||
|
||||
val oldSession = serializeForRealm(olmSession.olmSession)
|
||||
|
||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||
Thread.sleep(6_000)
|
||||
|
||||
latch = CountDownLatch(1)
|
||||
bobEventsListener = createEventListener(latch, 2)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 2nd message with a 2nd megolm session")
|
||||
// - Alice sends a 2nd message with a 2nd megolm session
|
||||
roomFromAlicePOV.sendTextMessage("Second message")
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.await(latch)
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 2
|
||||
// Session should have changed
|
||||
val secondMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
Assert.assertNotEquals(firstMessageSession, secondMessageSession)
|
||||
|
||||
// Let us wedge the session now. Set crypto state like after the first message
|
||||
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()!!)
|
||||
Thread.sleep(6_000)
|
||||
|
||||
// Force new session, and key share
|
||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.waitWithLatch {
|
||||
bobEventsListener = createEventListener(it, 3)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session")
|
||||
// - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
|
||||
roomFromAlicePOV.sendTextMessage("Third message")
|
||||
// Bob should not be able to decrypt, because the session key could not be sent
|
||||
}
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 3
|
||||
|
||||
val thirdMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
Timber.i("## CRYPTO | testUnwedging: third message session ID $thirdMessageSession")
|
||||
Assert.assertNotEquals(secondMessageSession, thirdMessageSession)
|
||||
|
||||
Assert.assertEquals(EventType.ENCRYPTED, messagesReceivedByBob[0].root.getClearType())
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[1].root.getClearType())
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[2].root.getClearType())
|
||||
// Bob Should not be able to decrypt last message, because session could not be sent as the olm channel was wedged
|
||||
mTestHelper.await(bobFinalLatch)
|
||||
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
// It's a trick to force key request on fail to decrypt
|
||||
mTestHelper.doSync<Unit> {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = bobSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), it)
|
||||
}
|
||||
|
||||
// Wait until we received back the key
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
// we should get back the key and be able to decrypt
|
||||
val result = tryThis {
|
||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||
}
|
||||
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
||||
result != null
|
||||
}
|
||||
}
|
||||
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {
|
||||
return object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
messagesReceivedByBob = snapshot.filter { it.root.type == EventType.ENCRYPTED }
|
||||
|
||||
if (messagesReceivedByBob.size == expectedNumberOfMessages) {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -122,7 +122,7 @@ class XSigningTest : InstrumentedTest {
|
||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||
|
||||
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
||||
val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId!!
|
||||
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
|
||||
|
||||
// Check that bob first session sees the new login
|
||||
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
|
@@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.internal.crypto.GossipingRequestState
|
||||
@@ -56,6 +57,7 @@ import java.util.concurrent.CountDownLatch
|
||||
class KeyShareTests : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
@Test
|
||||
fun test_DoNotSelfShareIfNotTrusted() {
|
||||
@@ -90,7 +92,7 @@ class KeyShareTests : InstrumentedTest {
|
||||
} catch (failure: Throwable) {
|
||||
}
|
||||
|
||||
val outgoingRequestBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
||||
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
// Try to request
|
||||
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
|
||||
|
||||
@@ -100,10 +102,10 @@ class KeyShareTests : InstrumentedTest {
|
||||
var outGoingRequestId: String? = null
|
||||
|
||||
mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.filter { req ->
|
||||
// filter out request that was known before
|
||||
!outgoingRequestBefore.any { req.requestId == it.requestId }
|
||||
!outgoingRequestsBefore.any { req.requestId == it.requestId }
|
||||
}
|
||||
.let {
|
||||
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
|
||||
@@ -115,10 +117,10 @@ class KeyShareTests : InstrumentedTest {
|
||||
|
||||
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
|
||||
|
||||
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
||||
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
|
||||
// We should have a new request
|
||||
Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestBefore.size)
|
||||
Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestsBefore.size)
|
||||
Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId })
|
||||
|
||||
// The first session should see an incoming request
|
||||
@@ -126,7 +128,7 @@ class KeyShareTests : InstrumentedTest {
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// DEBUG LOGS
|
||||
aliceSession.cryptoService().getIncomingRoomKeyRequest().let {
|
||||
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
||||
Log.v("TEST", "=========================")
|
||||
it.forEach { keyRequest ->
|
||||
@@ -135,7 +137,7 @@ class KeyShareTests : InstrumentedTest {
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
|
||||
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequest().firstOrNull { it.requestId == outGoingRequestId }
|
||||
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||
incoming?.state == GossipingRequestState.REJECTED
|
||||
}
|
||||
}
|
||||
@@ -148,14 +150,14 @@ class KeyShareTests : InstrumentedTest {
|
||||
|
||||
// Mark the device as trusted
|
||||
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||
aliceSession2.sessionParams.credentials.deviceId ?: "")
|
||||
aliceSession2.sessionParams.deviceId ?: "")
|
||||
|
||||
// Re request
|
||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession.cryptoService().getIncomingRoomKeyRequest().let {
|
||||
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||
Log.v("TEST", "Incoming request Session 1")
|
||||
Log.v("TEST", "=========================")
|
||||
it.forEach {
|
||||
@@ -171,7 +173,7 @@ class KeyShareTests : InstrumentedTest {
|
||||
Thread.sleep(6_000)
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequest().let {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let {
|
||||
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
|
||||
}
|
||||
}
|
||||
@@ -234,6 +236,7 @@ class KeyShareTests : InstrumentedTest {
|
||||
}
|
||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||
session1ShortCode = tx.getDecimalCodeRepresentation()
|
||||
Thread.sleep(500)
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
@@ -246,19 +249,20 @@ class KeyShareTests : InstrumentedTest {
|
||||
if (tx is SasVerificationTransaction) {
|
||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||
session2ShortCode = tx.getDecimalCodeRepresentation()
|
||||
Thread.sleep(500)
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val txId: String = "m.testVerif12"
|
||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.credentials.deviceId
|
||||
val txId = "m.testVerif12"
|
||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
||||
?: "", txId)
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.credentials.deviceId ?: "")?.isVerified == true
|
||||
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,5 +289,8 @@ class KeyShareTests : InstrumentedTest {
|
||||
keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
|
||||
}
|
||||
}
|
||||
|
||||
mTestHelper.signOutAndClose(aliceSession1)
|
||||
mTestHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.gossiping
|
||||
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||
import im.vector.matrix.android.api.extensions.tryThis
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.MockOkHttpInterceptor
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.event.WithHeldCode
|
||||
import org.junit.Assert
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class WithHeldTests : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
@Test
|
||||
fun test_WithHeldUnverifiedReason() {
|
||||
// =============================
|
||||
// ARRANGE
|
||||
// =============================
|
||||
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
// Initialize cross signing on both
|
||||
mCryptoTestHelper.initializeCrossSigning(aliceSession)
|
||||
mCryptoTestHelper.initializeCrossSigning(bobSession)
|
||||
|
||||
val roomId = mCryptoTestHelper.createDM(aliceSession, bobSession)
|
||||
mCryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, roomId)
|
||||
|
||||
val roomAlicePOV = aliceSession.getRoom(roomId)!!
|
||||
|
||||
val bobUnverifiedSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
|
||||
|
||||
// =============================
|
||||
// ACT
|
||||
// =============================
|
||||
|
||||
// Alice decide to not send to unverified sessions
|
||||
aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true)
|
||||
|
||||
val timelineEvent = mTestHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first()
|
||||
|
||||
// await for bob unverified session to get the message
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId) != null
|
||||
}
|
||||
}
|
||||
|
||||
val eventBobPOV = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId)!!
|
||||
|
||||
// =============================
|
||||
// ASSERT
|
||||
// =============================
|
||||
|
||||
// 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, "")
|
||||
Assert.fail("This session should not be able to decrypt")
|
||||
} catch (failure: Throwable) {
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||
}
|
||||
|
||||
// enable back sending to unverified
|
||||
aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false)
|
||||
|
||||
val secondEvent = mTestHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first()
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val ev = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(secondEvent.eventId)
|
||||
// wait until it's decrypted
|
||||
ev?.root?.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
// Previous message should still be undecryptable (partially withheld session)
|
||||
try {
|
||||
// .. might need to wait a bit for stability?
|
||||
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
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||
}
|
||||
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
mTestHelper.signOutAndClose(bobUnverifiedSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_WithHeldNoOlm() {
|
||||
val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
val aliceInterceptor = mTestHelper.getTestInterceptor(aliceSession)
|
||||
|
||||
// Simulate no OTK
|
||||
aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule(
|
||||
"/keys/claim",
|
||||
200,
|
||||
"""
|
||||
{ "one_time_keys" : {} }
|
||||
"""
|
||||
))
|
||||
Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}")
|
||||
|
||||
val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
|
||||
|
||||
val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
|
||||
|
||||
// await for bob session to get the message
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId) != null
|
||||
}
|
||||
}
|
||||
|
||||
// Previous message should still be undecryptable (partially withheld session)
|
||||
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)
|
||||
try {
|
||||
// .. might need to wait a bit for stability?
|
||||
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
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage)
|
||||
}
|
||||
|
||||
// Ensure that alice has marked the session to be shared with bob
|
||||
val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSession.myUserId, bobSession.sessionParams.credentials.deviceId)
|
||||
|
||||
Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex)
|
||||
// Add a new device for bob
|
||||
|
||||
aliceInterceptor.clearRules()
|
||||
val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(withInitialSync = true))
|
||||
// send a second message
|
||||
val secondMessageId = mTestHelper.sendTextMessage(roomAlicePov, "second message", 1).first().eventId
|
||||
|
||||
// Check that the
|
||||
// await for bob SecondSession session to get the message
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(secondMessageId) != null
|
||||
}
|
||||
}
|
||||
|
||||
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSecondSession.myUserId, bobSecondSession.sessionParams.credentials.deviceId)
|
||||
|
||||
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
|
||||
|
||||
aliceInterceptor.clearRules()
|
||||
testData.cleanUp(mTestHelper)
|
||||
mTestHelper.signOutAndClose(bobSecondSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_WithHeldKeyRequest() {
|
||||
val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
|
||||
val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
|
||||
|
||||
val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
|
||||
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
|
||||
// Create a new session for bob
|
||||
|
||||
val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
|
||||
// initialize to force request keys if missing
|
||||
mCryptoTestHelper.initializeCrossSigning(bobSecondSession)
|
||||
|
||||
// Trust bob second device from Alice POV
|
||||
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback())
|
||||
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!, NoOpMatrixCallback())
|
||||
|
||||
var sessionId: String? = null
|
||||
// Check that the
|
||||
// await for bob SecondSession session to get the message
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also {
|
||||
// try to decrypt and force key request
|
||||
tryThis { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
|
||||
}
|
||||
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
|
||||
timeLineEvent != null
|
||||
}
|
||||
}
|
||||
|
||||
// Check that bob second session requested the key
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!)
|
||||
wc?.code == WithHeldCode.UNAUTHORISED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestData
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
|
||||
/**
|
||||
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
||||
*/
|
||||
data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
||||
val aliceKeys: List<OlmInboundGroupSessionWrapper2>,
|
||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||
val aliceSession2: Session) {
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
}
|
@@ -20,27 +20,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.listeners.StepProgressListener
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestData
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.common.TestMatrixCallback
|
||||
import im.vector.matrix.android.common.assertDictEquals
|
||||
import im.vector.matrix.android.common.assertListEquals
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
@@ -61,9 +53,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
private val defaultSessionParams = SessionTestParams(withInitialSync = false)
|
||||
private val defaultSessionParamsWithInitialSync = SessionTestParams(withInitialSync = true)
|
||||
private val mKeysBackupTestHelper = KeysBackupTestHelper(mTestHelper, mCryptoTestHelper)
|
||||
|
||||
/**
|
||||
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
|
||||
@@ -110,7 +100,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun prepareKeysBackupVersionTest() {
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
|
||||
assertNotNull(bobSession.cryptoService().keysBackupService())
|
||||
|
||||
@@ -139,7 +129,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun createKeysBackupVersionTest() {
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
|
||||
val keysBackup = bobSession.cryptoService().keysBackupService()
|
||||
|
||||
@@ -182,7 +172,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
val stateObserver = StateObserver(keysBackup, latch, 5)
|
||||
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
mTestHelper.await(latch)
|
||||
|
||||
@@ -216,7 +206,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Check that backupAllGroupSessions returns valid data
|
||||
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
|
||||
@@ -263,7 +253,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// - Pick a megolm key
|
||||
val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0]
|
||||
|
||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
|
||||
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
|
||||
|
||||
// - Check encryptGroupSession() returns stg
|
||||
val keyBackupData = keysBackup.encryptGroupSession(session)
|
||||
@@ -281,7 +271,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
decryption!!)
|
||||
assertNotNull(sessionData)
|
||||
// - Compare the decrypted megolm key with the original one
|
||||
assertKeysEquals(session.exportKeys(), sessionData)
|
||||
mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
@@ -295,7 +285,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun restoreKeysBackupTest() {
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
@@ -308,7 +298,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
)
|
||||
}
|
||||
|
||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
@@ -329,7 +319,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// fun restoreKeysBackupAndKeyShareRequestTest() {
|
||||
// fail("Check with Valere for this test. I think we do not send key share request")
|
||||
//
|
||||
// val testData = createKeysBackupScenarioWithPassword(null)
|
||||
// val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
//
|
||||
// // - Check the SDK sent key share requests
|
||||
// val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
@@ -352,7 +342,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
// mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
//
|
||||
// // - There must be no more pending key share requests
|
||||
// val unsentRequestAfterRestoration = cryptoStore2
|
||||
@@ -380,7 +370,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun trustKeyBackupVersionTest() {
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -399,7 +389,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
||||
// - Backup must be enabled on the new device, on the same version
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||
@@ -439,7 +429,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun trustKeyBackupVersionWithRecoveryKeyTest() {
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -458,7 +448,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
||||
// - Backup must be enabled on the new device, on the same version
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||
@@ -496,7 +486,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -539,7 +529,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
// - Do an e2e backup to the homeserver with a password
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -558,7 +548,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
||||
// - Backup must be enabled on the new device, on the same version
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||
@@ -599,7 +589,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
// - Do an e2e backup to the homeserver with a password
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -634,7 +624,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Try to restore the e2e backup with a wrong recovery key
|
||||
val latch2 = CountDownLatch(1)
|
||||
@@ -669,7 +659,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun testBackupWithPassword() {
|
||||
val password = "password"
|
||||
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Restore the e2e backup with the password
|
||||
val steps = ArrayList<StepProgressListener.Step>()
|
||||
@@ -709,7 +699,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
|
||||
assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress)
|
||||
|
||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
@@ -725,7 +715,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val password = "password"
|
||||
val wrongPassword = "passw0rd"
|
||||
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Try to restore the e2e backup with a wrong password
|
||||
val latch2 = CountDownLatch(1)
|
||||
@@ -760,7 +750,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
|
||||
val password = "password"
|
||||
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Restore the e2e backup with the recovery key.
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
@@ -773,7 +763,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
)
|
||||
}
|
||||
|
||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
@@ -786,7 +776,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Try to restore the e2e backup with a password
|
||||
val latch2 = CountDownLatch(1)
|
||||
@@ -825,7 +815,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
// - Do an e2e backup to the homeserver
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Get key backup version from the home server
|
||||
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
|
||||
@@ -845,7 +835,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertTrue(signature.valid)
|
||||
assertNotNull(signature.device)
|
||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId)
|
||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
@@ -870,13 +860,13 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup)
|
||||
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
// - Restart alice session
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync)
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
|
||||
@@ -950,7 +940,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
})
|
||||
|
||||
// - Make alice back up her keys to her homeserver
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
@@ -1000,19 +990,19 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
// - Make alice back up her keys to her homeserver
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Wait for keys backup to finish by asking again to backup keys.
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(null, it)
|
||||
}
|
||||
|
||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
|
||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
|
||||
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
// - Post a message to have a new megolm session
|
||||
aliceSession2.cryptoService().setWarnOnUnknownDevices(false)
|
||||
@@ -1093,7 +1083,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup)
|
||||
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
@@ -1106,169 +1096,4 @@ class KeysBackupTest : InstrumentedTest {
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Private
|
||||
* ========================================================================================== */
|
||||
|
||||
/**
|
||||
* As KeysBackup is doing asynchronous call to update its internal state, this method help to wait for the
|
||||
* KeysBackup object to be in the specified state
|
||||
*/
|
||||
private fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) {
|
||||
// If already in the wanted state, return
|
||||
if (session.cryptoService().keysBackupService().state == state) {
|
||||
return
|
||||
}
|
||||
|
||||
// Else observe state changes
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
session.cryptoService().keysBackupService().addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
if (newState == state) {
|
||||
session.cryptoService().keysBackupService().removeListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(latch)
|
||||
}
|
||||
|
||||
private data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
val version: String)
|
||||
|
||||
private fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService,
|
||||
password: String? = null): PrepareKeysBackupDataResult {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(password, null, it)
|
||||
}
|
||||
|
||||
assertNotNull(megolmBackupCreationInfo)
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
// Create the version
|
||||
val keysVersion = mTestHelper.doSync<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
assertNotNull(keysVersion.version)
|
||||
|
||||
// Backup must be enable now
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!)
|
||||
}
|
||||
|
||||
private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
|
||||
assertNotNull(keys1)
|
||||
assertNotNull(keys2)
|
||||
|
||||
assertEquals(keys1?.algorithm, keys2?.algorithm)
|
||||
assertEquals(keys1?.roomId, keys2?.roomId)
|
||||
// No need to compare the shortcut
|
||||
// assertEquals(keys1?.sender_claimed_ed25519_key, keys2?.sender_claimed_ed25519_key)
|
||||
assertEquals(keys1?.senderKey, keys2?.senderKey)
|
||||
assertEquals(keys1?.sessionId, keys2?.sessionId)
|
||||
assertEquals(keys1?.sessionKey, keys2?.sessionKey)
|
||||
|
||||
assertListEquals(keys1?.forwardingCurve25519KeyChain, keys2?.forwardingCurve25519KeyChain)
|
||||
assertDictEquals(keys1?.senderClaimedKeys, keys2?.senderClaimedKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class to store result of [createKeysBackupScenarioWithPassword]
|
||||
*/
|
||||
private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
||||
val aliceKeys: List<OlmInboundGroupSessionWrapper>,
|
||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||
val aliceSession2: Session) {
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common initial condition
|
||||
* - Do an e2e backup to the homeserver
|
||||
* - Log Alice on a new device, and wait for its keysBackup object to be ready (in state NotTrusted)
|
||||
*
|
||||
* @param password optional password
|
||||
*/
|
||||
private fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
|
||||
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
|
||||
|
||||
// - Do an e2e backup to the homeserver
|
||||
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
|
||||
|
||||
var lastProgress = 0
|
||||
var lastTotal = 0
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
lastProgress = progress
|
||||
lastTotal = total
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
|
||||
assertEquals(2, lastProgress)
|
||||
assertEquals(2, lastTotal)
|
||||
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
|
||||
|
||||
// Test check: aliceSession2 has no keys at login
|
||||
assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
|
||||
// Wait for backup state to be NotTrusted
|
||||
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
||||
return KeysBackupScenarioData(cryptoTestData,
|
||||
aliceKeys,
|
||||
prepareKeysBackupDataResult,
|
||||
aliceSession2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common restore success check after [createKeysBackupScenarioWithPassword]:
|
||||
* - Imported keys number must be correct
|
||||
* - The new device must have the same count of megolm keys
|
||||
* - Alice must have the same keys on both devices
|
||||
*/
|
||||
private fun checkRestoreSuccess(testData: KeysBackupScenarioData,
|
||||
total: Int,
|
||||
imported: Int) {
|
||||
// - Imported keys number must be correct
|
||||
assertEquals(testData.aliceKeys.size, total)
|
||||
assertEquals(total, imported)
|
||||
|
||||
// - The new device must have the same count of megolm keys
|
||||
assertEquals(testData.aliceKeys.size, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
|
||||
// - Alice must have the same keys on both devices
|
||||
for (aliceKey1 in testData.aliceKeys) {
|
||||
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
.getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!)
|
||||
assertNotNull(aliceKey2)
|
||||
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
|
||||
object KeysBackupTestConstants {
|
||||
val defaultSessionParams = SessionTestParams(withInitialSync = false)
|
||||
val defaultSessionParamsWithInitialSync = SessionTestParams(withInitialSync = true)
|
||||
}
|
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.assertDictEquals
|
||||
import im.vector.matrix.android.common.assertListEquals
|
||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import org.junit.Assert
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class KeysBackupTestHelper(
|
||||
private val mTestHelper: CommonTestHelper,
|
||||
private val mCryptoTestHelper: CryptoTestHelper) {
|
||||
|
||||
/**
|
||||
* Common initial condition
|
||||
* - Do an e2e backup to the homeserver
|
||||
* - Log Alice on a new device, and wait for its keysBackup object to be ready (in state NotTrusted)
|
||||
*
|
||||
* @param password optional password
|
||||
*/
|
||||
fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
|
||||
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
|
||||
|
||||
// - Do an e2e backup to the homeserver
|
||||
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
|
||||
|
||||
var lastProgress = 0
|
||||
var lastTotal = 0
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
lastProgress = progress
|
||||
lastTotal = total
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
|
||||
Assert.assertEquals(2, lastProgress)
|
||||
Assert.assertEquals(2, lastTotal)
|
||||
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
// Test check: aliceSession2 has no keys at login
|
||||
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
|
||||
// Wait for backup state to be NotTrusted
|
||||
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
||||
return KeysBackupScenarioData(cryptoTestData,
|
||||
aliceKeys,
|
||||
prepareKeysBackupDataResult,
|
||||
aliceSession2)
|
||||
}
|
||||
|
||||
fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService,
|
||||
password: String? = null): PrepareKeysBackupDataResult {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(password, null, it)
|
||||
}
|
||||
|
||||
Assert.assertNotNull(megolmBackupCreationInfo)
|
||||
|
||||
Assert.assertFalse(keysBackup.isEnabled)
|
||||
|
||||
// Create the version
|
||||
val keysVersion = mTestHelper.doSync<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
Assert.assertNotNull(keysVersion.version)
|
||||
|
||||
// Backup must be enable now
|
||||
Assert.assertTrue(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* As KeysBackup is doing asynchronous call to update its internal state, this method help to wait for the
|
||||
* KeysBackup object to be in the specified state
|
||||
*/
|
||||
fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) {
|
||||
// If already in the wanted state, return
|
||||
if (session.cryptoService().keysBackupService().state == state) {
|
||||
return
|
||||
}
|
||||
|
||||
// Else observe state changes
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
session.cryptoService().keysBackupService().addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
if (newState == state) {
|
||||
session.cryptoService().keysBackupService().removeListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(latch)
|
||||
}
|
||||
|
||||
fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
|
||||
Assert.assertNotNull(keys1)
|
||||
Assert.assertNotNull(keys2)
|
||||
|
||||
Assert.assertEquals(keys1?.algorithm, keys2?.algorithm)
|
||||
Assert.assertEquals(keys1?.roomId, keys2?.roomId)
|
||||
// No need to compare the shortcut
|
||||
// assertEquals(keys1?.sender_claimed_ed25519_key, keys2?.sender_claimed_ed25519_key)
|
||||
Assert.assertEquals(keys1?.senderKey, keys2?.senderKey)
|
||||
Assert.assertEquals(keys1?.sessionId, keys2?.sessionId)
|
||||
Assert.assertEquals(keys1?.sessionKey, keys2?.sessionKey)
|
||||
|
||||
assertListEquals(keys1?.forwardingCurve25519KeyChain, keys2?.forwardingCurve25519KeyChain)
|
||||
assertDictEquals(keys1?.senderClaimedKeys, keys2?.senderClaimedKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common restore success check after [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]:
|
||||
* - Imported keys number must be correct
|
||||
* - The new device must have the same count of megolm keys
|
||||
* - Alice must have the same keys on both devices
|
||||
*/
|
||||
fun checkRestoreSuccess(testData: KeysBackupScenarioData,
|
||||
total: Int,
|
||||
imported: Int) {
|
||||
// - Imported keys number must be correct
|
||||
Assert.assertEquals(testData.aliceKeys.size, total)
|
||||
Assert.assertEquals(total, imported)
|
||||
|
||||
// - The new device must have the same count of megolm keys
|
||||
Assert.assertEquals(testData.aliceKeys.size, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
|
||||
// - Alice must have the same keys on both devices
|
||||
for (aliceKey1 in testData.aliceKeys) {
|
||||
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
.getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!)
|
||||
Assert.assertNotNull(aliceKey2)
|
||||
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
|
||||
data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
val version: String)
|
@@ -144,7 +144,7 @@ class QuadSTests : InstrumentedTest {
|
||||
|
||||
val secretAccountData = assertAccountData(aliceSession, "secret.of.life")
|
||||
|
||||
val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*, *>
|
||||
val encryptedContent = secretAccountData.content["encrypted"] as? Map<*, *>
|
||||
assertNotNull("Element should be encrypted", encryptedContent)
|
||||
assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
|
||||
|
||||
|
@@ -468,14 +468,19 @@ class SASTest : InstrumentedTest {
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== aliceState ${uxState.name}")
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
aliceSASLatch.countDown()
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
@@ -485,14 +490,23 @@ class SASTest : InstrumentedTest {
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
var acceptOnce = true
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== bobState ${uxState.name}")
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
tx.performAccept()
|
||||
if (acceptOnce) {
|
||||
acceptOnce = false
|
||||
tx.performAccept()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
bobSASLatch.countDown()
|
||||
@@ -579,7 +593,7 @@ class SASTest : InstrumentedTest {
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
bobSession.myUserId,
|
||||
bobSession.sessionParams.credentials.deviceId!!,
|
||||
bobSession.sessionParams.deviceId!!,
|
||||
null)
|
||||
|
||||
bobVerificationService.beginKeyVerificationInDMs(
|
||||
@@ -587,7 +601,7 @@ class SASTest : InstrumentedTest {
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
aliceSession.myUserId,
|
||||
aliceSession.sessionParams.credentials.deviceId!!,
|
||||
aliceSession.sessionParams.deviceId!!,
|
||||
null)
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
|
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.room.send
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.commonmark.renderer.text.TextContentRenderer
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
/**
|
||||
* It will not be possible to test all combinations. For the moment I add a few tests, then, depending on the problem discovered in the wild,
|
||||
* we can add more tests to cover the edge cases.
|
||||
* Some tests are suffixed with `_not_passing`, maybe one day we will fix them...
|
||||
* Riot-Web should be used as a reference for expected results, but not always. Especially Riot-Web add lots of `\n` in the
|
||||
* formatted body, which is quite useless.
|
||||
* Also Riot-Web does not provide plain text body when formatted text is provided. The body contains what the user has entered.
|
||||
* See https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class MarkdownParserTest : InstrumentedTest {
|
||||
|
||||
/**
|
||||
* Create the same parser than in the RoomModule
|
||||
*/
|
||||
private val markdownParser = MarkdownParser(
|
||||
Parser.builder().build(),
|
||||
HtmlRenderer.builder().build(),
|
||||
TextContentRenderer.builder().build()
|
||||
)
|
||||
|
||||
@Test
|
||||
fun parseNoMarkdown() {
|
||||
testIdentity("")
|
||||
testIdentity("a")
|
||||
testIdentity("1")
|
||||
testIdentity("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et " +
|
||||
"dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea com" +
|
||||
"modo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pari" +
|
||||
"atur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseSpaces() {
|
||||
testIdentity(" ")
|
||||
testIdentity(" ")
|
||||
testIdentity("\n")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseNewLines() {
|
||||
testIdentity("line1\nline2")
|
||||
testIdentity("line1\nline2\nline3")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBold() {
|
||||
testType(
|
||||
name = "bold",
|
||||
markdownPattern = "**",
|
||||
htmlExpectedTag = "strong"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseItalic() {
|
||||
testType(
|
||||
name = "italic",
|
||||
markdownPattern = "*",
|
||||
htmlExpectedTag = "em"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseItalic2() {
|
||||
// Riot-Web format
|
||||
"_italic_".let { markdownParser.parse(it) }.expect("italic", "<em>italic</em>")
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the test is not passing, it does not work on Riot-Web neither
|
||||
*/
|
||||
@Test
|
||||
fun parseStrike_not_passing() {
|
||||
testType(
|
||||
name = "strike",
|
||||
markdownPattern = "~~",
|
||||
htmlExpectedTag = "del"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode() {
|
||||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "`",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode2() {
|
||||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "``",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode3() {
|
||||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "```",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseUnorderedList() {
|
||||
"- item1".let { markdownParser.parse(it).expect(it, "<ul><li>item1</li></ul>") }
|
||||
"- item1\n- item2".let { markdownParser.parse(it).expect(it, "<ul><li>item1</li><li>item2</li></ul>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseOrderedList() {
|
||||
"1. item1".let { markdownParser.parse(it).expect(it, "<ol><li>item1</li></ol>") }
|
||||
"1. item1\n2. item2".let { markdownParser.parse(it).expect(it, "<ol><li>item1</li><li>item2</li></ol>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHorizontalLine() {
|
||||
"---".let { markdownParser.parse(it) }.expect("***", "<hr />")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseH2AndContent() {
|
||||
"a\n---\nb".let { markdownParser.parse(it) }.expect("a\nb", "<h2>a</h2><p>b</p>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseQuote() {
|
||||
"> quoted".let { markdownParser.parse(it) }.expect("«quoted»", "<blockquote><p>quoted</p></blockquote>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseQuote_not_passing() {
|
||||
"> quoted\nline2".let { markdownParser.parse(it) }.expect("«quoted\nline2»", "<blockquote><p>quoted<br/>line2</p></blockquote>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBoldItalic() {
|
||||
"*italic* **bold**".let { markdownParser.parse(it) }.expect("italic bold", "<em>italic</em> <strong>bold</strong>")
|
||||
"**bold** *italic*".let { markdownParser.parse(it) }.expect("bold italic", "<strong>bold</strong> <em>italic</em>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHead() {
|
||||
"# head1".let { markdownParser.parse(it) }.expect("head1", "<h1>head1</h1>")
|
||||
"## head2".let { markdownParser.parse(it) }.expect("head2", "<h2>head2</h2>")
|
||||
"### head3".let { markdownParser.parse(it) }.expect("head3", "<h3>head3</h3>")
|
||||
"#### head4".let { markdownParser.parse(it) }.expect("head4", "<h4>head4</h4>")
|
||||
"##### head5".let { markdownParser.parse(it) }.expect("head5", "<h5>head5</h5>")
|
||||
"###### head6".let { markdownParser.parse(it) }.expect("head6", "<h6>head6</h6>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHeads() {
|
||||
"# head1\n# head2".let { markdownParser.parse(it) }.expect("head1\nhead2", "<h1>head1</h1><h1>head2</h1>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBoldNewLines_not_passing() {
|
||||
"**bold**\nline2".let { markdownParser.parse(it) }.expect("bold\nline2", "<strong>bold</strong><br />line2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseLinks() {
|
||||
"[link](target)".let { markdownParser.parse(it) }.expect(""""link" (target)""", """<a href="target">link</a>""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseParagraph() {
|
||||
"# head\ncontent".let { markdownParser.parse(it) }.expect("head\ncontent", "<h1>head</h1><p>content</p>")
|
||||
}
|
||||
|
||||
private fun testIdentity(text: String) {
|
||||
markdownParser.parse(text).expect(text, null)
|
||||
}
|
||||
|
||||
private fun testType(name: String,
|
||||
markdownPattern: String,
|
||||
htmlExpectedTag: String,
|
||||
plainTextPrefix: String = "",
|
||||
plainTextSuffix: String = "") {
|
||||
// Test simple case
|
||||
"$markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
|
||||
// Test twice the same tag
|
||||
"$markdownPattern$name$markdownPattern and $markdownPattern$name bis$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix and $plainTextPrefix$name bis$plainTextSuffix",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> and <$htmlExpectedTag>$name bis</$htmlExpectedTag>")
|
||||
|
||||
val textBefore = "a"
|
||||
val textAfter = "b"
|
||||
|
||||
// With sticked text before
|
||||
"$textBefore$markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore$plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "$textBefore<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
|
||||
// With text before and space
|
||||
"$textBefore $markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore $plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
|
||||
// With sticked text after
|
||||
"$markdownPattern$name$markdownPattern$textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix$textAfter",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
|
||||
// With space and text after
|
||||
"$markdownPattern$name$markdownPattern $textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix $textAfter",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
|
||||
// With sticked text before and text after
|
||||
"$textBefore$markdownPattern$name$markdownPattern$textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore$plainTextPrefix$name$plainTextSuffix$textAfter",
|
||||
expectedFormattedText = "a<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
|
||||
// With text before and after, with spaces
|
||||
"$textBefore $markdownPattern$name$markdownPattern $textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore $plainTextPrefix$name$plainTextSuffix $textAfter",
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
}
|
||||
|
||||
private fun TextContent.expect(expectedText: String, expectedFormattedText: String?) {
|
||||
assertEquals("TextContent are not identical", TextContent(expectedText, expectedFormattedText), this)
|
||||
}
|
||||
}
|
@@ -59,7 +59,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
|
||||
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
|
||||
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
|
||||
realm.copyToRealmOrUpdate(it)
|
||||
}
|
||||
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
|
||||
@@ -71,7 +71,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
|
||||
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
|
||||
realm.copyToRealmOrUpdate(it)
|
||||
}
|
||||
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
|
||||
@@ -141,7 +141,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
events: List<Event>,
|
||||
direction: PaginationDirection) {
|
||||
events.forEach { event ->
|
||||
val fakeEvent = event.toEntity(roomId, SendState.SYNCED).let {
|
||||
val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
|
||||
realm.copyToRealmOrUpdate(it)
|
||||
}
|
||||
addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
|
||||
|
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.checkSendOrder
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelineBackToPreviousLastForwardTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink of an
|
||||
* even contained in a previous lastForward chunk, we will be able to go back to the live
|
||||
*/
|
||||
@Test
|
||||
fun backToPreviousLastForwardTest() {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
|
||||
bobTimeline.start()
|
||||
|
||||
var roomCreationEventId: String? = null
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
roomCreationEventId = snapshot.lastOrNull()?.root?.eventId
|
||||
// Ok, we have the 8 first messages of the initial sync (room creation and bob join event)
|
||||
snapshot.size == 8
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob stop to sync
|
||||
bobSession.stopSync()
|
||||
|
||||
val messageRoot = "First messages from Alice"
|
||||
|
||||
// Alice sends 30 messages
|
||||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
messageRoot,
|
||||
30)
|
||||
|
||||
// Bob start to sync
|
||||
bobSession.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages from Alice.
|
||||
snapshot.size == 10
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(messageRoot).orFalse() }
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob navigate to the first event (room creation event), so inside the previous last forward chunk
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// The event is in db, so it is fetch and auto pagination occurs, half of the number of events we have for this chunk (?)
|
||||
snapshot.size == 4
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event, which is already in the database, so pagination should start automatically
|
||||
assertTrue(roomFromBobPOV.getTimeLineEvent(roomCreationEventId!!) != null)
|
||||
|
||||
bobTimeline.restartWithEventId(roomCreationEventId)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob scroll to the future
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Bob can see the first event of the room (so Back pagination has worked)
|
||||
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
|
||||
// 8 for room creation item, and 30 for the forward pagination
|
||||
&& snapshot.size == 38
|
||||
&& snapshot.checkSendOrder(messageRoot, 30, 0)
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.checkSendOrder
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelineForwardPaginationTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* This test ensure that if we click to permalink, we will be able to go back to the live
|
||||
*/
|
||||
@Test
|
||||
fun forwardPaginationTest() {
|
||||
val numberOfMessagesToSend = 90
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
// Alice sends X messages
|
||||
val message = "Message from Alice"
|
||||
val sentMessages = commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
message,
|
||||
numberOfMessagesToSend)
|
||||
|
||||
// Alice clear the cache
|
||||
commonTestHelper.doSync<Unit> {
|
||||
aliceSession.clearCache(it)
|
||||
}
|
||||
|
||||
// And restarts the sync
|
||||
aliceSession.startSync(true)
|
||||
|
||||
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
|
||||
aliceTimeline.start()
|
||||
|
||||
// Alice sees the 10 last message of the room, and can only navigate BACKWARD
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages of the initial sync
|
||||
snapshot.size == 10
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(message).orFalse() }
|
||||
}
|
||||
|
||||
// Open the timeline at last sent message
|
||||
aliceTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Alice navigates to the first message of the room, which is not in its database. A GET /context is performed
|
||||
// Then she can paginate BACKWARD and FORWARD
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
|
||||
// The event is not in db, so it is fetch alone
|
||||
snapshot.size == 1
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith("Message from Alice").orFalse() }
|
||||
}
|
||||
|
||||
aliceTimeline.addListener(aliceEventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event
|
||||
aliceTimeline.restartWithEventId(sentMessages.last().eventId)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
}
|
||||
|
||||
// Alice paginates BACKWARD and FORWARD of 50 events each
|
||||
// Then she can only navigate FORWARD
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
|
||||
// Alice can see the first event of the room (so Back pagination has worked)
|
||||
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
|
||||
// 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination
|
||||
&& snapshot.size == 6 + 1 + 50
|
||||
}
|
||||
|
||||
aliceTimeline.addListener(aliceEventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event
|
||||
// We ask to load event backward and forward
|
||||
aliceTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
|
||||
aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Alice paginates once again FORWARD for 50 events
|
||||
// All the timeline is retrieved, she cannot paginate anymore in both direction
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
// 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room)
|
||||
snapshot.size == 6 + numberOfMessagesToSend
|
||||
&& snapshot.checkSendOrder(message, numberOfMessagesToSend, 0)
|
||||
}
|
||||
|
||||
aliceTimeline.addListener(aliceEventsListener)
|
||||
|
||||
// Ask for a forward pagination
|
||||
aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
// The timeline is fully loaded
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
aliceTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.checkSendOrder
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelinePreviousLastForwardTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink, we will be able to go back to the live
|
||||
*/
|
||||
@Test
|
||||
fun previousLastForwardTest() {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
|
||||
bobTimeline.start()
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 8 first messages of the initial sync (room creation and bob invite and join events)
|
||||
snapshot.size == 8
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob stop to sync
|
||||
bobSession.stopSync()
|
||||
|
||||
val firstMessage = "First messages from Alice"
|
||||
// Alice sends 30 messages
|
||||
val firstMessageFromAliceId = commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
firstMessage,
|
||||
30)
|
||||
.last()
|
||||
.eventId
|
||||
|
||||
// Bob start to sync
|
||||
bobSession.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk
|
||||
snapshot.size == 10
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(firstMessage).orFalse() }
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob stop to sync
|
||||
bobSession.stopSync()
|
||||
|
||||
val secondMessage = "Second messages from Alice"
|
||||
// Alice sends again 30 messages
|
||||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
secondMessage,
|
||||
30)
|
||||
|
||||
// Bob start to sync
|
||||
bobSession.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk
|
||||
snapshot.size == 10
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(secondMessage).orFalse() }
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob navigate to the first message sent from Alice
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// The event is not in db, so it is fetch
|
||||
snapshot.size == 1
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event, and paginate in both direction
|
||||
bobTimeline.restartWithEventId(firstMessageFromAliceId)
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
}
|
||||
|
||||
// Paginate in both direction
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
snapshot.size == 8 + 1 + 35
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
// Paginate in both direction
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
|
||||
// Ensure the chunk in the middle is included in the next pagination
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 35)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob scroll to the future, till the live
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Bob can see the first event of the room (so Back pagination has worked)
|
||||
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
|
||||
// 8 for room creation item 60 message from Alice
|
||||
&& snapshot.size == 8 + 60
|
||||
&& snapshot.checkSendOrder(secondMessage, 30, 0)
|
||||
&& snapshot.checkSendOrder(firstMessage, 30, 30)
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.database
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.kotlin.where
|
||||
import timber.log.Timber
|
||||
|
||||
object RealmDebugTools {
|
||||
/**
|
||||
* Log info about the crypto DB
|
||||
*/
|
||||
fun dumpCryptoDb(realmConfiguration: RealmConfiguration) {
|
||||
Realm.getInstance(realmConfiguration).use {
|
||||
Timber.d("Realm located at : ${realmConfiguration.realmDirectory}/${realmConfiguration.realmFileName}")
|
||||
|
||||
val key = realmConfiguration.encryptionKey.joinToString("") { byte -> "%02x".format(byte) }
|
||||
Timber.d("Realm encryption key : $key")
|
||||
|
||||
// Check if we have data
|
||||
Timber.e("Realm is empty: ${it.isEmpty}")
|
||||
|
||||
Timber.d("Realm has CryptoMetadataEntity: ${it.where<CryptoMetadataEntity>().count()}")
|
||||
Timber.d("Realm has CryptoRoomEntity: ${it.where<CryptoRoomEntity>().count()}")
|
||||
Timber.d("Realm has DeviceInfoEntity: ${it.where<DeviceInfoEntity>().count()}")
|
||||
Timber.d("Realm has KeysBackupDataEntity: ${it.where<KeysBackupDataEntity>().count()}")
|
||||
Timber.d("Realm has OlmInboundGroupSessionEntity: ${it.where<OlmInboundGroupSessionEntity>().count()}")
|
||||
Timber.d("Realm has OlmSessionEntity: ${it.where<OlmSessionEntity>().count()}")
|
||||
Timber.d("Realm has UserEntity: ${it.where<UserEntity>().count()}")
|
||||
Timber.d("Realm has KeyInfoEntity: ${it.where<KeyInfoEntity>().count()}")
|
||||
Timber.d("Realm has CrossSigningInfoEntity: ${it.where<CrossSigningInfoEntity>().count()}")
|
||||
Timber.d("Realm has TrustLevelEntity: ${it.where<TrustLevelEntity>().count()}")
|
||||
Timber.d("Realm has GossipingEventEntity: ${it.where<GossipingEventEntity>().count()}")
|
||||
Timber.d("Realm has IncomingGossipingRequestEntity: ${it.where<IncomingGossipingRequestEntity>().count()}")
|
||||
Timber.d("Realm has OutgoingGossipingRequestEntity: ${it.where<OutgoingGossipingRequestEntity>().count()}")
|
||||
Timber.d("Realm has MyDeviceLastSeenInfoEntity: ${it.where<MyDeviceLastSeenInfoEntity>().count()}")
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,7 +20,6 @@ package im.vector.matrix.android.internal.network.interceptors
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okio.Buffer
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
@@ -37,7 +36,7 @@ import javax.inject.Inject
|
||||
* non-production environment.
|
||||
*/
|
||||
@MatrixScope
|
||||
internal class CurlLoggingInterceptor @Inject constructor(private val logger: HttpLoggingInterceptor.Logger)
|
||||
internal class CurlLoggingInterceptor @Inject constructor()
|
||||
: Interceptor {
|
||||
|
||||
/**
|
||||
@@ -97,8 +96,8 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
||||
// Add Json formatting
|
||||
curlCmd += " | python -m json.tool"
|
||||
|
||||
logger.log("--- cURL (" + request.url + ")")
|
||||
logger.log(curlCmd)
|
||||
Timber.d("--- cURL (${request.url})")
|
||||
Timber.d(curlCmd)
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="im.vector.matrix.android">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
@@ -8,13 +7,20 @@
|
||||
|
||||
<application android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<!--
|
||||
The SDK offers a secured File provider to access downloaded files.
|
||||
Access to these file will be given via the FileService, with a temporary
|
||||
read access permission
|
||||
-->
|
||||
<provider
|
||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||
android:authorities="${applicationId}.workmanager-init"
|
||||
android:name="im.vector.matrix.android.api.session.file.MatrixSDKFileProvider"
|
||||
android:authorities="${applicationId}.mx-sdk.fileprovider"
|
||||
android:exported="false"
|
||||
tools:node="remove" />
|
||||
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/sdk_provider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
|
||||
</manifest>
|
||||
|
54
matrix-sdk-android/src/main/assets/postMessageAPI.js
Executable file
54
matrix-sdk-android/src/main/assets/postMessageAPI.js
Executable file
@@ -0,0 +1,54 @@
|
||||
var android_widget_events = {};
|
||||
|
||||
var sendObjectMessageToRiotAndroid = function(parameters) {
|
||||
Android.onWidgetEvent(JSON.stringify(parameters));
|
||||
};
|
||||
|
||||
var onWidgetMessageToRiotAndroid = function(event) {
|
||||
/* Use an internal "_id" field for matching onMessage events and requests
|
||||
_id was originally used by the Modular API. Keep it */
|
||||
if (!event.data._id) {
|
||||
/* The Matrix Widget API v2 spec says:
|
||||
"The requestId field should be unique and included in all requests" */
|
||||
event.data._id = event.data.requestId;
|
||||
}
|
||||
/* Make sure to have one id */
|
||||
if (!event.data._id) {
|
||||
event.data._id = Date.now() + "-" + Math.random().toString(36);
|
||||
}
|
||||
|
||||
console.log("onWidgetMessageToRiotAndroid " + event.data._id);
|
||||
|
||||
if (android_widget_events[event.data._id]) {
|
||||
console.log("onWidgetMessageToRiotAndroid : already managed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.origin) {
|
||||
event.origin = event.originalEvent.origin;
|
||||
}
|
||||
|
||||
android_widget_events[event.data._id] = event;
|
||||
|
||||
console.log("onWidgetMessageToRiotAndroid : manage " + event.data);
|
||||
sendObjectMessageToRiotAndroid({'event.data': event.data});
|
||||
};
|
||||
|
||||
var sendResponseFromRiotAndroid = function(eventId, res) {
|
||||
var event = android_widget_events[eventId];
|
||||
|
||||
console.log("sendResponseFromRiotAndroid to " + event.data.action + " for "+ eventId + ": " + JSON.stringify(res));
|
||||
|
||||
var data = JSON.parse(JSON.stringify(event.data));
|
||||
|
||||
data.response = res;
|
||||
|
||||
console.log("sendResponseFromRiotAndroid ---> " + data);
|
||||
|
||||
event.source.postMessage(data, event.origin);
|
||||
android_widget_events[eventId] = true;
|
||||
|
||||
console.log("sendResponseFromRiotAndroid to done");
|
||||
};
|
||||
|
||||
window.addEventListener('message', onWidgetMessageToRiotAndroid, false);
|
@@ -23,7 +23,7 @@ import androidx.work.WorkManager
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.api.crypto.MXCryptoConfig
|
||||
import im.vector.matrix.android.api.legacy.LegacySessionImporter
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
@@ -32,26 +32,17 @@ import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
data class MatrixConfiguration(
|
||||
val applicationFlavor: String = "Default-application-flavor",
|
||||
val cryptoConfig: MXCryptoConfig = MXCryptoConfig()
|
||||
) {
|
||||
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main entry point to the matrix sdk.
|
||||
* To get the singleton instance, use getInstance static method.
|
||||
*/
|
||||
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@@ -73,6 +64,10 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
return authenticationService
|
||||
}
|
||||
|
||||
fun legacySessionImporter(): LegacySessionImporter {
|
||||
return legacySessionImporter
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private lateinit var instance: Matrix
|
||||
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api
|
||||
|
||||
import im.vector.matrix.android.api.crypto.MXCryptoConfig
|
||||
import java.net.Proxy
|
||||
|
||||
data class MatrixConfiguration(
|
||||
val applicationFlavor: String = "Default-application-flavor",
|
||||
val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
|
||||
val integrationUIUrl: String = "https://scalar.vector.im/",
|
||||
val integrationRestUrl: String = "https://scalar.vector.im/api",
|
||||
val integrationWidgetUrls: List<String> = listOf(
|
||||
"https://scalar.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar.vector.im/api",
|
||||
"https://scalar-staging.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar-staging.vector.im/api",
|
||||
"https://scalar-staging.riot.im/scalar/api"
|
||||
),
|
||||
/**
|
||||
* Optional proxy to connect to the matrix servers
|
||||
* You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port)
|
||||
*/
|
||||
val proxy: Proxy? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* Can be implemented by your Application class
|
||||
*/
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
}
|
||||
}
|
@@ -20,9 +20,9 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
@@ -30,13 +30,17 @@ import im.vector.matrix.android.api.util.Cancelable
|
||||
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||
*/
|
||||
interface AuthenticationService {
|
||||
|
||||
/**
|
||||
* Request the supported login flows for this homeserver.
|
||||
* This is the first method to call to be able to get a wizard to login or the create an account
|
||||
*/
|
||||
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Request the supported login flows for the corresponding sessionId.
|
||||
*/
|
||||
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||
*/
|
||||
@@ -74,19 +78,27 @@ interface AuthenticationService {
|
||||
*/
|
||||
fun getLastAuthenticatedSession(): Session?
|
||||
|
||||
/**
|
||||
* Get an authenticated session. You should at least call authenticate one time before.
|
||||
* If you logout, this session will no longer be valid.
|
||||
*
|
||||
* @param sessionParams the sessionParams to open with.
|
||||
* @return the associated session if any, or null
|
||||
*/
|
||||
fun getSession(sessionParams: SessionParams): Session?
|
||||
|
||||
/**
|
||||
* Create a session after a SSO successful login
|
||||
*/
|
||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Perform a wellknown request, using the domain from the matrixId
|
||||
*/
|
||||
fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?,
|
||||
callback: MatrixCallback<WellknownResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Authenticate with a matrixId and a password
|
||||
* Usually call this after a successful call to getWellKnownData()
|
||||
*/
|
||||
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth
|
||||
|
||||
/**
|
||||
* Path to use when the client does not supported any or all login flows
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#login-fallback
|
||||
* */
|
||||
const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
||||
|
||||
/**
|
||||
* Path to use when the client does not supported any or all registration flows
|
||||
* Not documented
|
||||
*/
|
||||
const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
||||
|
||||
/**
|
||||
* Path to use when the client want to connect using SSO
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
||||
*/
|
||||
const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
|
||||
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
@@ -24,16 +24,38 @@ import im.vector.matrix.android.internal.util.md5
|
||||
* This data class hold credentials user data.
|
||||
* You shouldn't have to instantiate it.
|
||||
* The access token should be use to authenticate user in all server requests.
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Credentials(
|
||||
/**
|
||||
* The fully-qualified Matrix ID that has been registered.
|
||||
*/
|
||||
@Json(name = "user_id") val userId: String,
|
||||
@Json(name = "home_server") val homeServer: String,
|
||||
/**
|
||||
* An access token for the account. This access token can then be used to authorize other requests.
|
||||
*/
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
/**
|
||||
* Not documented
|
||||
*/
|
||||
@Json(name = "refresh_token") val refreshToken: String?,
|
||||
/**
|
||||
* The server_name of the homeserver on which the account has been registered.
|
||||
* @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon)
|
||||
* if they require it. Note also that homeserver is not spelt this way.
|
||||
*/
|
||||
@Json(name = "home_server") val homeServer: String?,
|
||||
/**
|
||||
* ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified.
|
||||
*/
|
||||
@Json(name = "device_id") val deviceId: String?,
|
||||
// Optional data that may contain info to override home server and/or identity server
|
||||
@Json(name = "well_known") val wellKnown: WellKnown? = null
|
||||
/**
|
||||
* Optional client configuration provided by the server. If present, clients SHOULD use the provided object to
|
||||
* reconfigure themselves, optionally validating the URLs within.
|
||||
* This object takes the same form as the one returned from .well-known autodiscovery.
|
||||
*/
|
||||
@Json(name = "well_known") val discoveryInformation: DiscoveryInformation? = null
|
||||
)
|
||||
|
||||
internal fun Credentials.sessionId(): String {
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* This is a light version of Wellknown model, used for login response
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DiscoveryInformation(
|
||||
/**
|
||||
* Required. Used by clients to discover homeserver information.
|
||||
*/
|
||||
@Json(name = "m.homeserver")
|
||||
val homeServer: WellKnownBaseConfig? = null,
|
||||
|
||||
/**
|
||||
* Used by clients to discover identity server information.
|
||||
* Note: matrix.org does not send this field
|
||||
*/
|
||||
@Json(name = "m.identity_server")
|
||||
val identityServer: WellKnownBaseConfig? = null
|
||||
)
|
@@ -20,6 +20,7 @@ import android.net.Uri
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig.Builder
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||
import okhttp3.CipherSuite
|
||||
import okhttp3.TlsVersion
|
||||
|
||||
@@ -33,10 +34,10 @@ data class HomeServerConnectionConfig(
|
||||
val homeServerUri: Uri,
|
||||
val identityServerUri: Uri? = null,
|
||||
val antiVirusServerUri: Uri? = null,
|
||||
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(),
|
||||
val allowedFingerprints: List<Fingerprint> = emptyList(),
|
||||
val shouldPin: Boolean = false,
|
||||
val tlsVersions: MutableList<TlsVersion>? = null,
|
||||
val tlsCipherSuites: MutableList<CipherSuite>? = null,
|
||||
val tlsVersions: List<TlsVersion>? = null,
|
||||
val tlsCipherSuites: List<CipherSuite>? = null,
|
||||
val shouldAcceptTlsExtensions: Boolean = true,
|
||||
val allowHttpExtension: Boolean = false,
|
||||
val forceUsageTlsVersions: Boolean = false
|
||||
@@ -68,18 +69,14 @@ data class HomeServerConnectionConfig(
|
||||
*/
|
||||
fun withHomeServerUri(hsUri: Uri): Builder {
|
||||
if (hsUri.scheme != "http" && hsUri.scheme != "https") {
|
||||
throw RuntimeException("Invalid home server URI: " + hsUri)
|
||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||
}
|
||||
// ensure trailing /
|
||||
homeServerUri = if (!hsUri.toString().endsWith("/")) {
|
||||
try {
|
||||
val url = hsUri.toString()
|
||||
Uri.parse("$url/")
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||
}
|
||||
} else {
|
||||
hsUri
|
||||
val hsString = hsUri.toString().ensureTrailingSlash()
|
||||
homeServerUri = try {
|
||||
Uri.parse(hsString)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -97,15 +94,11 @@ data class HomeServerConnectionConfig(
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
// ensure trailing /
|
||||
if (!identityServerUri.toString().endsWith("/")) {
|
||||
try {
|
||||
val url = identityServerUri.toString()
|
||||
this.identityServerUri = Uri.parse("$url/")
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
} else {
|
||||
this.identityServerUri = identityServerUri
|
||||
val isString = identityServerUri.toString().ensureTrailingSlash()
|
||||
this.identityServerUri = try {
|
||||
Uri.parse(isString)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
@@ -16,12 +16,10 @@
|
||||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||
|
||||
// Either a LoginFlowResponse, or an error if the homeserver is outdated
|
||||
// Either a list of supported login types, or an error if the homeserver is outdated
|
||||
sealed class LoginFlowResult {
|
||||
data class Success(
|
||||
val loginFlowResponse: LoginFlowResponse,
|
||||
val supportedLoginTypes: List<String>,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String
|
||||
) : LoginFlowResult()
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.auth.data
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
object LoginFlowTypes {
|
||||
const val PASSWORD = "m.login.password"
|
@@ -21,7 +21,48 @@ package im.vector.matrix.android.api.auth.data
|
||||
* You don't have to manually instantiate it.
|
||||
*/
|
||||
data class SessionParams(
|
||||
/**
|
||||
* Please consider using shortcuts instead
|
||||
*/
|
||||
val credentials: Credentials,
|
||||
|
||||
/**
|
||||
* Please consider using shortcuts instead
|
||||
*/
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
|
||||
/**
|
||||
* Set to false if the current token is not valid anymore. Application should not have to use this info.
|
||||
*/
|
||||
val isTokenValid: Boolean
|
||||
)
|
||||
) {
|
||||
/*
|
||||
* Shortcuts. Usually the application should only need to use these shortcuts
|
||||
*/
|
||||
|
||||
/**
|
||||
* The userId of the session (Ex: "@user:domain.org")
|
||||
*/
|
||||
val userId = credentials.userId
|
||||
|
||||
/**
|
||||
* The deviceId of the session (Ex: "ABCDEFGH")
|
||||
*/
|
||||
val deviceId = credentials.deviceId
|
||||
|
||||
/**
|
||||
* The current homeserver Url. It can be different that the homeserver url entered
|
||||
* during login phase, because a redirection may have occurred
|
||||
*/
|
||||
val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
|
||||
|
||||
/**
|
||||
* The current homeserver host
|
||||
*/
|
||||
val homeServerHost = homeServerConnectionConfig.homeServerUri.host
|
||||
|
||||
/**
|
||||
* The default identity server url if any, returned by the homeserver during login phase
|
||||
*/
|
||||
val defaultIdentityServerUrl = homeServerConnectionConfig.identityServerUri?.toString()
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||
@@ -52,31 +53,5 @@ data class WellKnown(
|
||||
val identityServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.integrations")
|
||||
val integrations: Map<String, @JvmSuppressWildcards Any>? = null
|
||||
) {
|
||||
/**
|
||||
* Returns the list of integration managers proposed
|
||||
*/
|
||||
fun getIntegrationManagers(): List<WellKnownManagerConfig> {
|
||||
val managers = ArrayList<WellKnownManagerConfig>()
|
||||
integrations?.get("managers")?.let {
|
||||
(it as? ArrayList<*>)?.let { configs ->
|
||||
configs.forEach { config ->
|
||||
(config as? Map<*, *>)?.let { map ->
|
||||
val apiUrl = map["api_url"] as? String
|
||||
val uiUrl = map["ui_url"] as? String ?: apiUrl
|
||||
if (apiUrl != null
|
||||
&& apiUrl.startsWith("https://")
|
||||
&& uiUrl!!.startsWith("https://")) {
|
||||
managers.add(WellKnownManagerConfig(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return managers
|
||||
}
|
||||
}
|
||||
val integrations: JsonDict? = null
|
||||
)
|
||||
|
@@ -34,6 +34,12 @@ interface LoginWizard {
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Exchange a login token to an access token
|
||||
*/
|
||||
fun loginWithToken(loginToken: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Reset user password
|
||||
*/
|
||||
|
@@ -21,15 +21,12 @@ sealed class Stage(open val mandatory: Boolean) {
|
||||
// m.login.recaptcha
|
||||
data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
|
||||
|
||||
// m.login.oauth2
|
||||
// m.login.email.identity
|
||||
data class Email(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// m.login.msisdn
|
||||
data class Msisdn(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// m.login.token
|
||||
|
||||
// m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username
|
||||
// and a password, the dummy stage has to be done
|
||||
data class Dummy(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.wellknown
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
|
||||
*/
|
||||
sealed class WellknownResult {
|
||||
/**
|
||||
* The provided matrixId is no valid. Unable to extract a domain name.
|
||||
*/
|
||||
object InvalidMatrixId : WellknownResult()
|
||||
|
||||
/**
|
||||
* Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
|
||||
* if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.
|
||||
*/
|
||||
data class Prompt(val homeServerUrl: String,
|
||||
val identityServerUrl: String?,
|
||||
val wellKnown: WellKnown) : WellknownResult()
|
||||
|
||||
/**
|
||||
* Stop the current auto-discovery mechanism. If no more auto-discovery mechanisms are available,
|
||||
* then the client may use other methods of determining the required parameters, such as prompting the user, or using default values.
|
||||
*/
|
||||
object Ignore : WellknownResult()
|
||||
|
||||
/**
|
||||
* Inform the user that auto-discovery failed due to invalid/empty data and PROMPT for the parameter.
|
||||
*/
|
||||
object FailPrompt : WellknownResult()
|
||||
|
||||
/**
|
||||
* Inform the user that auto-discovery did not return any usable URLs. Do not continue further with the current login process.
|
||||
* At this point, valid data was obtained, but no homeserver is available to serve the client.
|
||||
* No further guess should be attempted and the user should make a conscientious decision what to do next.
|
||||
*/
|
||||
object FailError : WellknownResult()
|
||||
}
|
@@ -19,11 +19,11 @@ package im.vector.matrix.android.api.crypto
|
||||
/**
|
||||
* Class to define the parameters used to customize or configure the end-to-end crypto.
|
||||
*/
|
||||
data class MXCryptoConfig(
|
||||
data class MXCryptoConfig constructor(
|
||||
// Tell whether the encryption of the event content is enabled for the invited members.
|
||||
// SDK clients can disable this by settings it to false.
|
||||
// Note that the encryption for the invited members will be blocked if the history visibility is "joined".
|
||||
var enableEncryptionForInvitedMembers: Boolean = true,
|
||||
val enableEncryptionForInvitedMembers: Boolean = true,
|
||||
|
||||
/**
|
||||
* If set to true, the SDK will automatically ignore room key request (gossiping)
|
||||
@@ -31,6 +31,5 @@ data class MXCryptoConfig(
|
||||
* If set to false, the request will be forwarded to the application layer; in this
|
||||
* case the application can decide to prompt the user.
|
||||
*/
|
||||
var discardRoomKeyRequestsFromUntrustedDevices : Boolean = true
|
||||
|
||||
val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true
|
||||
)
|
||||
|
@@ -16,10 +16,15 @@
|
||||
|
||||
package im.vector.matrix.android.api.extensions
|
||||
|
||||
inline fun <A> tryThis(operation: () -> A): A? {
|
||||
import timber.log.Timber
|
||||
|
||||
inline fun <A> tryThis(message: String? = null, operation: () -> A): A? {
|
||||
return try {
|
||||
operation()
|
||||
} catch (any: Throwable) {
|
||||
if (message != null) {
|
||||
Timber.e(any, message)
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,10 @@
|
||||
|
||||
package im.vector.matrix.android.api.failure
|
||||
|
||||
import im.vector.matrix.android.api.extensions.tryThis
|
||||
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import java.io.IOException
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
fun Throwable.is401() =
|
||||
@@ -29,6 +33,7 @@ fun Throwable.isTokenError() =
|
||||
|
||||
fun Throwable.shouldBeRetried(): Boolean {
|
||||
return this is Failure.NetworkConnection
|
||||
|| this is IOException
|
||||
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
|
||||
}
|
||||
|
||||
@@ -37,3 +42,18 @@ fun Throwable.isInvalidPassword(): Boolean {
|
||||
&& error.code == MatrixError.M_FORBIDDEN
|
||||
&& error.message == "Invalid password"
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
|
||||
*/
|
||||
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
|
||||
return if (this is Failure.OtherServerError && this.httpCode == 401) {
|
||||
tryThis {
|
||||
MoshiProvider.providesMoshi()
|
||||
.adapter(RegistrationFlowResponse::class.java)
|
||||
.fromJson(this.errorBody)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.failure
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
@@ -32,9 +33,11 @@ import java.io.IOException
|
||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure()
|
||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
|
||||
|
||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody"))
|
||||
|
||||
|
@@ -16,8 +16,11 @@
|
||||
|
||||
package im.vector.matrix.android.api.failure
|
||||
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
|
||||
// This class will be sent to the bus
|
||||
sealed class GlobalError {
|
||||
data class InvalidToken(val softLogout: Boolean) : GlobalError()
|
||||
data class ConsentNotGivenError(val consentUri: String) : GlobalError()
|
||||
data class CertificateError(val fingerprint: Fingerprint) : GlobalError()
|
||||
}
|
||||
|
@@ -39,7 +39,10 @@ data class MatrixError(
|
||||
// For M_LIMIT_EXCEEDED
|
||||
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
||||
// For M_UNKNOWN_TOKEN
|
||||
@Json(name = "soft_logout") val isSoftLogout: Boolean = false
|
||||
@Json(name = "soft_logout") val isSoftLogout: Boolean = false,
|
||||
// For M_INVALID_PEPPER
|
||||
// {"error": "pepper does not match 'erZvr'", "lookup_pepper": "pQgMS", "algorithm": "sha256", "errcode": "M_INVALID_PEPPER"}
|
||||
@Json(name = "lookup_pepper") val newLookupPepper: String? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -129,6 +132,11 @@ data class MatrixError(
|
||||
/** (Not documented yet) */
|
||||
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
|
||||
const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED"
|
||||
|
||||
// For identity service
|
||||
const val M_INVALID_PEPPER = "M_INVALID_PEPPER"
|
||||
|
||||
// Possible value for "limit_type"
|
||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||
}
|
||||
|
@@ -1,25 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.powerlevels
|
||||
package im.vector.matrix.android.api.legacy
|
||||
|
||||
object PowerLevelsConstants {
|
||||
interface LegacySessionImporter {
|
||||
|
||||
const val DEFAULT_ROOM_ADMIN_LEVEL = 100
|
||||
const val DEFAULT_ROOM_MODERATOR_LEVEL = 50
|
||||
const val DEFAULT_ROOM_USER_LEVEL = 0
|
||||
/**
|
||||
* Will eventually import a session created by the legacy app.
|
||||
*/
|
||||
fun process()
|
||||
}
|
@@ -87,14 +87,13 @@ class EventMatchCondition(
|
||||
// Very simple glob to regexp converter
|
||||
private fun simpleGlobToRegExp(glob: String): String {
|
||||
var out = "" // "^"
|
||||
for (i in 0 until glob.length) {
|
||||
val c = glob[i]
|
||||
when (c) {
|
||||
for (element in glob) {
|
||||
when (element) {
|
||||
'*' -> out += ".*"
|
||||
'?' -> out += '.'.toString()
|
||||
'.' -> out += "\\."
|
||||
'\\' -> out += "\\\\"
|
||||
else -> out += c
|
||||
else -> out += element
|
||||
}
|
||||
}
|
||||
out += "" // '$'.toString()
|
||||
|
@@ -39,6 +39,6 @@ class SenderNotificationPermissionCondition(
|
||||
|
||||
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
||||
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
|
||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevel(event.senderId) >= powerLevelsHelper.notificationLevel(key)
|
||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevelsHelper.notificationLevel(key)
|
||||
}
|
||||
}
|
||||
|
@@ -25,8 +25,8 @@ sealed class QueryStringValue {
|
||||
object IsNotNull : QueryStringValue()
|
||||
object IsEmpty : QueryStringValue()
|
||||
object IsNotEmpty : QueryStringValue()
|
||||
data class Equals(val string: String, val case: Case) : QueryStringValue()
|
||||
data class Contains(val string: String, val case: Case) : QueryStringValue()
|
||||
data class Equals(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||
data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||
|
||||
enum class Case {
|
||||
SENSITIVE,
|
||||
|
@@ -20,10 +20,13 @@ import androidx.lifecycle.LiveData
|
||||
|
||||
interface InitialSyncProgressService {
|
||||
|
||||
fun getInitialSyncProgressStatus() : LiveData<Status?>
|
||||
fun getInitialSyncProgressStatus(): LiveData<Status>
|
||||
|
||||
data class Status(
|
||||
@StringRes val statusText: Int,
|
||||
val percentProgress: Int = 0
|
||||
)
|
||||
sealed class Status {
|
||||
object Idle : Status()
|
||||
data class Progressing(
|
||||
@StringRes val statusText: Int,
|
||||
val percentProgress: Int = 0
|
||||
) : Status()
|
||||
}
|
||||
}
|
||||
|
@@ -24,12 +24,16 @@ import im.vector.matrix.android.api.pushrules.PushRuleService
|
||||
import im.vector.matrix.android.api.session.account.AccountService
|
||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||
import im.vector.matrix.android.api.session.cache.CacheService
|
||||
import im.vector.matrix.android.api.session.call.CallSignalingService
|
||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.file.ContentDownloadStateTracker
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.identity.IdentityService
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
@@ -39,7 +43,10 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
|
||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.typing.TypingUsersTracker
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||
|
||||
/**
|
||||
* This interface defines interactions with a session.
|
||||
@@ -53,7 +60,7 @@ interface Session :
|
||||
CacheService,
|
||||
SignOutService,
|
||||
FilterService,
|
||||
FileService,
|
||||
TermsService,
|
||||
ProfileService,
|
||||
PushRuleService,
|
||||
PushersService,
|
||||
@@ -77,7 +84,7 @@ interface Session :
|
||||
* Useful shortcut to get access to the userId
|
||||
*/
|
||||
val myUserId: String
|
||||
get() = sessionParams.credentials.userId
|
||||
get() = sessionParams.userId
|
||||
|
||||
/**
|
||||
* The sessionId
|
||||
@@ -120,6 +127,12 @@ interface Session :
|
||||
*/
|
||||
fun getSyncStateLive(): LiveData<SyncState>
|
||||
|
||||
/**
|
||||
* This method returns the current sync state.
|
||||
* @return the current [SyncState].
|
||||
*/
|
||||
fun getSyncState(): SyncState
|
||||
|
||||
/**
|
||||
* This methods return true if an initial sync has been processed
|
||||
*/
|
||||
@@ -140,11 +153,46 @@ interface Session :
|
||||
*/
|
||||
fun contentUploadProgressTracker(): ContentUploadStateTracker
|
||||
|
||||
/**
|
||||
* Returns the TypingUsersTracker associated with the session
|
||||
*/
|
||||
fun typingUsersTracker(): TypingUsersTracker
|
||||
|
||||
/**
|
||||
* Returns the ContentDownloadStateTracker associated with the session
|
||||
*/
|
||||
fun contentDownloadProgressTracker(): ContentDownloadStateTracker
|
||||
|
||||
/**
|
||||
* Returns the cryptoService associated with the session
|
||||
*/
|
||||
fun cryptoService(): CryptoService
|
||||
|
||||
/**
|
||||
* Returns the identity service associated with the session
|
||||
*/
|
||||
fun identityService(): IdentityService
|
||||
|
||||
/**
|
||||
* Returns the widget service associated with the session
|
||||
*/
|
||||
fun widgetService(): WidgetService
|
||||
|
||||
/**
|
||||
* Returns the integration manager service associated with the session
|
||||
*/
|
||||
fun integrationManagerService(): IntegrationManagerService
|
||||
|
||||
/**
|
||||
* Returns the call signaling service associated with the session
|
||||
*/
|
||||
fun callSignalingService(): CallSignalingService
|
||||
|
||||
/**
|
||||
* Returns the file download service associated with the session
|
||||
*/
|
||||
fun fileService(): FileService
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
|
@@ -23,11 +23,28 @@ import im.vector.matrix.android.api.util.Cancelable
|
||||
* This interface defines methods to manage the account. It's implemented at the session level.
|
||||
*/
|
||||
interface AccountService {
|
||||
|
||||
/**
|
||||
* Ask the homeserver to change the password.
|
||||
* @param password Current password.
|
||||
* @param newPassword New password
|
||||
*/
|
||||
fun changePassword(password: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Deactivate the account.
|
||||
*
|
||||
* This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register
|
||||
* the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account
|
||||
* details from your identity server. <b>This action is irreversible</b>.\n\nDeactivating your account <b>does not by default
|
||||
* cause us to forget messages you have sent</b>. If you would like us to forget your messages, please tick the box below.
|
||||
*
|
||||
* Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not
|
||||
* be shared with any new or unregistered users, but registered users who already have access to these messages will still
|
||||
* have access to their copy.
|
||||
*
|
||||
* @param password the account password
|
||||
* @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see
|
||||
* an incomplete view of conversations
|
||||
*/
|
||||
fun deactivateAccount(password: String, eraseAllData: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
||||
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.call
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface CallSignalingService {
|
||||
|
||||
fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable
|
||||
|
||||
/**
|
||||
* Create an outgoing call
|
||||
*/
|
||||
fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall
|
||||
|
||||
fun addCallListener(listener: CallsListener)
|
||||
|
||||
fun removeCallListener(listener: CallsListener)
|
||||
|
||||
fun getCallWithId(callId: String) : MxCall?
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.call
|
||||
|
||||
import org.webrtc.PeerConnection
|
||||
|
||||
sealed class CallState {
|
||||
|
||||
/** Idle, setting up objects */
|
||||
object Idle : CallState()
|
||||
|
||||
/** Dialing. Outgoing call is signaling the remote peer */
|
||||
object Dialing : CallState()
|
||||
|
||||
/** Local ringing. Incoming call offer received */
|
||||
object LocalRinging : CallState()
|
||||
|
||||
/** Answering. Incoming call is responding to remote peer */
|
||||
object Answering : CallState()
|
||||
|
||||
/**
|
||||
* Connected. Incoming/Outgoing call, ice layer connecting or connected
|
||||
* Notice that the PeerState failed is not always final, if you switch network, new ice candidtates
|
||||
* could be exchanged, and the connection could go back to connected
|
||||
* */
|
||||
data class Connected(val iceConnectionState: PeerConnection.PeerConnectionState) : CallState()
|
||||
|
||||
/** Terminated. Incoming/Outgoing call, the call is terminated */
|
||||
object Terminated : CallState()
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.call
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.call.CallAnswerContent
|
||||
import im.vector.matrix.android.api.session.room.model.call.CallCandidatesContent
|
||||
import im.vector.matrix.android.api.session.room.model.call.CallHangupContent
|
||||
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||
|
||||
interface CallsListener {
|
||||
/**
|
||||
* Called when there is an incoming call within the room.
|
||||
*/
|
||||
fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent)
|
||||
|
||||
fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent)
|
||||
|
||||
/**
|
||||
* An outgoing call is started.
|
||||
*/
|
||||
fun onCallAnswerReceived(callAnswerContent: CallAnswerContent)
|
||||
|
||||
/**
|
||||
* Called when a called has been hung up
|
||||
*/
|
||||
fun onCallHangupReceived(callHangupContent: CallHangupContent)
|
||||
|
||||
fun onCallManagedByOtherSession(callId: String)
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.call
|
||||
|
||||
import org.webrtc.EglBase
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* The root [EglBase] instance shared by the entire application for
|
||||
* the sake of reducing the utilization of system resources (such as EGL
|
||||
* contexts)
|
||||
* by performing a runtime check.
|
||||
*/
|
||||
object EglUtils {
|
||||
|
||||
// TODO how do we release that?
|
||||
|
||||
/**
|
||||
* Lazily creates and returns the one and only [EglBase] which will
|
||||
* serve as the root for all contexts that are needed.
|
||||
*/
|
||||
@get:Synchronized var rootEglBase: EglBase? = null
|
||||
get() {
|
||||
if (field == null) {
|
||||
val configAttributes = EglBase.CONFIG_PLAIN
|
||||
try {
|
||||
field = EglBase.createEgl14(configAttributes)
|
||||
?: EglBase.createEgl10(configAttributes) // Fall back to EglBase10.
|
||||
} catch (ex: Throwable) {
|
||||
Timber.e(ex, "Failed to create EglBase")
|
||||
}
|
||||
}
|
||||
return field
|
||||
}
|
||||
private set
|
||||
|
||||
val rootEglBaseContext: EglBase.Context?
|
||||
get() {
|
||||
val eglBase = rootEglBase
|
||||
return eglBase?.eglBaseContext
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.call
|
||||
|
||||
import org.webrtc.IceCandidate
|
||||
import org.webrtc.SessionDescription
|
||||
|
||||
interface MxCallDetail {
|
||||
val callId: String
|
||||
val isOutgoing: Boolean
|
||||
val roomId: String
|
||||
val otherUserId: String
|
||||
val isVideoCall: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Define both an incoming call and on outgoing call
|
||||
*/
|
||||
interface MxCall : MxCallDetail {
|
||||
|
||||
var state: CallState
|
||||
/**
|
||||
* Pick Up the incoming call
|
||||
* It has no effect on outgoing call
|
||||
*/
|
||||
fun accept(sdp: SessionDescription)
|
||||
|
||||
/**
|
||||
* Reject an incoming call
|
||||
* It's an alias to hangUp
|
||||
*/
|
||||
fun reject() = hangUp()
|
||||
|
||||
/**
|
||||
* End the call
|
||||
*/
|
||||
fun hangUp()
|
||||
|
||||
/**
|
||||
* Start a call
|
||||
* Send offer SDP to the other participant.
|
||||
*/
|
||||
fun offerSdp(sdp: SessionDescription)
|
||||
|
||||
/**
|
||||
* Send Ice candidate to the other participant.
|
||||
*/
|
||||
fun sendLocalIceCandidates(candidates: List<IceCandidate>)
|
||||
|
||||
/**
|
||||
* Send removed ICE candidates to the other participant.
|
||||
*/
|
||||
fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>)
|
||||
|
||||
fun addListener(listener: StateListener)
|
||||
fun removeListener(listener: StateListener)
|
||||
|
||||
interface StateListener {
|
||||
fun onStateUpdate(call: MxCall)
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.call
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
// TODO Should not be exposed
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-voip-turnserver
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TurnServerResponse(
|
||||
/**
|
||||
* Required. The username to use.
|
||||
*/
|
||||
@Json(name = "username") val username: String?,
|
||||
|
||||
/**
|
||||
* Required. The password to use.
|
||||
*/
|
||||
@Json(name = "password") val password: String?,
|
||||
|
||||
/**
|
||||
* Required. A list of TURN URIs
|
||||
*/
|
||||
@Json(name = "uris") val uris: List<String>?,
|
||||
|
||||
/**
|
||||
* Required. The time-to-live in seconds
|
||||
*/
|
||||
@Json(name = "ttl") val ttl: Int?
|
||||
)
|
@@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
@@ -98,7 +99,11 @@ interface CryptoService {
|
||||
|
||||
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||
|
||||
fun getMyDevicesInfo() : List<DeviceInfo>
|
||||
|
||||
fun getLiveMyDevicesInfo() : LiveData<List<DeviceInfo>>
|
||||
|
||||
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||
|
||||
@@ -111,6 +116,8 @@ interface CryptoService {
|
||||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
||||
|
||||
fun discardOutboundSession(roomId: String)
|
||||
|
||||
@Throws(MXCryptoError::class)
|
||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
|
||||
@@ -134,7 +141,13 @@ interface CryptoService {
|
||||
|
||||
fun removeSessionListener(listener: NewSessionListener)
|
||||
|
||||
fun getOutgoingRoomKeyRequest(): List<OutgoingRoomKeyRequest>
|
||||
fun getIncomingRoomKeyRequest(): List<IncomingRoomKeyRequest>
|
||||
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||
|
||||
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||
|
||||
fun getGossipingEventsTrail(): List<Event>
|
||||
|
||||
// For testing shared session
|
||||
fun getSharedWithInfo(roomId: String?, sessionId: String) : MXUsersDevicesMap<Int>
|
||||
fun getWithHeldMegolmSession(roomId: String, sessionId: String) : RoomKeyWithHeldContent?
|
||||
}
|
||||
|
@@ -59,7 +59,8 @@ sealed class MXCryptoError : Throwable() {
|
||||
MISSING_PROPERTY,
|
||||
OLM,
|
||||
UNKNOWN_DEVICES,
|
||||
UNKNOWN_MESSAGE_INDEX
|
||||
UNKNOWN_MESSAGE_INDEX,
|
||||
KEYS_WITHHELD
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@@ -41,11 +41,13 @@ interface CrossSigningService {
|
||||
* Users needs to enter credentials
|
||||
*/
|
||||
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
||||
callback: MatrixCallback<Unit>? = null)
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
|
||||
|
||||
fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
||||
uskKeyPrivateKey: String?,
|
||||
sskPrivateKey: String?) : UserTrustResult
|
||||
sskPrivateKey: String?): UserTrustResult
|
||||
|
||||
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||
|
||||
@@ -55,6 +57,8 @@ interface CrossSigningService {
|
||||
|
||||
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
|
||||
|
||||
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
|
||||
|
||||
fun canCrossSign(): Boolean
|
||||
|
||||
fun trustUser(otherUserId: String,
|
||||
@@ -72,6 +76,8 @@ interface CrossSigningService {
|
||||
otherDeviceId: String,
|
||||
locallyTrusted: Boolean?): DeviceTrustResult
|
||||
|
||||
// FIXME Those method do not have to be in the service
|
||||
fun onSecretMSKGossip(mskPrivateKey: String)
|
||||
fun onSecretSSKGossip(sskPrivateKey: String)
|
||||
fun onSecretUSKGossip(uskPrivateKey: String)
|
||||
}
|
||||
|
@@ -82,6 +82,9 @@ data class Event(
|
||||
@Transient
|
||||
var mCryptoError: MXCryptoError.ErrorType? = null
|
||||
|
||||
@Transient
|
||||
var mCryptoErrorReason: String? = null
|
||||
|
||||
@Transient
|
||||
var sendState: SendState = SendState.UNKNOWN
|
||||
|
||||
@@ -98,7 +101,7 @@ data class Event(
|
||||
* @return true if event is state event.
|
||||
*/
|
||||
fun isStateEvent(): Boolean {
|
||||
return EventType.isStateEvent(getClearType())
|
||||
return stateKey != null
|
||||
}
|
||||
|
||||
// ==============================================================================================================
|
||||
@@ -162,6 +165,8 @@ data class Event(
|
||||
*/
|
||||
fun isRedactedBySameUser() = senderId == unsignedData?.redactedEvent?.senderId
|
||||
|
||||
fun resolvedPrevContent(): Content? = prevContent ?: unsignedData?.prevContent
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
@@ -180,6 +185,7 @@ data class Event(
|
||||
if (redacts != other.redacts) return false
|
||||
if (mxDecryptionResult != other.mxDecryptionResult) return false
|
||||
if (mCryptoError != other.mCryptoError) return false
|
||||
if (mCryptoErrorReason != other.mCryptoErrorReason) return false
|
||||
if (sendState != other.sendState) return false
|
||||
|
||||
return true
|
||||
@@ -198,6 +204,7 @@ data class Event(
|
||||
result = 31 * result + (redacts?.hashCode() ?: 0)
|
||||
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
|
||||
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
|
||||
result = 31 * result + (mCryptoErrorReason?.hashCode() ?: 0)
|
||||
result = 31 * result + sendState.hashCode()
|
||||
return result
|
||||
}
|
||||
@@ -220,3 +227,19 @@ fun Event.isImageMessage(): Boolean {
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.isVideoMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_VIDEO -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.isFileMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_FILE -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
@@ -38,6 +38,8 @@ object EventType {
|
||||
|
||||
// State Events
|
||||
|
||||
const val STATE_ROOM_WIDGET_LEGACY = "im.vector.modular.widgets"
|
||||
const val STATE_ROOM_WIDGET = "m.widget"
|
||||
const val STATE_ROOM_NAME = "m.room.name"
|
||||
const val STATE_ROOM_TOPIC = "m.room.topic"
|
||||
const val STATE_ROOM_AVATAR = "m.room.avatar"
|
||||
@@ -56,7 +58,6 @@ object EventType {
|
||||
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
||||
|
||||
// Call Events
|
||||
|
||||
const val CALL_INVITE = "m.call.invite"
|
||||
const val CALL_CANDIDATES = "m.call.candidates"
|
||||
const val CALL_ANSWER = "m.call.answer"
|
||||
@@ -65,6 +66,7 @@ object EventType {
|
||||
// Key share events
|
||||
const val ROOM_KEY_REQUEST = "m.room_key_request"
|
||||
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
|
||||
const val ROOM_KEY_WITHHELD = "org.matrix.room_key.withheld"
|
||||
|
||||
const val REQUEST_SECRET = "m.secret.request"
|
||||
const val SEND_SECRET = "m.secret.send"
|
||||
@@ -81,28 +83,8 @@ object EventType {
|
||||
// Relation Events
|
||||
const val REACTION = "m.reaction"
|
||||
|
||||
private val STATE_EVENTS = listOf(
|
||||
STATE_ROOM_NAME,
|
||||
STATE_ROOM_TOPIC,
|
||||
STATE_ROOM_AVATAR,
|
||||
STATE_ROOM_MEMBER,
|
||||
STATE_ROOM_THIRD_PARTY_INVITE,
|
||||
STATE_ROOM_CREATE,
|
||||
STATE_ROOM_JOIN_RULES,
|
||||
STATE_ROOM_GUEST_ACCESS,
|
||||
STATE_ROOM_POWER_LEVELS,
|
||||
STATE_ROOM_ALIASES,
|
||||
STATE_ROOM_TOMBSTONE,
|
||||
STATE_ROOM_CANONICAL_ALIAS,
|
||||
STATE_ROOM_HISTORY_VISIBILITY,
|
||||
STATE_ROOM_RELATED_GROUPS,
|
||||
STATE_ROOM_PINNED_EVENT,
|
||||
STATE_ROOM_ENCRYPTION
|
||||
)
|
||||
|
||||
fun isStateEvent(type: String): Boolean {
|
||||
return STATE_EVENTS.contains(type)
|
||||
}
|
||||
// Unwedging
|
||||
internal const val DUMMY = "m.dummy"
|
||||
|
||||
fun isCallEvent(type: String): Boolean {
|
||||
return type == CALL_INVITE
|
||||
|
@@ -26,5 +26,5 @@ object RelationType {
|
||||
/** Lets you define an event which references an existing event.*/
|
||||
const val REFERENCE = "m.reference"
|
||||
/** Lets you define an event which adds a response to an existing event.*/
|
||||
const val RESPONSE = "m.response"
|
||||
const val RESPONSE = "org.matrix.response"
|
||||
}
|
||||
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.file
|
||||
|
||||
interface ContentDownloadStateTracker {
|
||||
fun track(key: String, updateListener: UpdateListener)
|
||||
fun unTrack(key: String, updateListener: UpdateListener)
|
||||
fun clear()
|
||||
|
||||
sealed class State {
|
||||
object Idle : State()
|
||||
data class Downloading(val current: Long, val total: Long, val indeterminate: Boolean) : State()
|
||||
object Decrypting : State()
|
||||
object Success : State()
|
||||
data class Failure(val errorCode: Int) : State()
|
||||
}
|
||||
|
||||
interface UpdateListener {
|
||||
fun onDownloadStateUpdate(state: State)
|
||||
}
|
||||
}
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.file
|
||||
|
||||
import android.net.Uri
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
@@ -31,26 +32,58 @@ interface FileService {
|
||||
* Download file in external storage
|
||||
*/
|
||||
TO_EXPORT,
|
||||
|
||||
/**
|
||||
* Download file in cache
|
||||
*/
|
||||
FOR_INTERNAL_USE,
|
||||
|
||||
/**
|
||||
* Download file in file provider path
|
||||
*/
|
||||
FOR_EXTERNAL_SHARE
|
||||
}
|
||||
|
||||
enum class FileState {
|
||||
IN_CACHE,
|
||||
DOWNLOADING,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file.
|
||||
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
|
||||
* You can pass the eventId
|
||||
* Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
|
||||
*/
|
||||
fun downloadFile(
|
||||
downloadMode: DownloadMode,
|
||||
id: String,
|
||||
fileName: String,
|
||||
mimeType: String?,
|
||||
url: String?,
|
||||
elementToDecrypt: ElementToDecrypt?,
|
||||
callback: MatrixCallback<File>): Cancelable
|
||||
|
||||
fun isFileInCache(mxcUrl: String, mimeType: String?): Boolean
|
||||
|
||||
/**
|
||||
* Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
* (if not other app won't be able to access it)
|
||||
*/
|
||||
fun getTemporarySharableURI(mxcUrl: String, mimeType: String?): Uri?
|
||||
|
||||
/**
|
||||
* Get information on the given file.
|
||||
* Mimetype should be the same one as passed to downloadFile (limitation for now)
|
||||
*/
|
||||
fun fileState(mxcUrl: String, mimeType: String?): FileState
|
||||
|
||||
/**
|
||||
* Clears all the files downloaded by the service
|
||||
*/
|
||||
fun clearCache()
|
||||
|
||||
/**
|
||||
* Get size of cached files
|
||||
*/
|
||||
fun getCacheSize(): Int
|
||||
}
|
||||
|
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.file
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
|
||||
/**
|
||||
* We have to declare our own file provider to avoid collision with apps using the sdk
|
||||
* and having their own
|
||||
*/
|
||||
class MatrixSDKFileProvider : FileProvider() {
|
||||
override fun getType(uri: Uri): String? {
|
||||
return super.getType(uri) ?: "plain/text"
|
||||
}
|
||||
}
|
@@ -24,7 +24,15 @@ data class HomeServerCapabilities(
|
||||
/**
|
||||
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
|
||||
*/
|
||||
val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN
|
||||
val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
||||
/**
|
||||
* Last version identity server and binding supported
|
||||
*/
|
||||
val lastVersionIdentityServerSupported: Boolean = false,
|
||||
/**
|
||||
* Default identity server url, provided in Wellknown
|
||||
*/
|
||||
val defaultIdentityServerUrl: String? = null
|
||||
) {
|
||||
companion object {
|
||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||
|
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
data class FoundThreePid(
|
||||
val threePid: ThreePid,
|
||||
val matrixId: String
|
||||
)
|
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* Provides access to the identity server configuration and services identity server can provide
|
||||
*/
|
||||
interface IdentityService {
|
||||
/**
|
||||
* Return the default identity server of the user, which may have been provided at login time by the homeserver,
|
||||
* or by the Well-known setup of the homeserver
|
||||
* It may be different from the current configured identity server
|
||||
*/
|
||||
fun getDefaultIdentityServer(): String?
|
||||
|
||||
/**
|
||||
* Return the current identity server URL used by this account. Returns null if no identity server is configured.
|
||||
*/
|
||||
fun getCurrentIdentityServerUrl(): String?
|
||||
|
||||
/**
|
||||
* Check if the identity server is valid
|
||||
* See https://matrix.org/docs/spec/identity_service/latest#status-check
|
||||
* RiotX SDK only supports identity server API v2
|
||||
*/
|
||||
fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Update the identity server url.
|
||||
* If successful, any previous identity server will be disconnected.
|
||||
* In case of error, any previous identity server will remain configured.
|
||||
* @param url the new url.
|
||||
* @param callback will notify the user if change is successful. The String will be the final url of the identity server.
|
||||
* The SDK can prepend "https://" for instance.
|
||||
*/
|
||||
fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable
|
||||
|
||||
/**
|
||||
* Disconnect (logout) from the current identity server
|
||||
*/
|
||||
fun disconnect(callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid
|
||||
*/
|
||||
fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* This will cancel a pending binding of threePid.
|
||||
*/
|
||||
fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid
|
||||
*/
|
||||
fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Submit the code that the identity server has sent to the user (in email or SMS)
|
||||
* Once successful, you will have to call [finalizeBindThreePid]
|
||||
* @param code the code sent to the user
|
||||
*/
|
||||
fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* This will perform the actual association of ThreePid and Matrix account
|
||||
*/
|
||||
fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Unbind a threePid
|
||||
* The request will actually be done on the homeserver
|
||||
*/
|
||||
fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Search MatrixId of users providing email and phone numbers
|
||||
*/
|
||||
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
||||
|
||||
/**
|
||||
* Get the status of the current user's threePid
|
||||
* A lookup will be performed, but also pending binding state will be restored
|
||||
*
|
||||
* @param threePids the list of threePid the user owns (retrieved form the homeserver)
|
||||
* @param callback onSuccess will be called with a map of ThreePid -> SharedState
|
||||
*/
|
||||
fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable
|
||||
|
||||
fun addListener(listener: IdentityServiceListener)
|
||||
fun removeListener(listener: IdentityServiceListener)
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
|
||||
sealed class IdentityServiceError : Failure.FeatureFailure() {
|
||||
object OutdatedIdentityServer : IdentityServiceError()
|
||||
object OutdatedHomeServer : IdentityServiceError()
|
||||
object NoIdentityServerConfigured : IdentityServiceError()
|
||||
object TermsNotSignedException : IdentityServiceError()
|
||||
object BulkLookupSha256NotSupported : IdentityServiceError()
|
||||
object BindingError : IdentityServiceError()
|
||||
object NoCurrentBindingError : IdentityServiceError()
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
data class WellKnownManagerConfig(
|
||||
val apiUrl : String,
|
||||
val uiUrl: String
|
||||
)
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
interface IdentityServiceListener {
|
||||
fun onIdentityServerChange()
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
enum class SharedState {
|
||||
SHARED,
|
||||
NOT_SHARED,
|
||||
BINDING_IN_PROGRESS
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import im.vector.matrix.android.internal.session.profile.ThirdPartyIdentifier
|
||||
|
||||
sealed class ThreePid(open val value: String) {
|
||||
data class Email(val email: String) : ThreePid(email)
|
||||
data class Msisdn(val msisdn: String) : ThreePid(msisdn)
|
||||
}
|
||||
|
||||
internal fun ThreePid.toMedium(): String {
|
||||
return when (this) {
|
||||
is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL
|
||||
is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(NumberParseException::class)
|
||||
internal fun ThreePid.Msisdn.getCountryCode(): String {
|
||||
return with(PhoneNumberUtil.getInstance()) {
|
||||
getRegionCodeForCountryCode(parse("+$msisdn", null).countryCode)
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.integrationmanager
|
||||
|
||||
/**
|
||||
* This class holds configuration of integration manager.
|
||||
*/
|
||||
data class IntegrationManagerConfig(
|
||||
val uiUrl: String,
|
||||
val restUrl: String,
|
||||
val kind: Kind
|
||||
) {
|
||||
|
||||
// Order matters, first is preferred
|
||||
/**
|
||||
* The kind of config, it will reflect where the data is coming from.
|
||||
*/
|
||||
enum class Kind {
|
||||
/**
|
||||
* Defined in UserAccountData
|
||||
*/
|
||||
ACCOUNT,
|
||||
/**
|
||||
* Defined in Wellknown
|
||||
*/
|
||||
HOMESERVER,
|
||||
/**
|
||||
* Fallback value, hardcoded by the SDK
|
||||
*/
|
||||
DEFAULT
|
||||
}
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.integrationmanager
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This is the entry point to manage integration. You can grab an instance of this service through an active session.
|
||||
*/
|
||||
interface IntegrationManagerService {
|
||||
|
||||
/**
|
||||
* This listener allow you to observe change related to integrations.
|
||||
*/
|
||||
interface Listener {
|
||||
/**
|
||||
* Is called whenever integration is enabled or disabled, comes from user account data.
|
||||
*/
|
||||
fun onIsEnabledChanged(enabled: Boolean) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called whenever configs from user account data or wellknown are updated.
|
||||
*/
|
||||
fun onConfigurationChanged(configs: List<IntegrationManagerConfig>) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called whenever widget permissions from user account data are updated.
|
||||
*/
|
||||
fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to observe changes.
|
||||
*/
|
||||
fun addListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Removes a previously added listener.
|
||||
*/
|
||||
fun removeListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Return the list of current configurations, sorted by kind. First one is preferred.
|
||||
* See [IntegrationManagerConfig.Kind]
|
||||
*/
|
||||
fun getOrderedConfigs(): List<IntegrationManagerConfig>
|
||||
|
||||
/**
|
||||
* Return the preferred current configuration.
|
||||
* See [IntegrationManagerConfig.Kind]
|
||||
*/
|
||||
fun getPreferredConfig(): IntegrationManagerConfig
|
||||
|
||||
/**
|
||||
* Returns true if integration is enabled, false otherwise.
|
||||
*/
|
||||
fun isIntegrationEnabled(): Boolean
|
||||
|
||||
/**
|
||||
* Offers to enable or disable the integration.
|
||||
* @param enable the param to change
|
||||
* @param callback the matrix callback to listen for result.
|
||||
* @return Cancelable
|
||||
*/
|
||||
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Offers to allow or disallow a widget.
|
||||
* @param stateEventId the eventId of the state event defining the widget.
|
||||
* @param allowed the param to change
|
||||
* @param callback the matrix callback to listen for result.
|
||||
* @return Cancelable
|
||||
*/
|
||||
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Returns true if the widget is allowed, false otherwise.
|
||||
* @param stateEventId the eventId of the state event defining the widget.
|
||||
*/
|
||||
fun isWidgetAllowed(stateEventId: String): Boolean
|
||||
|
||||
/**
|
||||
* Offers to allow or disallow a native widget domain.
|
||||
* @param widgetType the widget type to check for
|
||||
* @param domain the domain to check for
|
||||
*/
|
||||
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Returns true if the widget domain is allowed, false otherwise.
|
||||
* @param widgetType the widget type to check for
|
||||
* @param domain the domain to check for
|
||||
*/
|
||||
fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user