mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
1323 Commits
v0.17.0
...
feature/ne
Author | SHA1 | Date | |
---|---|---|---|
|
c3bdc6127c | ||
|
2d6ae3f494 | ||
|
920e36a554 | ||
|
a13a39495f | ||
|
a98bc23691 | ||
|
185c4eb764 | ||
|
625a1abe84 | ||
|
4e1a827405 | ||
|
3f44056243 | ||
|
d075cbf69b | ||
|
35fed2676a | ||
|
9754e26e5f | ||
|
af9295723c | ||
|
55993aff04 | ||
|
66c5a35f36 | ||
|
c1260dcb9b | ||
|
f4e7405d92 | ||
|
c15cc34bfd | ||
|
16f32da647 | ||
|
4c34d73501 | ||
|
df1bd62f47 | ||
|
3ced179fbb | ||
|
957ceff87c | ||
|
85fcc2eab7 | ||
|
e7143b53d5 | ||
|
b728e10616 | ||
|
8ffa0061e9 | ||
|
b767c2fa54 | ||
|
aeb41bc516 | ||
|
178bdff62a | ||
|
3e79da6a79 | ||
|
ef2fcd60d7 | ||
|
55b61775e8 | ||
|
07e57b1498 | ||
|
b522c9f62f | ||
|
04a7c57d64 | ||
|
367da0c78f | ||
|
057c21f7d0 | ||
|
af8ab57e60 | ||
|
409b6b807e | ||
|
4a454f0817 | ||
|
6b806922ee | ||
|
64a67b57b8 | ||
|
76bcf9dcf7 | ||
|
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 | ||
|
af81a52746 | ||
|
86b4c9ac73 | ||
|
1df3b4e18f | ||
|
fa204eca3b | ||
|
2c34fe2dc3 | ||
|
9c34187391 | ||
|
ff39b22686 | ||
|
601e11980f | ||
|
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 | ||
|
671c1259af | ||
|
3d07ccd98e | ||
|
03b9774c56 | ||
|
0f1ddee71c | ||
|
855efa93cc | ||
|
d0f776a9cf | ||
|
da66e38c68 | ||
|
a4ba8c152d | ||
|
9b320ed3c7 | ||
|
c854491248 | ||
|
5755d5bfaa | ||
|
ff320fec55 | ||
|
8c8a84b039 | ||
|
045e3d7bae | ||
|
491f0e6032 | ||
|
3163bc8b80 | ||
|
eca3bf0817 | ||
|
63355ca256 | ||
|
c39a0e4fd5 | ||
|
59280ed18e | ||
|
c1acb1af66 | ||
|
8a4f0a0c00 | ||
|
a6368c473e | ||
|
3615ca6b95 | ||
|
ddb00ba23a | ||
|
91cf4b647d | ||
|
f989eed8b0 | ||
|
4d296ddc09 | ||
|
6186c22e02 | ||
|
13cd13a42f | ||
|
a42eb42178 | ||
|
7924ef207c | ||
|
5900245018 | ||
|
00c239bc42 | ||
|
0cb43eef51 | ||
|
41a8f40241 | ||
|
a8641ef879 | ||
|
4d207e6acd | ||
|
1227de3f9c | ||
|
6cad129625 | ||
|
9ccf51fbc0 | ||
|
990867204e | ||
|
5795b7e063 | ||
|
b612a7e63c | ||
|
50c73b68aa | ||
|
c7ac5e2293 | ||
|
43cb1fe68b | ||
|
f807de9a83 | ||
|
754f220596 | ||
|
3f7ca8669a | ||
|
28c6921a0a | ||
|
26bb8ce2be | ||
|
8434f9326e | ||
|
68f93c6c31 | ||
|
7961423556 | ||
|
ac07fb47d7 | ||
|
fbcbd6def5 | ||
|
3fe15f2d45 | ||
|
968377a5be | ||
|
5652140f5d | ||
|
e97c95f40a | ||
|
7ac5f58f32 | ||
|
ce2f4e163d | ||
|
cc94b6cf7d | ||
|
ab3cc90ed5 | ||
|
614127e46b | ||
|
66fc38ad4b | ||
|
f68e84d9da | ||
|
2b2e6dd6f8 | ||
|
621e78a864 | ||
|
828e972c74 | ||
|
15bd7d1c5b | ||
|
79e81dbdde | ||
|
f93f50b582 | ||
|
d934f92ebd | ||
|
d20cf484ff | ||
|
ec4458e84a | ||
|
6c1719e365 | ||
|
467f48f1a6 | ||
|
a44cb876c9 | ||
|
e79c824913 | ||
|
b8e9cc70f2 | ||
|
a2f32307f0 | ||
|
c1d39cefd5 | ||
|
0edc562120 | ||
|
8ae2f06044 | ||
|
aa496e6efb | ||
|
1372192031 | ||
|
ea03f76847 | ||
|
d74a5f9979 | ||
|
febadcc4f6 | ||
|
17ece54cb0 | ||
|
da04a74350 | ||
|
634c8947bd | ||
|
f6f6fa99fb | ||
|
dcfbfc4981 | ||
|
6201a9b8ef | ||
|
1981d2e9ac | ||
|
66f6b1ecac | ||
|
affe2b59da | ||
|
757f8ec96a | ||
|
bf5e2b96df | ||
|
5666965321 | ||
|
7bf1f916c4 | ||
|
d44e43d94b | ||
|
9fe32fe915 | ||
|
e21cb3082b | ||
|
c50bc10f92 | ||
|
1b416028b4 | ||
|
85493b7532 | ||
|
dbabe0232f | ||
|
dfc8e8ec4c | ||
|
f00db49bda | ||
|
b4a3eb2cb3 | ||
|
81012746c4 | ||
|
1deacfbb34 | ||
|
c35d854776 | ||
|
c0fa259b40 | ||
|
391d3cb6b5 | ||
|
6d56220d98 | ||
|
2e4d30ef29 | ||
|
c57fa3f0d0 | ||
|
f2bca51046 | ||
|
6751d88ade | ||
|
e26a0bc9ae | ||
|
6dc517584f | ||
|
24d0cdef1f | ||
|
6639f89a68 | ||
|
0c0e9521f5 | ||
|
f2b684aa9e | ||
|
68ca0e9d4b | ||
|
e54077e020 | ||
|
9111800e7a | ||
|
ef6847671a | ||
|
b8cb0588fe | ||
|
667b371653 | ||
|
190fbb95ec | ||
|
f97e08b4e5 | ||
|
7242cbda40 | ||
|
9e3011d4c8 | ||
|
eb50256af7 | ||
|
ccacd20428 | ||
|
ac46fe9e16 | ||
|
9cfb83f0d2 | ||
|
943ba3bebd | ||
|
fccfd00949 | ||
|
68323057aa | ||
|
5081361c2d | ||
|
3ba619d45c | ||
|
f5dc0b38ff | ||
|
8357abd455 | ||
|
ede899d78e | ||
|
a703574004 | ||
|
ef2abbfbd4 | ||
|
7c0137e2dc | ||
|
34dec64d9c | ||
|
3968bb3488 | ||
|
6f2d7aebba | ||
|
366a35913b | ||
|
aec49fe542 | ||
|
f04d8b0e03 | ||
|
08af61b778 | ||
|
277f35a352 | ||
|
68512e475f | ||
|
0eff00ebee | ||
|
8a4a288074 | ||
|
5b1f887760 | ||
|
dcb6af6c45 | ||
|
3ff5952417 | ||
|
b480eb3688 | ||
|
12abca1b80 | ||
|
1d8ed387bc | ||
|
5521c094f7 | ||
|
222b72a014 | ||
|
8904ca27f2 | ||
|
6c5da97c16 | ||
|
d4d73db5ae | ||
|
1a436f962f | ||
|
dc61ee61f5 | ||
|
5b4b5e7a57 | ||
|
0164f94047 | ||
|
153587bd82 | ||
|
326f2e99fb | ||
|
1dfd6f232a | ||
|
42d61944b5 | ||
|
50a8ffeca1 | ||
|
f605bb8270 | ||
|
7ffb6113a4 | ||
|
156e6114c1 | ||
|
c91bc82cd9 | ||
|
8b481e2294 | ||
|
92bf3f1349 | ||
|
6474735662 | ||
|
45c5626267 | ||
|
c27264761d | ||
|
c6abfa14ea | ||
|
2f237cf17b | ||
|
bf5ba99653 | ||
|
8ecdac7c31 | ||
|
a40dd31543 | ||
|
dff89cb2e1 | ||
|
cdbb657961 | ||
|
443d45db6a | ||
|
a995615f87 | ||
|
024c62515c | ||
|
75e66a6550 | ||
|
e2f7890bb8 | ||
|
0a77d5014e | ||
|
91464a071e | ||
|
5244612ef6 | ||
|
e51439ade0 | ||
|
acd90657c7 | ||
|
e5482d48c0 | ||
|
5816a04a37 | ||
|
4b7da9ae6b | ||
|
f7cbc01023 | ||
|
1de57bbf3b | ||
|
42a8c561db | ||
|
5bef9aef6a | ||
|
f8c1ec985f | ||
|
12429d8091 | ||
|
3bb5e127d6 | ||
|
1d46b523b9 | ||
|
6721f337bd | ||
|
535cdf0ef5 | ||
|
19990b27bb | ||
|
4b3c5d5135 | ||
|
b6fe80faf4 | ||
|
638970fa77 | ||
|
c63f3edb06 | ||
|
9a6fe1af4e | ||
|
a01482dca4 | ||
|
5db1010e47 | ||
|
6130a0a654 | ||
|
3c1e1090e7 | ||
|
5cb47dae35 | ||
|
f68e98b2c7 | ||
|
420a55da76 | ||
|
f9aed28732 | ||
|
e30c17eab7 | ||
|
2c9a8865bf | ||
|
bddd70afdb | ||
|
c4388348f7 | ||
|
ee7828a445 | ||
|
37ac45c90a | ||
|
63d3bf93f2 | ||
|
2de8865730 | ||
|
fcffe1f3c3 | ||
|
cfcec04029 | ||
|
b56a41bec7 | ||
|
6bf89aeac9 | ||
|
e583c03751 | ||
|
d20b1cb64a | ||
|
22642e71a3 | ||
|
6e85b20b0e | ||
|
fcd290410e | ||
|
727d86236b | ||
|
2651f82337 | ||
|
3b62402cfe | ||
|
6cc8d1b205 | ||
|
49e5fafb2d | ||
|
e36367c040 | ||
|
f7fd23b153 | ||
|
4f70c40b1a | ||
|
5b875e0571 | ||
|
6db0de321c | ||
|
0c1f30208d | ||
|
08cfe79625 | ||
|
22a599633d | ||
|
14acbb2b4d | ||
|
6f5bebedf8 | ||
|
6fe77eba72 | ||
|
286a5081ff | ||
|
56c241c9bd | ||
|
68151d838f | ||
|
572b174cfe | ||
|
b71d8185a2 | ||
|
8051d9e3be | ||
|
1bf8fef292 | ||
|
b8a9397e73 | ||
|
009d691d5b | ||
|
6933159245 | ||
|
75549c41e0 | ||
|
5e2f888eaf | ||
|
d3d6d44665 | ||
|
fc6225a7ac | ||
|
3639007985 | ||
|
d5137897c1 | ||
|
b67735c31a | ||
|
8ff31ac49d | ||
|
5e0235e48d | ||
|
9e63a3219c | ||
|
757e90986e | ||
|
06fc5c2dd9 | ||
|
20dbe2dd0d | ||
|
367f793929 | ||
|
6f56c74e9d | ||
|
78d90c8f04 | ||
|
75fa2904d3 | ||
|
a9ed55e6a2 | ||
|
b37600f536 | ||
|
ef2783e9f4 | ||
|
8827b4b5ef | ||
|
7664b716b3 | ||
|
a2367ef14f | ||
|
067a22883c | ||
|
eb74523905 | ||
|
18d82b1bea | ||
|
687884431e | ||
|
3c9c2e61d7 | ||
|
e3246a1f2c | ||
|
642651a069 | ||
|
85a987ca8d | ||
|
5cb7e455b1 | ||
|
7c1428e097 | ||
|
32fd4c1be9 | ||
|
f53fc205e1 | ||
|
54eca7525e | ||
|
dec591517c | ||
|
005a11b765 | ||
|
d5cbcdd4e7 | ||
|
fe1af4e34f | ||
|
49f2064439 | ||
|
ff5b284b2e | ||
|
d9085b1231 | ||
|
a9074cdbbb | ||
|
09946ecf1b | ||
|
d21a536542 | ||
|
937d497a1f | ||
|
4261b0e78a | ||
|
71446a1a74 | ||
|
a29ec7b0be | ||
|
13036a5933 | ||
|
a8e19f3cc9 | ||
|
cb4752812a | ||
|
ccd9d2961d | ||
|
d1db17f244 | ||
|
aa4327c4da | ||
|
35179509f2 | ||
|
5008524635 | ||
|
147766176c | ||
|
23862cb3d0 | ||
|
2b8e2a312b | ||
|
62fdb4c27a | ||
|
b929a2f185 | ||
|
fb858bc112 | ||
|
5d0e917f04 | ||
|
e420070066 | ||
|
4504308f25 | ||
|
05683967c0 | ||
|
23c20acff1 | ||
|
be5e6eaa93 | ||
|
555863fecc | ||
|
b187699a86 | ||
|
d891da39e6 | ||
|
f37cd8cddc | ||
|
2d456d93a7 | ||
|
de36a28541 | ||
|
4634b963a2 | ||
|
b3f887ca28 | ||
|
3425dd0a63 | ||
|
2a774833ec | ||
|
bda4bbb59c | ||
|
0828f9270e | ||
|
e326631752 | ||
|
a3f8a53a52 | ||
|
7b5a50ec6e | ||
|
36c52d24a7 | ||
|
1b29c7bf91 | ||
|
b6aee04e24 | ||
|
a264dcf5c4 | ||
|
37afc983c3 | ||
|
b10e9c54b4 | ||
|
03d2cd0639 | ||
|
b7ad50a3ce | ||
|
9cbaadedfb | ||
|
d6cdcc60d1 | ||
|
1680cd8d8e | ||
|
d4384328fe | ||
|
5fd8425289 | ||
|
dcb618c257 | ||
|
277ad9e042 | ||
|
26d387cc12 | ||
|
4aebe6d303 | ||
|
7eccda6e25 | ||
|
78bc2bbaa5 | ||
|
cde068f267 | ||
|
c8f43e73e2 | ||
|
f106f92d7e | ||
|
fa0e07e146 | ||
|
d6df0e451c | ||
|
0121eee5b8 | ||
|
30e46445ca | ||
|
7158554ee2 | ||
|
34c5537436 | ||
|
d09ac8fbce | ||
|
319667096f | ||
|
c4f2eeeab7 | ||
|
5f14516dec | ||
|
5ff670b184 | ||
|
b7ff546f66 | ||
|
c13b636bae | ||
|
a05caf6f0e | ||
|
3462df405c | ||
|
6009026e2f | ||
|
06e0af9a5b | ||
|
3987a68a9a | ||
|
1228fcda0d | ||
|
c20de4feb0 | ||
|
957b9eee23 | ||
|
e5eb36e917 | ||
|
551604cdcb | ||
|
8a2bafec5f | ||
|
3013e311a4 | ||
|
1c35d07acc | ||
|
039924436f | ||
|
41b4f412c4 | ||
|
2abe29300b | ||
|
779026b0af | ||
|
151fec5ce0 | ||
|
b1b8513da4 | ||
|
0a9008a73d | ||
|
1ead2778c2 | ||
|
8299487f6d | ||
|
ceab0903cf | ||
|
77dd911054 | ||
|
a914381090 | ||
|
9f28738fd6 | ||
|
36a848471f | ||
|
12861aacda | ||
|
d85776297d | ||
|
50031bef50 | ||
|
c0d6b9d130 | ||
|
a1466e299b | ||
|
bc9493d5b9 | ||
|
9bb4c7ed25 | ||
|
65e5a04836 | ||
|
a69049645a |
@@ -1,95 +0,0 @@
|
|||||||
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
|
||||||
# Last docker plugin version can be found here:
|
|
||||||
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
|
||||||
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- label: "Compile and run Unit tests"
|
|
||||||
agents:
|
|
||||||
# We use a medium sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "medium"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean test --stacktrace"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
- label: "Compile Android tests"
|
|
||||||
agents:
|
|
||||||
# We use a medium sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "medium"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean assembleAndroidTest --stacktrace"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
- label: "Assemble GPlay Debug version"
|
|
||||||
agents:
|
|
||||||
# We use a xlarge sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "xlarge"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
|
||||||
artifact_paths:
|
|
||||||
- "vector/build/outputs/apk/gplay/debug/*.apk"
|
|
||||||
branches: "!master"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
- label: "Assemble FDroid Debug version"
|
|
||||||
agents:
|
|
||||||
# We use a xlarge sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "xlarge"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
|
||||||
artifact_paths:
|
|
||||||
- "vector/build/outputs/apk/fdroid/debug/*.apk"
|
|
||||||
branches: "!master"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
- label: "Build Google Play unsigned APK"
|
|
||||||
agents:
|
|
||||||
# We use a xlarge sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "xlarge"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean assembleGplayRelease --stacktrace"
|
|
||||||
artifact_paths:
|
|
||||||
- "vector/build/outputs/apk/gplay/release/*.apk"
|
|
||||||
branches: "master"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
# Code quality
|
|
||||||
|
|
||||||
- label: "Code quality"
|
|
||||||
command:
|
|
||||||
- "./tools/check/check_code_quality.sh"
|
|
||||||
|
|
||||||
- label: "ktlint"
|
|
||||||
command:
|
|
||||||
- "curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.34.2/ktlint && chmod a+x ktlint"
|
|
||||||
- "./ktlint --android --experimental -v"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "openjdk"
|
|
||||||
|
|
||||||
# Check that indonesians files are identical.
|
|
||||||
# Due to Android issue, the resource folder must be values-in/, and Weblate export data into values-id/.
|
|
||||||
# If this step fails, it means that Weblate has updated the file in value-id/ so to fix it, copy the file to values-in/
|
|
||||||
- label: "Indonesian"
|
|
||||||
command:
|
|
||||||
- "diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml"
|
|
3
.idea/codeStyles/Project.xml
generated
3
.idea/codeStyles/Project.xml
generated
@@ -1,9 +1,6 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<option name="RIGHT_MARGIN" value="160" />
|
<option name="RIGHT_MARGIN" value="160" />
|
||||||
<AndroidXmlCodeStyleSettings>
|
|
||||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
|
||||||
</AndroidXmlCodeStyleSettings>
|
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
<value>
|
<value>
|
||||||
|
6
.idea/dictionaries/bmarty.xml
generated
6
.idea/dictionaries/bmarty.xml
generated
@@ -7,23 +7,29 @@
|
|||||||
<w>ciphertext</w>
|
<w>ciphertext</w>
|
||||||
<w>coroutine</w>
|
<w>coroutine</w>
|
||||||
<w>decryptor</w>
|
<w>decryptor</w>
|
||||||
|
<w>displayname</w>
|
||||||
<w>emoji</w>
|
<w>emoji</w>
|
||||||
<w>emojis</w>
|
<w>emojis</w>
|
||||||
<w>fdroid</w>
|
<w>fdroid</w>
|
||||||
<w>gplay</w>
|
<w>gplay</w>
|
||||||
<w>hmac</w>
|
<w>hmac</w>
|
||||||
|
<w>homeserver</w>
|
||||||
<w>ktlint</w>
|
<w>ktlint</w>
|
||||||
<w>linkified</w>
|
<w>linkified</w>
|
||||||
<w>linkify</w>
|
<w>linkify</w>
|
||||||
<w>megolm</w>
|
<w>megolm</w>
|
||||||
<w>msisdn</w>
|
<w>msisdn</w>
|
||||||
|
<w>msisdns</w>
|
||||||
<w>pbkdf</w>
|
<w>pbkdf</w>
|
||||||
|
<w>pids</w>
|
||||||
<w>pkcs</w>
|
<w>pkcs</w>
|
||||||
|
<w>riotx</w>
|
||||||
<w>signin</w>
|
<w>signin</w>
|
||||||
<w>signout</w>
|
<w>signout</w>
|
||||||
<w>signup</w>
|
<w>signup</w>
|
||||||
<w>ssss</w>
|
<w>ssss</w>
|
||||||
<w>threepid</w>
|
<w>threepid</w>
|
||||||
|
<w>unwedging</w>
|
||||||
</words>
|
</words>
|
||||||
</dictionary>
|
</dictionary>
|
||||||
</component>
|
</component>
|
12
.travis.yml
12
.travis.yml
@@ -23,10 +23,10 @@ android:
|
|||||||
- platform-tools
|
- platform-tools
|
||||||
|
|
||||||
# The BuildTools version used by your project
|
# The BuildTools version used by your project
|
||||||
- build-tools-28.0.3
|
- build-tools-29.0.3
|
||||||
|
|
||||||
# The SDK version used to compile your project
|
# The SDK version used to compile your project
|
||||||
- android-28
|
- android-29
|
||||||
|
|
||||||
before_cache:
|
before_cache:
|
||||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
@@ -49,12 +49,12 @@ script:
|
|||||||
# Build Android test (assembleAndroidTest) (disabled for now)
|
# Build Android test (assembleAndroidTest) (disabled for now)
|
||||||
# Code quality (lintGplayRelease lintFdroidRelease)
|
# Code quality (lintGplayRelease lintFdroidRelease)
|
||||||
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
||||||
- ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
# Done by Buildkite now: - ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
||||||
- ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
# Done by Buildkite now: - ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
||||||
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
|
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
|
||||||
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
||||||
# Other code quality check
|
# Other code quality check
|
||||||
- ./tools/check/check_code_quality.sh
|
# Done by Buildkite now: - ./tools/check/check_code_quality.sh
|
||||||
- ./tools/travis/check_pr.sh
|
- ./tools/travis/check_pr.sh
|
||||||
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
|
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
|
||||||
- diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml
|
# Done by Buildkite now: - diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml
|
||||||
|
215
CHANGES.md
215
CHANGES.md
@@ -1,3 +1,217 @@
|
|||||||
|
Changes in RiotX 0.23.0 (2020-XX-XX)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Call with WebRTC support (##611)
|
||||||
|
- Add capability to change the display name (#1529)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- "Add Matrix app" menu is now always visible (#1495)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix dark theme issue on login screen (#1097)
|
||||||
|
- Incomplete predicate in RealmCryptoStore#getOutgoingRoomKeyRequest (#1519)
|
||||||
|
|
||||||
|
Translations 🗣:
|
||||||
|
-
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
-
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Enable code optimization (Proguard)
|
||||||
|
- SDK is now API level 21 minimum, and so RiotX (#405)
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
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 ✨:
|
||||||
|
- Change password (#528)
|
||||||
|
- Cross-Signing | Support SSSS secret sharing (#944)
|
||||||
|
- 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)
|
||||||
|
- Reimplementation of multiple attachment picker
|
||||||
|
- Cross-Signing | Update Shield Logic for DM (#963)
|
||||||
|
- Cross-Signing | Complete security new session design update (#1135)
|
||||||
|
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
|
||||||
|
- Cross-Signing | Gossip key backup recovery key (#1200)
|
||||||
|
- Show room encryption status as a bubble tile (#1078)
|
||||||
|
- UX/UI | Add indicator to home tab on invite (#957)
|
||||||
|
- Cross-Signing | Restore history after recover from passphrase (#1214)
|
||||||
|
- Cross-Sign | QR code scan confirmation screens design update (#1187)
|
||||||
|
- 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"
|
||||||
|
- Missing avatar/displayname after verification request message (#841)
|
||||||
|
- Crypto | RiotX sometimes rotate the current device keys (#1170)
|
||||||
|
- RiotX can't restore cross signing keys saved by web in SSSS (#1174)
|
||||||
|
- Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191)
|
||||||
|
- Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925)
|
||||||
|
- Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210)
|
||||||
|
- Fix crash when trying to download file without internet connection (#1229)
|
||||||
|
- Local echo are not updated in timeline (for failed & encrypted states)
|
||||||
|
- 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
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Compile with Android SDK 29 (Android Q)
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Add a setting to prevent screenshots of the application, disabled by default (#1027)
|
||||||
|
- Increase File Logger capacities ( + use dev log preferences)
|
||||||
|
|
||||||
|
Changes in RiotX 0.18.1 (2020-03-17)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Implementation of /join command
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Message transitions in encrypted rooms are jarring #518
|
||||||
|
- Images that failed to send are waiting to be sent forever #1145
|
||||||
|
- Fix / Crashed when trying to send a gif from the Gboard #1136
|
||||||
|
- Fix / Cannot click on key backup banner when new keys are available
|
||||||
|
|
||||||
|
|
||||||
|
Changes in RiotX 0.18.0 (2020-03-11)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Share image and other media from e2e rooms (#677)
|
||||||
|
- Add support for `/plain` command (#12)
|
||||||
|
- Detect spaces in password if user fail to login (#1038)
|
||||||
|
- FTUE: do not display a different color when encrypting message when not in developer mode.
|
||||||
|
- Open room member profile from avatar of the room member state event (#935)
|
||||||
|
- Restore the push rules configuration in the settings
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix crash on attachment preview screen (#1088)
|
||||||
|
- "Share" option is not appearing in encrypted rooms for images (#1031)
|
||||||
|
- Set "image/jpeg" as MIME type of images instead of "image/jpg" (#1075)
|
||||||
|
- Self verification via QR code is failing (#1130)
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- PushRuleService.getPushRules() now returns a RuleSet. Use getAllRules() on this object to get all the rules.
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Upgrade ktlint to version 0.36.0
|
||||||
|
- Pipeline file for Buildkite is now hosted on another Github repository: https://github.com/matrix-org/pipelines/blob/master/riotx-android/pipeline.yml
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Restore availability to Chromebooks (#932)
|
||||||
|
- Add a [documentation](./docs/integration_tests.md) to run integration tests
|
||||||
|
|
||||||
Changes in RiotX 0.17.0 (2020-02-27)
|
Changes in RiotX 0.17.0 (2020-02-27)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
@@ -363,6 +577,7 @@ Bugfix:
|
|||||||
- Fix messages with empty `in_reply_to` not rendering (#447)
|
- Fix messages with empty `in_reply_to` not rendering (#447)
|
||||||
- Fix clear cache (#408) and Logout (#205)
|
- Fix clear cache (#408) and Logout (#205)
|
||||||
- Fix `(edited)` link can be copied to clipboard (#402)
|
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||||
|
- KeyBackup / SSSS | Should get the key from SSSS instead of asking recovery Key (#1163)
|
||||||
|
|
||||||
Build:
|
Build:
|
||||||
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
||||||
|
@@ -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 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.
|
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
|
## 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.
|
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.
|
||||||
@@ -82,6 +104,8 @@ Make sure the following commands execute without any error:
|
|||||||
RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices.
|
RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices.
|
||||||
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
||||||
|
|
||||||
|
You should consider adding Unit tests with your PR, and also integration tests (AndroidTest). Please refer to [this document](./docs/integration_tests.md) to install and run the integration test environment.
|
||||||
|
|
||||||
### Internationalisation
|
### Internationalisation
|
||||||
|
|
||||||
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||||
|
@@ -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.
|
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="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="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/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)
|
Nightly build: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||||
|
|
||||||
|
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
|
103
docs/integration_tests.md
Normal file
103
docs/integration_tests.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Integration tests
|
||||||
|
|
||||||
|
Integration tests are useful to ensure that the code works well for any use cases.
|
||||||
|
|
||||||
|
They can also be used as sample on how to use the Matrix SDK.
|
||||||
|
|
||||||
|
In a ideal world, every API of the SDK should be covered by integration tests. For the moment, we have test mainly for the Crypto part, which is the tricky part. But it covers quite a lot of features: accounts creation, login to existing account, send encrypted messages, keys backup, verification, etc.
|
||||||
|
|
||||||
|
The Matrix SDK is able to open multiple sessions, for the same user, of for different users. This way we can test communication between several sessions on a single device.
|
||||||
|
|
||||||
|
## Pre requirements
|
||||||
|
|
||||||
|
Integration tests need a homeserver running on localhost.
|
||||||
|
|
||||||
|
The documentation describes what we do to have one, using [Synapse](https://github.com/matrix-org/synapse/), which is the Matrix reference homeserver.
|
||||||
|
|
||||||
|
## Install and run Synapse
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
- Install virtualenv
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m pip install virtualenv
|
||||||
|
```
|
||||||
|
|
||||||
|
- Clone Synapse repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone -b develop https://github.com/matrix-org/synapse.git
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```bash
|
||||||
|
git clone -b develop git@github.com:matrix-org/synapse.git
|
||||||
|
```
|
||||||
|
|
||||||
|
You should have the develop branch cloned by default.
|
||||||
|
|
||||||
|
- Run synapse, from the Synapse folder you just cloned
|
||||||
|
|
||||||
|
```bash
|
||||||
|
virtualenv -p python3 env
|
||||||
|
source env/bin/activate
|
||||||
|
pip install -e .
|
||||||
|
demo/start.sh --no-rate-limit
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, to install the latest Synapse release package (and not a cloned branch) you can run the following instead of `pip install -e .`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install matrix-synapse
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now have 3 running federated Synapse instances 🎉, at http://127.0.0.1:8080/, http://127.0.0.1:8081/ and http://127.0.0.1:8082/, which should display a "It Works! Synapse is running" message.
|
||||||
|
|
||||||
|
## Run the test
|
||||||
|
|
||||||
|
It's recommended to run tests using an Android Emulator and not a real device. First reason for that is that the tests will use http://10.0.2.2:8080 to connect to Synapse, which run locally on your machine.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./demo/stop.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can deactivate the virtualenv:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deactivate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshoot
|
||||||
|
|
||||||
|
You'll need python3 to be able to run synapse
|
||||||
|
|
||||||
|
### Android Emulator does cannot reach the homeserver
|
||||||
|
|
||||||
|
Try on the Emulator browser to open "http://10.0.2.2:8080". You should see the "Synapse is running" message.
|
||||||
|
|
||||||
|
### virtualenv command fails
|
||||||
|
|
||||||
|
You can try using
|
||||||
|
```bash
|
||||||
|
python3 -m venv env
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```bash
|
||||||
|
python3 -m virtualenv env
|
||||||
|
```
|
||||||
|
instead of
|
||||||
|
```bash
|
||||||
|
virtualenv -p python3 env
|
||||||
|
```
|
@@ -38,10 +38,10 @@ When the client receives the new information, it immediately sends another reque
|
|||||||
This effectively emulates a server push feature.
|
This effectively emulates a server push feature.
|
||||||
|
|
||||||
The HTTP long Polling can be fine tuned in the **SDK** using two parameters:
|
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)
|
* 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.`
|
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.
|
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.
|
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
|
### Get the flow
|
||||||
|
|
||||||
@@ -57,8 +57,8 @@ We get credential (200)
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"user_id": "@benoit0816:matrix.org",
|
"user_id": "@alice:matrix.org",
|
||||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg",
|
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lr",
|
||||||
"home_server": "matrix.org",
|
"home_server": "matrix.org",
|
||||||
"device_id": "GTVREDALBF",
|
"device_id": "GTVREDALBF",
|
||||||
"well_known": {
|
"well_known": {
|
||||||
@@ -117,7 +117,7 @@ We get the credentials (200)
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"user_id": "@alice:matrix.org",
|
"user_id": "@alice:matrix.org",
|
||||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNjtDY0MwRlNPSFFoOC5wOgowMDJmc2lnbmF0dXJlIGiTRm1mYLLxQywxOh3qzQVT8HoEorSokEP2u-bAwtnYCg",
|
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3Jnfrfdegfszsefddvf",
|
||||||
"home_server": "matrix.org",
|
"home_server": "matrix.org",
|
||||||
"device_id": "WBSREDASND",
|
"device_id": "WBSREDASND",
|
||||||
"well_known": {
|
"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
|
### Login with Msisdn
|
||||||
|
|
||||||
Not supported yet in RiotX
|
Not supported yet in RiotX
|
||||||
@@ -143,12 +145,59 @@ Not supported yet in RiotX
|
|||||||
"flows": [
|
"flows": [
|
||||||
{
|
{
|
||||||
"type": "m.login.sso"
|
"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
|
## Reset password
|
||||||
|
|
||||||
|
420
docs/voip_signaling.md
Normal file
420
docs/voip_signaling.md
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
╔════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║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.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
org.gradle.jvmargs=-Xmx1536m
|
org.gradle.jvmargs=-Xmx8192m
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# 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
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
@@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 29
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
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.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||||
@@ -28,6 +29,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.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
||||||
@@ -60,7 +62,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()
|
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
room.getStateEvent(eventType, stateKey).toOptional()
|
room.getStateEvent(eventType, stateKey).toOptional()
|
||||||
@@ -95,6 +97,10 @@ class RxRoom(private val room: Room) {
|
|||||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||||
return room.getLiveRoomNotificationState().asObservable()
|
return room.getLiveRoomNotificationState().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun invite(userId: String, reason: String? = null): Completable = completableBuilder<Unit> {
|
||||||
|
room.invite(userId, reason, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(): RxRoom {
|
||||||
|
@@ -17,20 +17,25 @@
|
|||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
import androidx.paging.PagedList
|
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.Session
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
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.GroupSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
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.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
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.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.JsonDict
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
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.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 im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
@@ -51,10 +56,17 @@ class RxSession(private val session: Session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
return session.getBreadcrumbsLive().asObservable()
|
return session.getBreadcrumbsLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
.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()
|
return session.getIgnoredUsersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
fun livePagedUsers(filter: String? = null, excludedUserIds: Set<String>? = null): Observable<PagedList<User>> {
|
||||||
return session.getPagedUsersLive(filter).asObservable()
|
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 {
|
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>> {
|
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
|
||||||
return session.getLiveAccountDataEvents(types).asObservable()
|
return session.getLiveAccountDataEvents(types).asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
session.getAccountDataEvents(types)
|
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 {
|
fun Session.rx(): RxSession {
|
||||||
|
@@ -19,12 +19,12 @@ androidExtensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 29
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "0.0.1"
|
versionName "0.0.1"
|
||||||
// Multidex is useful for tests
|
// Multidex is useful for tests
|
||||||
@@ -35,6 +35,10 @@ android {
|
|||||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||||
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
||||||
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
consumerProguardFiles 'proguard-rules.pro'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -49,9 +53,6 @@ android {
|
|||||||
release {
|
release {
|
||||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
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 +72,15 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
androidTest {
|
||||||
|
java.srcDirs += "src/sharedTest/java"
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
java.srcDirs += "src/sharedTest/java"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static def gitRevision() {
|
static def gitRevision() {
|
||||||
@@ -97,6 +107,7 @@ dependencies {
|
|||||||
def coroutines_version = "1.3.2"
|
def coroutines_version = "1.3.2"
|
||||||
def markwon_version = '3.1.0'
|
def markwon_version = '3.1.0'
|
||||||
def daggerVersion = '2.25.4'
|
def daggerVersion = '2.25.4'
|
||||||
|
def work_version = '2.3.3'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
@@ -118,7 +129,7 @@ dependencies {
|
|||||||
implementation "ru.noties.markwon:core:$markwon_version"
|
implementation "ru.noties.markwon:core:$markwon_version"
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.1.0'
|
implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
|
||||||
implementation 'id.zelory:compressor:3.0.0'
|
implementation 'id.zelory:compressor:3.0.0'
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
@@ -126,7 +137,7 @@ dependencies {
|
|||||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:2.3.0"
|
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||||
|
|
||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
@@ -148,6 +159,13 @@ dependencies {
|
|||||||
// Bus
|
// Bus
|
||||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
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'
|
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
||||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||||
@@ -159,6 +177,8 @@ dependencies {
|
|||||||
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
|
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
|
||||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
// Plant Timber tree for test
|
||||||
|
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
@@ -170,5 +190,6 @@ dependencies {
|
|||||||
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
|
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
|
||||||
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
|
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
|
||||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
// Plant Timber tree for test
|
||||||
|
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||||
}
|
}
|
||||||
|
42
matrix-sdk-android/proguard-rules.pro
vendored
42
matrix-sdk-android/proguard-rules.pro
vendored
@@ -19,3 +19,45 @@
|
|||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-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.** { *; }
|
@@ -18,10 +18,15 @@ package im.vector.matrix.android
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import im.vector.matrix.android.test.shared.createTimberTestRule
|
||||||
|
import org.junit.Rule
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface InstrumentedTest {
|
interface InstrumentedTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
fun timberTestRule() = createTimberTestRule()
|
||||||
|
|
||||||
fun context(): Context {
|
fun context(): Context {
|
||||||
return ApplicationProvider.getApplicationContext()
|
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
|
||||||
|
}
|
||||||
|
}
|
@@ -28,16 +28,17 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
|||||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.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.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
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.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
@@ -87,7 +88,8 @@ class CommonTestHelper(context: Context) {
|
|||||||
fun syncSession(session: Session) {
|
fun syncSession(session: Session) {
|
||||||
val lock = CountDownLatch(1)
|
val lock = CountDownLatch(1)
|
||||||
|
|
||||||
session.open()
|
GlobalScope.launch(Dispatchers.Main) { session.open() }
|
||||||
|
|
||||||
session.startSync(true)
|
session.startSync(true)
|
||||||
|
|
||||||
val syncLiveData = runBlocking(Dispatchers.Main) {
|
val syncLiveData = runBlocking(Dispatchers.Main) {
|
||||||
@@ -115,7 +117,7 @@ class CommonTestHelper(context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||||
val latch = CountDownLatch(nbOfMessages)
|
val latch = CountDownLatch(1)
|
||||||
val timelineListener = object : Timeline.Listener {
|
val timelineListener = object : Timeline.Listener {
|
||||||
override fun onTimelineFailure(throwable: Throwable) {
|
override fun onTimelineFailure(throwable: Throwable) {
|
||||||
}
|
}
|
||||||
@@ -126,7 +128,7 @@ class CommonTestHelper(context: Context) {
|
|||||||
|
|
||||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
val newMessages = snapshot
|
val newMessages = snapshot
|
||||||
.filter { LocalEcho.isLocalEchoId(it.eventId).not() }
|
.filter { it.root.sendState == SendState.SYNCED }
|
||||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||||
|
|
||||||
@@ -142,7 +144,8 @@ class CommonTestHelper(context: Context) {
|
|||||||
for (i in 0 until nbOfMessages) {
|
for (i in 0 until nbOfMessages) {
|
||||||
room.sendTextMessage(message + " #" + (i + 1))
|
room.sendTextMessage(message + " #" + (i + 1))
|
||||||
}
|
}
|
||||||
await(latch)
|
// Wait 3 second more per message
|
||||||
|
await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages)
|
||||||
timeline.removeListener(timelineListener)
|
timeline.removeListener(timelineListener)
|
||||||
timeline.dispose()
|
timeline.dispose()
|
||||||
|
|
||||||
@@ -182,9 +185,9 @@ class CommonTestHelper(context: Context) {
|
|||||||
* @param testParams test params about the session
|
* @param testParams test params about the session
|
||||||
* @return the session associated with the existing account
|
* @return the session associated with the existing account
|
||||||
*/
|
*/
|
||||||
private fun logIntoAccount(userId: String,
|
fun logIntoAccount(userId: String,
|
||||||
password: String,
|
password: String,
|
||||||
testParams: SessionTestParams): Session {
|
testParams: SessionTestParams): Session {
|
||||||
val session = logAccountAndSync(userId, password, testParams)
|
val session = logAccountAndSync(userId, password, testParams)
|
||||||
assertNotNull(session)
|
assertNotNull(session)
|
||||||
return session
|
return session
|
||||||
@@ -259,14 +262,81 @@ class CommonTestHelper(context: Context) {
|
|||||||
return session
|
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
|
* Await for a latch and ensure the result is true
|
||||||
*
|
*
|
||||||
* @param latch
|
* @param latch
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
fun await(latch: CountDownLatch) {
|
fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) {
|
||||||
assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
|
||||||
|
GlobalScope.launch {
|
||||||
|
while (true) {
|
||||||
|
delay(1000)
|
||||||
|
if (condition()) {
|
||||||
|
latch.countDown()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
block(latch)
|
||||||
|
await(latch, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform a method with a MatrixCallback to a synchronous method
|
// Transform a method with a MatrixCallback to a synchronous method
|
||||||
@@ -299,3 +369,13 @@ class CommonTestHelper(context: Context) {
|
|||||||
session.close()
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.Session
|
|||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toContent
|
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.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
@@ -40,8 +41,6 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import java.util.HashMap
|
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
@@ -54,17 +53,19 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||||||
/**
|
/**
|
||||||
* @return alice session
|
* @return alice session
|
||||||
*/
|
*/
|
||||||
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||||
|
|
||||||
val roomId = mTestHelper.doSync<String> {
|
val roomId = mTestHelper.doSync<String> {
|
||||||
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
|
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val room = aliceSession.getRoom(roomId)!!
|
if (encryptedRoom) {
|
||||||
|
val room = aliceSession.getRoom(roomId)!!
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
room.enableEncryption(callback = it)
|
room.enableEncryption(callback = it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CryptoTestData(aliceSession, roomId)
|
return CryptoTestData(aliceSession, roomId)
|
||||||
@@ -73,8 +74,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||||||
/**
|
/**
|
||||||
* @return alice and bob sessions
|
* @return alice and bob sessions
|
||||||
*/
|
*/
|
||||||
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||||
val cryptoTestData = doE2ETestWithAliceInARoom()
|
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val aliceRoomId = cryptoTestData.roomId
|
val aliceRoomId = cryptoTestData.roomId
|
||||||
|
|
||||||
@@ -140,64 +141,38 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||||||
* @return Alice, Bob and Sam session
|
* @return Alice, Bob and Sam session
|
||||||
*/
|
*/
|
||||||
fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData {
|
fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData {
|
||||||
val statuses = HashMap<String, String>()
|
|
||||||
|
|
||||||
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val aliceRoomId = cryptoTestData.roomId
|
val aliceRoomId = cryptoTestData.roomId
|
||||||
|
|
||||||
val room = aliceSession.getRoom(aliceRoomId)!!
|
val room = aliceSession.getRoom(aliceRoomId)!!
|
||||||
|
|
||||||
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
val samSession = createSamAccountAndInviteToTheRoom(room)
|
||||||
|
|
||||||
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"))
|
|
||||||
|
|
||||||
// wait the initial sync
|
// wait the initial sync
|
||||||
SystemClock.sleep(1000)
|
SystemClock.sleep(1000)
|
||||||
|
|
||||||
// samSession.dataHandler.removeListener(samEventListener)
|
|
||||||
|
|
||||||
return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
|
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
|
* @return Alice and Bob sessions
|
||||||
*/
|
*/
|
||||||
@@ -273,7 +248,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||||||
assertNotNull(eventWireContent.get("session_id"))
|
assertNotNull(eventWireContent.get("session_id"))
|
||||||
assertNotNull(eventWireContent.get("sender_key"))
|
assertNotNull(eventWireContent.get("sender_key"))
|
||||||
|
|
||||||
assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id"))
|
assertEquals(senderSession.sessionParams.deviceId, eventWireContent.get("device_id"))
|
||||||
|
|
||||||
assertNotNull(event.eventId)
|
assertNotNull(event.eventId)
|
||||||
assertEquals(roomId, event.roomId)
|
assertEquals(roomId, event.roomId)
|
||||||
|
@@ -22,8 +22,8 @@ object TestConstants {
|
|||||||
|
|
||||||
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
|
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
|
||||||
|
|
||||||
// Time out to use when waiting for server response. 10s
|
// Time out to use when waiting for server response. 20s
|
||||||
private const val AWAIT_TIME_OUT_MILLIS = 10_000
|
private const val AWAIT_TIME_OUT_MILLIS = 20_000
|
||||||
|
|
||||||
// Time out to use when waiting for server response, when the debugger is connected. 10 minutes
|
// Time out to use when waiting for server response, when the debugger is connected. 10 minutes
|
||||||
private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000
|
private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000
|
||||||
|
@@ -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.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||||
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@@ -31,6 +33,7 @@ internal class CryptoStoreHelper {
|
|||||||
.name("test.realm")
|
.name("test.realm")
|
||||||
.modules(RealmCryptoStoreModule())
|
.modules(RealmCryptoStoreModule())
|
||||||
.build(),
|
.build(),
|
||||||
|
crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()),
|
||||||
credentials = createCredential())
|
credentials = createCredential())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.amshove.kluent.shouldBeTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
|
class ExtensionsKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testComparingBase64StringWithOrWithoutPadding() {
|
||||||
|
// Without padding
|
||||||
|
"NMJyumnhMic".fromBase64().contentEquals("NMJyumnhMic".fromBase64()).shouldBeTrue()
|
||||||
|
// With padding
|
||||||
|
"NMJyumnhMic".fromBase64().contentEquals("NMJyumnhMic=".fromBase64()).shouldBeTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBadBase64() {
|
||||||
|
"===".fromBase64Safe().shouldBeNull()
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
// 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 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
|
// Check that bob first session sees the new login
|
||||||
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||||
|
@@ -0,0 +1,289 @@
|
|||||||
|
/*
|
||||||
|
* 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.session.crypto.verification.IncomingSasVerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||||
|
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.SessionTestParams
|
||||||
|
import im.vector.matrix.android.common.TestConstants
|
||||||
|
import im.vector.matrix.android.internal.crypto.GossipingRequestState
|
||||||
|
import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
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.model.CryptoDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import junit.framework.TestCase.assertNotNull
|
||||||
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import junit.framework.TestCase.fail
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class KeyShareTests : InstrumentedTest {
|
||||||
|
|
||||||
|
private val mTestHelper = CommonTestHelper(context())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_DoNotSelfShareIfNotTrusted() {
|
||||||
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
|
// Create an encrypted room and add a message
|
||||||
|
val roomId = mTestHelper.doSync<String> {
|
||||||
|
aliceSession.createRoom(
|
||||||
|
CreateRoomParams(RoomDirectoryVisibility.PRIVATE).enableEncryptionWithAlgorithm(true),
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val room = aliceSession.getRoom(roomId)
|
||||||
|
assertNotNull(room)
|
||||||
|
Thread.sleep(4_000)
|
||||||
|
assertTrue(room?.isEncrypted() == true)
|
||||||
|
val sentEventId = mTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
|
||||||
|
|
||||||
|
// Open a new sessionx
|
||||||
|
|
||||||
|
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||||
|
|
||||||
|
val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
|
||||||
|
|
||||||
|
val receivedEvent = roomSecondSessionPOV?.getTimeLineEvent(sentEventId)
|
||||||
|
assertNotNull(receivedEvent)
|
||||||
|
assert(receivedEvent!!.isEncrypted())
|
||||||
|
|
||||||
|
try {
|
||||||
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||||
|
fail("should fail")
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
|
// Try to request
|
||||||
|
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
|
||||||
|
|
||||||
|
val waitLatch = CountDownLatch(1)
|
||||||
|
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
|
||||||
|
|
||||||
|
var outGoingRequestId: String? = null
|
||||||
|
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
|
||||||
|
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
|
.filter { req ->
|
||||||
|
// filter out request that was known before
|
||||||
|
!outgoingRequestsBefore.any { req.requestId == it.requestId }
|
||||||
|
}
|
||||||
|
.let {
|
||||||
|
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
|
||||||
|
outGoingRequestId = outgoing?.requestId
|
||||||
|
outgoing != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mTestHelper.await(waitLatch)
|
||||||
|
|
||||||
|
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
|
||||||
|
|
||||||
|
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
|
|
||||||
|
// We should have a new request
|
||||||
|
Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestsBefore.size)
|
||||||
|
Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId })
|
||||||
|
|
||||||
|
// The first session should see an incoming request
|
||||||
|
// the request should be refused, because the device is not trusted
|
||||||
|
mTestHelper.waitWithLatch { latch ->
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
// DEBUG LOGS
|
||||||
|
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||||
|
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
||||||
|
Log.v("TEST", "=========================")
|
||||||
|
it.forEach { keyRequest ->
|
||||||
|
Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}")
|
||||||
|
}
|
||||||
|
Log.v("TEST", "=========================")
|
||||||
|
}
|
||||||
|
|
||||||
|
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||||
|
incoming?.state == GossipingRequestState.REJECTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||||
|
fail("should fail")
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the device as trusted
|
||||||
|
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||||
|
aliceSession2.sessionParams.deviceId ?: "")
|
||||||
|
|
||||||
|
// Re request
|
||||||
|
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||||
|
|
||||||
|
mTestHelper.waitWithLatch { latch ->
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||||
|
Log.v("TEST", "Incoming request Session 1")
|
||||||
|
Log.v("TEST", "=========================")
|
||||||
|
it.forEach {
|
||||||
|
Log.v("TEST", "requestId ${it.requestId}, for sessionId ${it.requestBody?.sessionId} is ${it.state}")
|
||||||
|
}
|
||||||
|
Log.v("TEST", "=========================")
|
||||||
|
|
||||||
|
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(6_000)
|
||||||
|
mTestHelper.waitWithLatch { latch ->
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let {
|
||||||
|
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
fail("should have been able to decrypt")
|
||||||
|
}
|
||||||
|
|
||||||
|
mTestHelper.signOutAndClose(aliceSession)
|
||||||
|
mTestHelper.signOutAndClose(aliceSession2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_ShareSSSSSecret() {
|
||||||
|
val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
|
mTestHelper.doSync<Unit> {
|
||||||
|
aliceSession1.cryptoService().crossSigningService()
|
||||||
|
.initializeCrossSigning(UserPasswordAuth(
|
||||||
|
user = aliceSession1.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also bootstrap keybackup on first session
|
||||||
|
val creationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||||
|
aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||||
|
}
|
||||||
|
val version = mTestHelper.doSync<KeysVersion> {
|
||||||
|
aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
||||||
|
}
|
||||||
|
// Save it for gossiping
|
||||||
|
aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||||
|
|
||||||
|
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
|
||||||
|
|
||||||
|
val aliceVerificationService1 = aliceSession1.cryptoService().verificationService()
|
||||||
|
val aliceVerificationService2 = aliceSession2.cryptoService().verificationService()
|
||||||
|
|
||||||
|
// force keys download
|
||||||
|
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||||
|
aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it)
|
||||||
|
}
|
||||||
|
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||||
|
aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
var session1ShortCode: String? = null
|
||||||
|
var session2ShortCode: String? = null
|
||||||
|
|
||||||
|
aliceVerificationService1.addListener(object : VerificationService.Listener {
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||||
|
if (tx is SasVerificationTransaction) {
|
||||||
|
if (tx.state == VerificationTxState.OnStarted) {
|
||||||
|
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||||
|
}
|
||||||
|
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||||
|
session1ShortCode = tx.getDecimalCodeRepresentation()
|
||||||
|
tx.userHasVerifiedShortCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
aliceVerificationService2.addListener(object : VerificationService.Listener {
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||||
|
if (tx is SasVerificationTransaction) {
|
||||||
|
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||||
|
session2ShortCode = tx.getDecimalCodeRepresentation()
|
||||||
|
tx.userHasVerifiedShortCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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.deviceId ?: "")?.isVerified == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotNull(session1ShortCode)
|
||||||
|
Log.d("#TEST", "session1ShortCode: $session1ShortCode")
|
||||||
|
assertNotNull(session2ShortCode)
|
||||||
|
Log.d("#TEST", "session2ShortCode: $session2ShortCode")
|
||||||
|
assertEquals(session1ShortCode, session2ShortCode)
|
||||||
|
|
||||||
|
// SSK and USK private keys should have been shared
|
||||||
|
|
||||||
|
mTestHelper.waitWithLatch(60_000) { latch ->
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}")
|
||||||
|
aliceSession2.cryptoService().crossSigningService().canCrossSign()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that key backup key has been shared to
|
||||||
|
mTestHelper.waitWithLatch(60_000) { latch ->
|
||||||
|
val keysBackupService = aliceSession2.cryptoService().keysBackupService()
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
Log.d("#TEST", "Recovery :${ keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
|
||||||
|
keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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,28 +20,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.api.listeners.StepProgressListener
|
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.KeysBackupState
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import im.vector.matrix.android.common.CommonTestHelper
|
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.CryptoTestHelper
|
||||||
import im.vector.matrix.android.common.SessionTestParams
|
|
||||||
import im.vector.matrix.android.common.TestConstants
|
import im.vector.matrix.android.common.TestConstants
|
||||||
import im.vector.matrix.android.common.TestMatrixCallback
|
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.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
|
||||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
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.KeysBackupVersionTrust
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
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.KeysVersion
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
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.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
@@ -62,9 +53,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
private val mTestHelper = CommonTestHelper(context())
|
private val mTestHelper = CommonTestHelper(context())
|
||||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||||
|
private val mKeysBackupTestHelper = KeysBackupTestHelper(mTestHelper, mCryptoTestHelper)
|
||||||
private val defaultSessionParams = SessionTestParams(withInitialSync = false)
|
|
||||||
private val defaultSessionParamsWithInitialSync = SessionTestParams(withInitialSync = true)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
|
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
|
||||||
@@ -111,7 +100,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun prepareKeysBackupVersionTest() {
|
fun prepareKeysBackupVersionTest() {
|
||||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||||
|
|
||||||
assertNotNull(bobSession.cryptoService().keysBackupService())
|
assertNotNull(bobSession.cryptoService().keysBackupService())
|
||||||
|
|
||||||
@@ -140,7 +129,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun createKeysBackupVersionTest() {
|
fun createKeysBackupVersionTest() {
|
||||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||||
|
|
||||||
val keysBackup = bobSession.cryptoService().keysBackupService()
|
val keysBackup = bobSession.cryptoService().keysBackupService()
|
||||||
|
|
||||||
@@ -183,7 +172,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
val stateObserver = StateObserver(keysBackup, latch, 5)
|
val stateObserver = StateObserver(keysBackup, latch, 5)
|
||||||
|
|
||||||
prepareAndCreateKeysBackupData(keysBackup)
|
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
mTestHelper.await(latch)
|
mTestHelper.await(latch)
|
||||||
|
|
||||||
@@ -217,7 +206,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
val stateObserver = StateObserver(keysBackup)
|
val stateObserver = StateObserver(keysBackup)
|
||||||
|
|
||||||
prepareAndCreateKeysBackupData(keysBackup)
|
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
// Check that backupAllGroupSessions returns valid data
|
// Check that backupAllGroupSessions returns valid data
|
||||||
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
|
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
|
||||||
@@ -264,7 +253,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
// - Pick a megolm key
|
// - Pick a megolm key
|
||||||
val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0]
|
val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0]
|
||||||
|
|
||||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
|
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
|
||||||
|
|
||||||
// - Check encryptGroupSession() returns stg
|
// - Check encryptGroupSession() returns stg
|
||||||
val keyBackupData = keysBackup.encryptGroupSession(session)
|
val keyBackupData = keysBackup.encryptGroupSession(session)
|
||||||
@@ -282,7 +271,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
decryption!!)
|
decryption!!)
|
||||||
assertNotNull(sessionData)
|
assertNotNull(sessionData)
|
||||||
// - Compare the decrypted megolm key with the original one
|
// - Compare the decrypted megolm key with the original one
|
||||||
assertKeysEquals(session.exportKeys(), sessionData)
|
mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
|
||||||
|
|
||||||
stateObserver.stopAndCheckStates(null)
|
stateObserver.stopAndCheckStates(null)
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
@@ -296,7 +285,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun restoreKeysBackupTest() {
|
fun restoreKeysBackupTest() {
|
||||||
val testData = createKeysBackupScenarioWithPassword(null)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||||
|
|
||||||
// - Restore the e2e backup from the homeserver
|
// - Restore the e2e backup from the homeserver
|
||||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||||
@@ -309,7 +298,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||||
|
|
||||||
testData.cleanUp(mTestHelper)
|
testData.cleanUp(mTestHelper)
|
||||||
}
|
}
|
||||||
@@ -326,46 +315,46 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
* - Restore must be successful
|
* - Restore must be successful
|
||||||
* - *** There must be no more pending key share requests
|
* - *** There must be no more pending key share requests
|
||||||
*/
|
*/
|
||||||
@Test
|
// @Test
|
||||||
fun restoreKeysBackupAndKeyShareRequestTest() {
|
// fun restoreKeysBackupAndKeyShareRequestTest() {
|
||||||
fail("Check with Valere for this test. I think we do not send key share request")
|
// 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
|
// // - Check the SDK sent key share requests
|
||||||
val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
// val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||||
val unsentRequest = cryptoStore2
|
// val unsentRequest = cryptoStore2
|
||||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
|
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT))
|
||||||
val sentRequest = cryptoStore2
|
// val sentRequest = cryptoStore2
|
||||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
|
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT))
|
||||||
|
//
|
||||||
// Request is either sent or unsent
|
// // Request is either sent or unsent
|
||||||
assertTrue(unsentRequest != null || sentRequest != null)
|
// assertTrue(unsentRequest != null || sentRequest != null)
|
||||||
|
//
|
||||||
// - Restore the e2e backup from the homeserver
|
// // - Restore the e2e backup from the homeserver
|
||||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
// val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
// testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
// testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||||
null,
|
// null,
|
||||||
null,
|
// null,
|
||||||
null,
|
// null,
|
||||||
it
|
// it
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
// mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||||
|
//
|
||||||
// - There must be no more pending key share requests
|
// // - There must be no more pending key share requests
|
||||||
val unsentRequestAfterRestoration = cryptoStore2
|
// val unsentRequestAfterRestoration = cryptoStore2
|
||||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
|
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT))
|
||||||
val sentRequestAfterRestoration = cryptoStore2
|
// val sentRequestAfterRestoration = cryptoStore2
|
||||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
|
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT))
|
||||||
|
//
|
||||||
// Request is either sent or unsent
|
// // Request is either sent or unsent
|
||||||
assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
|
// assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
|
||||||
|
//
|
||||||
testData.cleanUp(mTestHelper)
|
// testData.cleanUp(mTestHelper)
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - Do an e2e backup to the homeserver with a recovery key
|
* - Do an e2e backup to the homeserver with a recovery key
|
||||||
@@ -381,7 +370,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
fun trustKeyBackupVersionTest() {
|
fun trustKeyBackupVersionTest() {
|
||||||
// - Do an e2e backup to the homeserver with a recovery key
|
// - Do an e2e backup to the homeserver with a recovery key
|
||||||
// - And log Alice on a new device
|
// - And log Alice on a new device
|
||||||
val testData = createKeysBackupScenarioWithPassword(null)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||||
|
|
||||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||||
|
|
||||||
@@ -400,7 +389,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for backup state to be ReadyToBackUp
|
// 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
|
// - Backup must be enabled on the new device, on the same version
|
||||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||||
@@ -440,7 +429,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
fun trustKeyBackupVersionWithRecoveryKeyTest() {
|
fun trustKeyBackupVersionWithRecoveryKeyTest() {
|
||||||
// - Do an e2e backup to the homeserver with a recovery key
|
// - Do an e2e backup to the homeserver with a recovery key
|
||||||
// - And log Alice on a new device
|
// - And log Alice on a new device
|
||||||
val testData = createKeysBackupScenarioWithPassword(null)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||||
|
|
||||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||||
|
|
||||||
@@ -459,7 +448,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for backup state to be ReadyToBackUp
|
// 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
|
// - Backup must be enabled on the new device, on the same version
|
||||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||||
@@ -497,7 +486,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
|
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
|
||||||
// - Do an e2e backup to the homeserver with a recovery key
|
// - Do an e2e backup to the homeserver with a recovery key
|
||||||
// - And log Alice on a new device
|
// - And log Alice on a new device
|
||||||
val testData = createKeysBackupScenarioWithPassword(null)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||||
|
|
||||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||||
|
|
||||||
@@ -540,7 +529,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
// - Do an e2e backup to the homeserver with a password
|
// - Do an e2e backup to the homeserver with a password
|
||||||
// - And log Alice on a new device
|
// - And log Alice on a new device
|
||||||
val testData = createKeysBackupScenarioWithPassword(password)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||||
|
|
||||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||||
|
|
||||||
@@ -559,7 +548,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for backup state to be ReadyToBackUp
|
// 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
|
// - Backup must be enabled on the new device, on the same version
|
||||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||||
@@ -600,7 +589,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
// - Do an e2e backup to the homeserver with a password
|
// - Do an e2e backup to the homeserver with a password
|
||||||
// - And log Alice on a new device
|
// - And log Alice on a new device
|
||||||
val testData = createKeysBackupScenarioWithPassword(password)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||||
|
|
||||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||||
|
|
||||||
@@ -635,7 +624,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
|
fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
|
||||||
val testData = createKeysBackupScenarioWithPassword(null)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||||
|
|
||||||
// - Try to restore the e2e backup with a wrong recovery key
|
// - Try to restore the e2e backup with a wrong recovery key
|
||||||
val latch2 = CountDownLatch(1)
|
val latch2 = CountDownLatch(1)
|
||||||
@@ -670,7 +659,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
fun testBackupWithPassword() {
|
fun testBackupWithPassword() {
|
||||||
val password = "password"
|
val password = "password"
|
||||||
|
|
||||||
val testData = createKeysBackupScenarioWithPassword(password)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||||
|
|
||||||
// - Restore the e2e backup with the password
|
// - Restore the e2e backup with the password
|
||||||
val steps = ArrayList<StepProgressListener.Step>()
|
val steps = ArrayList<StepProgressListener.Step>()
|
||||||
@@ -710,7 +699,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
|
assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
|
||||||
assertEquals(100, (steps[104] 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)
|
testData.cleanUp(mTestHelper)
|
||||||
}
|
}
|
||||||
@@ -726,7 +715,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val password = "password"
|
val password = "password"
|
||||||
val wrongPassword = "passw0rd"
|
val wrongPassword = "passw0rd"
|
||||||
|
|
||||||
val testData = createKeysBackupScenarioWithPassword(password)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||||
|
|
||||||
// - Try to restore the e2e backup with a wrong password
|
// - Try to restore the e2e backup with a wrong password
|
||||||
val latch2 = CountDownLatch(1)
|
val latch2 = CountDownLatch(1)
|
||||||
@@ -761,7 +750,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
|
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
|
||||||
val password = "password"
|
val password = "password"
|
||||||
|
|
||||||
val testData = createKeysBackupScenarioWithPassword(password)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||||
|
|
||||||
// - Restore the e2e backup with the recovery key.
|
// - Restore the e2e backup with the recovery key.
|
||||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||||
@@ -774,7 +763,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||||
|
|
||||||
testData.cleanUp(mTestHelper)
|
testData.cleanUp(mTestHelper)
|
||||||
}
|
}
|
||||||
@@ -787,7 +776,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
|
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
|
||||||
val testData = createKeysBackupScenarioWithPassword(null)
|
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||||
|
|
||||||
// - Try to restore the e2e backup with a password
|
// - Try to restore the e2e backup with a password
|
||||||
val latch2 = CountDownLatch(1)
|
val latch2 = CountDownLatch(1)
|
||||||
@@ -826,7 +815,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val stateObserver = StateObserver(keysBackup)
|
val stateObserver = StateObserver(keysBackup)
|
||||||
|
|
||||||
// - Do an e2e backup to the homeserver
|
// - Do an e2e backup to the homeserver
|
||||||
prepareAndCreateKeysBackupData(keysBackup)
|
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
// Get key backup version from the home server
|
// Get key backup version from the home server
|
||||||
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
|
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
|
||||||
@@ -846,7 +835,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertTrue(signature.valid)
|
assertTrue(signature.valid)
|
||||||
assertNotNull(signature.device)
|
assertNotNull(signature.device)
|
||||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
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)
|
stateObserver.stopAndCheckStates(null)
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
@@ -871,13 +860,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
assertFalse(keysBackup.isEnabled)
|
assertFalse(keysBackup.isEnabled)
|
||||||
|
|
||||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup)
|
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
assertTrue(keysBackup.isEnabled)
|
assertTrue(keysBackup.isEnabled)
|
||||||
|
|
||||||
// - Restart alice session
|
// - Restart alice session
|
||||||
// - Log Alice on a new device
|
// - 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)
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
|
|
||||||
@@ -951,7 +940,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// - Make alice back up her keys to her homeserver
|
// - Make alice back up her keys to her homeserver
|
||||||
prepareAndCreateKeysBackupData(keysBackup)
|
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
assertTrue(keysBackup.isEnabled)
|
assertTrue(keysBackup.isEnabled)
|
||||||
|
|
||||||
@@ -1001,19 +990,19 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val stateObserver = StateObserver(keysBackup)
|
val stateObserver = StateObserver(keysBackup)
|
||||||
|
|
||||||
// - Make alice back up her keys to her homeserver
|
// - 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.
|
// Wait for keys backup to finish by asking again to backup keys.
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
keysBackup.backupAllGroupSessions(null, it)
|
keysBackup.backupAllGroupSessions(null, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
|
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
|
||||||
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
||||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||||
|
|
||||||
// - Log Alice on a new device
|
// - 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
|
// - Post a message to have a new megolm session
|
||||||
aliceSession2.cryptoService().setWarnOnUnknownDevices(false)
|
aliceSession2.cryptoService().setWarnOnUnknownDevices(false)
|
||||||
@@ -1094,7 +1083,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
assertFalse(keysBackup.isEnabled)
|
assertFalse(keysBackup.isEnabled)
|
||||||
|
|
||||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup)
|
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
assertTrue(keysBackup.isEnabled)
|
assertTrue(keysBackup.isEnabled)
|
||||||
|
|
||||||
@@ -1107,169 +1096,4 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
stateObserver.stopAndCheckStates(null)
|
stateObserver.stopAndCheckStates(null)
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
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)
|
@@ -71,7 +71,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
val TEST_KEY_ID = "my.test.Key"
|
val TEST_KEY_ID = "my.test.Key"
|
||||||
|
|
||||||
mTestHelper.doSync<SsssKeyCreationInfo> {
|
mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||||
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
|
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert Account data is updated
|
// Assert Account data is updated
|
||||||
@@ -177,7 +177,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
val TEST_KEY_ID = "my.test.Key"
|
val TEST_KEY_ID = "my.test.Key"
|
||||||
|
|
||||||
mTestHelper.doSync<SsssKeyCreationInfo> {
|
mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||||
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
|
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that we don't need to wait for an account data sync to access directly the keyid from DB
|
// Test that we don't need to wait for an account data sync to access directly the keyid from DB
|
||||||
@@ -322,7 +322,7 @@ class QuadSTests : InstrumentedTest {
|
|||||||
val quadS = session.sharedSecretStorageService
|
val quadS = session.sharedSecretStorageService
|
||||||
|
|
||||||
val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
|
val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||||
quadS.generateKey(keyId, keyId, emptyKeySigner, it)
|
quadS.generateKey(keyId, null, keyId, emptyKeySigner, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
@@ -23,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
|||||||
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
|
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.OutgoingSasVerificationTransaction
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||||
@@ -33,7 +35,6 @@ import im.vector.matrix.android.common.CommonTestHelper
|
|||||||
import im.vector.matrix.android.common.CryptoTestHelper
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
||||||
@@ -279,7 +280,7 @@ class SASTest : InstrumentedTest {
|
|||||||
val startMessage = KeyVerificationStart(
|
val startMessage = KeyVerificationStart(
|
||||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
||||||
method = VerificationMethod.SAS.toValue(),
|
method = VerificationMethod.SAS.toValue(),
|
||||||
transactionID = tid,
|
transactionId = tid,
|
||||||
keyAgreementProtocols = protocols,
|
keyAgreementProtocols = protocols,
|
||||||
hashes = hashes,
|
hashes = hashes,
|
||||||
messageAuthenticationCodes = mac,
|
messageAuthenticationCodes = mac,
|
||||||
@@ -350,16 +351,17 @@ class SASTest : InstrumentedTest {
|
|||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||||
|
|
||||||
var accepted: KeyVerificationAccept? = null
|
var accepted: ValidVerificationInfoAccept? = null
|
||||||
var startReq: KeyVerificationStart? = null
|
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||||
|
|
||||||
val aliceAcceptedLatch = CountDownLatch(1)
|
val aliceAcceptedLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
|
||||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||||
val at = tx as SASDefaultVerificationTransaction
|
val at = tx as SASDefaultVerificationTransaction
|
||||||
accepted = at.accepted as? KeyVerificationAccept
|
accepted = at.accepted
|
||||||
startReq = at.startReq as? KeyVerificationStart
|
startReq = at.startReq
|
||||||
aliceAcceptedLatch.countDown()
|
aliceAcceptedLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +370,9 @@ class SASTest : InstrumentedTest {
|
|||||||
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||||
|
bobVerificationService.removeListener(this)
|
||||||
val at = tx as IncomingSasVerificationTransaction
|
val at = tx as IncomingSasVerificationTransaction
|
||||||
at.performAccept()
|
at.performAccept()
|
||||||
}
|
}
|
||||||
@@ -384,13 +388,13 @@ class SASTest : InstrumentedTest {
|
|||||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||||
|
|
||||||
// check that agreement is valid
|
// check that agreement is valid
|
||||||
assertTrue("Agreed Protocol should be Valid", accepted!!.isValid())
|
assertTrue("Agreed Protocol should be Valid", accepted != null)
|
||||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols!!.contains(accepted!!.keyAgreementProtocol))
|
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
|
||||||
assertTrue("Hash should be known by alice", startReq!!.hashes!!.contains(accepted!!.hash))
|
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
|
||||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode))
|
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
|
||||||
|
|
||||||
accepted!!.shortAuthenticationStrings?.forEach {
|
accepted!!.shortAuthenticationStrings.forEach {
|
||||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
|
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
@@ -464,14 +468,19 @@ class SASTest : InstrumentedTest {
|
|||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
val aliceSASLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
|
var matchOnce = true
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||||
|
Log.v("TEST", "== aliceState ${uxState.name}")
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||||
tx.userHasVerifiedShortCode()
|
tx.userHasVerifiedShortCode()
|
||||||
}
|
}
|
||||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||||
aliceSASLatch.countDown()
|
if (matchOnce) {
|
||||||
|
matchOnce = false
|
||||||
|
aliceSASLatch.countDown()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
@@ -481,14 +490,23 @@ class SASTest : InstrumentedTest {
|
|||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
val bobSASLatch = CountDownLatch(1)
|
||||||
val bobListener = object : VerificationService.Listener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
|
var acceptOnce = true
|
||||||
|
var matchOnce = true
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||||
|
Log.v("TEST", "== bobState ${uxState.name}")
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||||
tx.performAccept()
|
if (acceptOnce) {
|
||||||
|
acceptOnce = false
|
||||||
|
tx.performAccept()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||||
tx.userHasVerifiedShortCode()
|
if (matchOnce) {
|
||||||
|
matchOnce = false
|
||||||
|
tx.userHasVerifiedShortCode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||||
bobSASLatch.countDown()
|
bobSASLatch.countDown()
|
||||||
@@ -516,4 +534,96 @@ class SASTest : InstrumentedTest {
|
|||||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_ConcurrentStart() {
|
||||||
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||||
|
|
||||||
|
val req = aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
|
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
|
bobSession.myUserId,
|
||||||
|
cryptoTestData.roomId
|
||||||
|
)
|
||||||
|
|
||||||
|
var requestID : String? = null
|
||||||
|
|
||||||
|
mTestHelper.waitWithLatch {
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||||
|
val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull()
|
||||||
|
requestID = prAlicePOV?.transactionId
|
||||||
|
Log.v("TEST", "== alicePOV is $prAlicePOV")
|
||||||
|
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v("TEST", "== requestID is $requestID")
|
||||||
|
|
||||||
|
mTestHelper.waitWithLatch {
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||||
|
val prBobPOV = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId)?.firstOrNull()
|
||||||
|
Log.v("TEST", "== prBobPOV is $prBobPOV")
|
||||||
|
prBobPOV?.transactionId == requestID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bobVerificationService.readyPendingVerification(
|
||||||
|
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
|
aliceSession.myUserId,
|
||||||
|
requestID!!
|
||||||
|
)
|
||||||
|
|
||||||
|
// wait for alice to get the ready
|
||||||
|
mTestHelper.waitWithLatch {
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||||
|
val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull()
|
||||||
|
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
|
||||||
|
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start concurrent!
|
||||||
|
aliceVerificationService.beginKeyVerificationInDMs(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
requestID!!,
|
||||||
|
cryptoTestData.roomId,
|
||||||
|
bobSession.myUserId,
|
||||||
|
bobSession.sessionParams.deviceId!!,
|
||||||
|
null)
|
||||||
|
|
||||||
|
bobVerificationService.beginKeyVerificationInDMs(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
requestID!!,
|
||||||
|
cryptoTestData.roomId,
|
||||||
|
aliceSession.myUserId,
|
||||||
|
aliceSession.sessionParams.deviceId!!,
|
||||||
|
null)
|
||||||
|
|
||||||
|
// we should reach SHOW SAS on both
|
||||||
|
var alicePovTx: SasVerificationTransaction?
|
||||||
|
var bobPovTx: SasVerificationTransaction?
|
||||||
|
|
||||||
|
mTestHelper.waitWithLatch {
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||||
|
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||||
|
Log.v("TEST", "== alicePovTx is $alicePovTx")
|
||||||
|
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// wait for alice to get the ready
|
||||||
|
mTestHelper.waitWithLatch {
|
||||||
|
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||||
|
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||||
|
Log.v("TEST", "== bobPovTx is $bobPovTx")
|
||||||
|
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ import im.vector.matrix.android.common.CommonTestHelper
|
|||||||
import im.vector.matrix.android.common.CryptoTestHelper
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
import im.vector.matrix.android.common.TestConstants
|
import im.vector.matrix.android.common.TestConstants
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,6 @@ package im.vector.matrix.android.internal.network.interceptors
|
|||||||
import im.vector.matrix.android.internal.di.MatrixScope
|
import im.vector.matrix.android.internal.di.MatrixScope
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -37,7 +36,7 @@ import javax.inject.Inject
|
|||||||
* non-production environment.
|
* non-production environment.
|
||||||
*/
|
*/
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
internal class CurlLoggingInterceptor @Inject constructor(private val logger: HttpLoggingInterceptor.Logger)
|
internal class CurlLoggingInterceptor @Inject constructor()
|
||||||
: Interceptor {
|
: Interceptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,8 +96,8 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
|||||||
// Add Json formatting
|
// Add Json formatting
|
||||||
curlCmd += " | python -m json.tool"
|
curlCmd += " | python -m json.tool"
|
||||||
|
|
||||||
logger.log("--- cURL (" + request.url + ")")
|
Timber.d("--- cURL (${request.url})")
|
||||||
logger.log(curlCmd)
|
Timber.d(curlCmd)
|
||||||
|
|
||||||
return chain.proceed(request)
|
return chain.proceed(request)
|
||||||
}
|
}
|
||||||
|
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,6 @@ import androidx.work.WorkManager
|
|||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.api.crypto.MXCryptoConfig
|
|
||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
@@ -32,19 +31,10 @@ import im.vector.matrix.android.internal.network.UserAgentHolder
|
|||||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
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.
|
* This is the main entry point to the matrix sdk.
|
||||||
* To get the singleton instance, use getInstance static method.
|
* To get the singleton instance, use getInstance static method.
|
||||||
@@ -61,7 +51,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||||||
Monarchy.init(context)
|
Monarchy.init(context)
|
||||||
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
||||||
if (context.applicationContext !is Configuration.Provider) {
|
if (context.applicationContext !is Configuration.Provider) {
|
||||||
WorkManager.initialize(context, Configuration.Builder().build())
|
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
|
||||||
}
|
}
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||||
}
|
}
|
||||||
|
@@ -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.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
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.login.LoginWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
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.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
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.
|
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||||
*/
|
*/
|
||||||
interface AuthenticationService {
|
interface AuthenticationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the supported login flows for this homeserver.
|
* 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
|
* 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
|
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.
|
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||||
*/
|
*/
|
||||||
@@ -74,19 +78,26 @@ interface AuthenticationService {
|
|||||||
*/
|
*/
|
||||||
fun getLastAuthenticatedSession(): Session?
|
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
|
* Create a session after a SSO successful login
|
||||||
*/
|
*/
|
||||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
callback: MatrixCallback<Session>): Cancelable
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a wellknown request, using the domain from the matrixId
|
||||||
|
*/
|
||||||
|
fun getWellKnownData(matrixId: String,
|
||||||
|
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.
|
* This data class hold credentials user data.
|
||||||
* You shouldn't have to instantiate it.
|
* You shouldn't have to instantiate it.
|
||||||
* The access token should be use to authenticate user in all server requests.
|
* 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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Credentials(
|
data class Credentials(
|
||||||
|
/**
|
||||||
|
* The fully-qualified Matrix ID that has been registered.
|
||||||
|
*/
|
||||||
@Json(name = "user_id") val userId: String,
|
@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,
|
@Json(name = "access_token") val accessToken: String,
|
||||||
|
/**
|
||||||
|
* Not documented
|
||||||
|
*/
|
||||||
@Json(name = "refresh_token") val refreshToken: String?,
|
@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?,
|
@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 {
|
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 com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig.Builder
|
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.network.ssl.Fingerprint
|
||||||
|
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||||
import okhttp3.CipherSuite
|
import okhttp3.CipherSuite
|
||||||
import okhttp3.TlsVersion
|
import okhttp3.TlsVersion
|
||||||
|
|
||||||
@@ -68,18 +69,14 @@ data class HomeServerConnectionConfig(
|
|||||||
*/
|
*/
|
||||||
fun withHomeServerUri(hsUri: Uri): Builder {
|
fun withHomeServerUri(hsUri: Uri): Builder {
|
||||||
if (hsUri.scheme != "http" && hsUri.scheme != "https") {
|
if (hsUri.scheme != "http" && hsUri.scheme != "https") {
|
||||||
throw RuntimeException("Invalid home server URI: " + hsUri)
|
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||||
}
|
}
|
||||||
// ensure trailing /
|
// ensure trailing /
|
||||||
homeServerUri = if (!hsUri.toString().endsWith("/")) {
|
val hsString = hsUri.toString().ensureTrailingSlash()
|
||||||
try {
|
homeServerUri = try {
|
||||||
val url = hsUri.toString()
|
Uri.parse(hsString)
|
||||||
Uri.parse("$url/")
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hsUri
|
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@@ -97,15 +94,11 @@ data class HomeServerConnectionConfig(
|
|||||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||||
}
|
}
|
||||||
// ensure trailing /
|
// ensure trailing /
|
||||||
if (!identityServerUri.toString().endsWith("/")) {
|
val isString = identityServerUri.toString().ensureTrailingSlash()
|
||||||
try {
|
this.identityServerUri = try {
|
||||||
val url = identityServerUri.toString()
|
Uri.parse(isString)
|
||||||
this.identityServerUri = Uri.parse("$url/")
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.identityServerUri = identityServerUri
|
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@@ -16,12 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.auth.data
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
// Either a list of supported login types, or an error if the homeserver is outdated
|
||||||
|
|
||||||
// Either a LoginFlowResponse, or an error if the homeserver is outdated
|
|
||||||
sealed class LoginFlowResult {
|
sealed class LoginFlowResult {
|
||||||
data class Success(
|
data class Success(
|
||||||
val loginFlowResponse: LoginFlowResponse,
|
val supportedLoginTypes: List<String>,
|
||||||
val isLoginAndRegistrationSupported: Boolean,
|
val isLoginAndRegistrationSupported: Boolean,
|
||||||
val homeServerUrl: String
|
val homeServerUrl: String
|
||||||
) : LoginFlowResult()
|
) : LoginFlowResult()
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.data
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
object LoginFlowTypes {
|
object LoginFlowTypes {
|
||||||
const val PASSWORD = "m.login.password"
|
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.
|
* You don't have to manually instantiate it.
|
||||||
*/
|
*/
|
||||||
data class SessionParams(
|
data class SessionParams(
|
||||||
|
/**
|
||||||
|
* Please consider using shortcuts instead
|
||||||
|
*/
|
||||||
val credentials: Credentials,
|
val credentials: Credentials,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Please consider using shortcuts instead
|
||||||
|
*/
|
||||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
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
|
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.Json
|
||||||
import com.squareup.moshi.JsonClass
|
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
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||||
@@ -52,31 +53,5 @@ data class WellKnown(
|
|||||||
val identityServer: WellKnownBaseConfig? = null,
|
val identityServer: WellKnownBaseConfig? = null,
|
||||||
|
|
||||||
@Json(name = "m.integrations")
|
@Json(name = "m.integrations")
|
||||||
val integrations: Map<String, @JvmSuppressWildcards Any>? = null
|
val integrations: JsonDict? = 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -34,6 +34,12 @@ interface LoginWizard {
|
|||||||
deviceName: String,
|
deviceName: String,
|
||||||
callback: MatrixCallback<Session>): Cancelable
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange a login token to an access token
|
||||||
|
*/
|
||||||
|
fun loginWithToken(loginToken: String,
|
||||||
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset user password
|
* Reset user password
|
||||||
*/
|
*/
|
||||||
|
@@ -21,15 +21,12 @@ sealed class Stage(open val mandatory: Boolean) {
|
|||||||
// m.login.recaptcha
|
// m.login.recaptcha
|
||||||
data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
|
data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
|
||||||
|
|
||||||
// m.login.oauth2
|
|
||||||
// m.login.email.identity
|
// m.login.email.identity
|
||||||
data class Email(override val mandatory: Boolean) : Stage(mandatory)
|
data class Email(override val mandatory: Boolean) : Stage(mandatory)
|
||||||
|
|
||||||
// m.login.msisdn
|
// m.login.msisdn
|
||||||
data class Msisdn(override val mandatory: Boolean) : Stage(mandatory)
|
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
|
// 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
|
// and a password, the dummy stage has to be done
|
||||||
data class Dummy(override val mandatory: Boolean) : Stage(mandatory)
|
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,9 +19,17 @@ package im.vector.matrix.android.api.crypto
|
|||||||
/**
|
/**
|
||||||
* Class to define the parameters used to customize or configure the end-to-end 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.
|
// Tell whether the encryption of the event content is enabled for the invited members.
|
||||||
// SDK clients can disable this by settings it to false.
|
// SDK clients can disable this by settings it to false.
|
||||||
// Note that the encryption for the invited members will be blocked if the history visibility is "joined".
|
// 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)
|
||||||
|
* coming from your other untrusted sessions (or blocked).
|
||||||
|
* If set to false, the request will be forwarded to the application layer; in this
|
||||||
|
* case the application can decide to prompt the user.
|
||||||
|
*/
|
||||||
|
val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true
|
||||||
)
|
)
|
||||||
|
@@ -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.extensions
|
||||||
|
|
||||||
|
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
|
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
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
fun Throwable.is401() =
|
fun Throwable.is401() =
|
||||||
@@ -29,5 +33,27 @@ fun Throwable.isTokenError() =
|
|||||||
|
|
||||||
fun Throwable.shouldBeRetried(): Boolean {
|
fun Throwable.shouldBeRetried(): Boolean {
|
||||||
return this is Failure.NetworkConnection
|
return this is Failure.NetworkConnection
|
||||||
|
|| this is IOException
|
||||||
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
|
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Throwable.isInvalidPassword(): Boolean {
|
||||||
|
return this is Failure.ServerError
|
||||||
|
&& 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -39,7 +39,10 @@ data class MatrixError(
|
|||||||
// For M_LIMIT_EXCEEDED
|
// For M_LIMIT_EXCEEDED
|
||||||
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
||||||
// For M_UNKNOWN_TOKEN
|
// 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 {
|
companion object {
|
||||||
@@ -129,6 +132,11 @@ data class MatrixError(
|
|||||||
/** (Not documented yet) */
|
/** (Not documented yet) */
|
||||||
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
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"
|
// Possible value for "limit_type"
|
||||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||||
}
|
}
|
||||||
|
@@ -17,26 +17,37 @@ package im.vector.matrix.android.api.pushrules
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
|
import im.vector.matrix.android.api.pushrules.rest.RuleSet
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
interface PushRuleService {
|
interface PushRuleService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the push rules from the server
|
* Fetch the push rules from the server
|
||||||
*/
|
*/
|
||||||
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
|
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
|
||||||
|
|
||||||
// TODO get push rule set
|
fun getPushRules(scope: String = RuleScope.GLOBAL): RuleSet
|
||||||
fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
|
|
||||||
|
|
||||||
// TODO update rule
|
fun updatePushRuleEnableStatus(kind: RuleKind,
|
||||||
|
pushRule: PushRule,
|
||||||
|
enabled: Boolean,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
fun addPushRule(kind: RuleKind,
|
||||||
|
pushRule: PushRule,
|
||||||
|
beforeRuleId: String?,
|
||||||
|
afterRuleId: String?,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
fun updatePushRuleActions(kind: RuleKind,
|
||||||
|
oldPushRule: PushRule,
|
||||||
|
newPushRule: PushRule,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
fun removePushRule(kind: RuleKind,
|
||||||
|
pushRule: PushRule,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun addPushRuleListener(listener: PushRuleListener)
|
fun addPushRuleListener(listener: PushRuleListener)
|
||||||
|
|
||||||
|
@@ -39,6 +39,6 @@ class SenderNotificationPermissionCondition(
|
|||||||
|
|
||||||
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
||||||
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import com.squareup.moshi.JsonClass
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* All push rulesets for a user.
|
* All push rulesets for a user.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class GetPushRulesResponse(
|
internal data class GetPushRulesResponse(
|
||||||
@@ -27,11 +28,11 @@ internal data class GetPushRulesResponse(
|
|||||||
* Global rules, account level applying to all devices
|
* Global rules, account level applying to all devices
|
||||||
*/
|
*/
|
||||||
@Json(name = "global")
|
@Json(name = "global")
|
||||||
val global: Ruleset,
|
val global: RuleSet,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device specific rules, apply only to current device
|
* Device specific rules, apply only to current device. Not used anymore
|
||||||
*/
|
*/
|
||||||
@Json(name = "device")
|
@Json(name = "device")
|
||||||
val device: Ruleset? = null
|
val device: RuleSet? = null
|
||||||
)
|
)
|
||||||
|
@@ -24,21 +24,27 @@ import im.vector.matrix.android.api.pushrules.RoomMemberCountCondition
|
|||||||
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
|
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class PushCondition(
|
data class PushCondition(
|
||||||
/**
|
/**
|
||||||
* Required. The kind of condition to apply.
|
* Required. The kind of condition to apply.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "kind")
|
||||||
val kind: String,
|
val kind: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required for event_match conditions. The dot- separated field of the event to match.
|
* Required for event_match conditions. The dot- separated field of the event to match.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "key")
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required for event_match conditions.
|
* Required for event_match conditions.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "pattern")
|
||||||
val pattern: String? = null,
|
val pattern: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +53,8 @@ data class PushCondition(
|
|||||||
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth.
|
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth.
|
||||||
* If no prefix is present, this parameter defaults to ==.
|
* If no prefix is present, this parameter defaults to ==.
|
||||||
*/
|
*/
|
||||||
@Json(name = "is") val iz: String? = null
|
@Json(name = "is")
|
||||||
|
val iz: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun asExecutableCondition(): Condition? {
|
fun asExecutableCondition(): Condition? {
|
||||||
|
@@ -18,31 +18,158 @@ package im.vector.matrix.android.api.pushrules.rest
|
|||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.pushrules.Action
|
||||||
|
import im.vector.matrix.android.api.pushrules.getActions
|
||||||
|
import im.vector.matrix.android.api.pushrules.toJson
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class PushRule(
|
data class PushRule(
|
||||||
/**
|
/**
|
||||||
* Required. The actions to perform when this rule is matched.
|
* Required. The actions to perform when this rule is matched.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "actions")
|
||||||
val actions: List<Any>,
|
val actions: List<Any>,
|
||||||
/**
|
/**
|
||||||
* Required. Whether this is a default rule, or has been set explicitly.
|
* Required. Whether this is a default rule, or has been set explicitly.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "default")
|
||||||
val default: Boolean? = false,
|
val default: Boolean? = false,
|
||||||
/**
|
/**
|
||||||
* Required. Whether the push rule is enabled or not.
|
* Required. Whether the push rule is enabled or not.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "enabled")
|
||||||
val enabled: Boolean,
|
val enabled: Boolean,
|
||||||
/**
|
/**
|
||||||
* Required. The ID of this rule.
|
* Required. The ID of this rule.
|
||||||
*/
|
*/
|
||||||
@Json(name = "rule_id") val ruleId: String,
|
@Json(name = "rule_id")
|
||||||
|
val ruleId: String,
|
||||||
/**
|
/**
|
||||||
* The conditions that must hold true for an event in order for a rule to be applied to an event
|
* The conditions that must hold true for an event in order for a rule to be applied to an event
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "conditions")
|
||||||
val conditions: List<PushCondition>? = null,
|
val conditions: List<PushCondition>? = null,
|
||||||
/**
|
/**
|
||||||
* The glob-style pattern to match against. Only applicable to content rules.
|
* The glob-style pattern to match against. Only applicable to content rules.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "pattern")
|
||||||
val pattern: String? = null
|
val pattern: String? = null
|
||||||
)
|
) {
|
||||||
|
/**
|
||||||
|
* Add the default notification sound.
|
||||||
|
*/
|
||||||
|
fun setNotificationSound(): PushRule {
|
||||||
|
return setNotificationSound(ACTION_VALUE_DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNotificationSound(): String? {
|
||||||
|
return (getActions().firstOrNull { it is Action.Sound } as? Action.Sound)?.sound
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the notification sound
|
||||||
|
*
|
||||||
|
* @param sound notification sound
|
||||||
|
*/
|
||||||
|
fun setNotificationSound(sound: String): PushRule {
|
||||||
|
return copy(
|
||||||
|
actions = (getActions().filter { it !is Action.Sound } + Action.Sound(sound)).toJson()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the notification sound
|
||||||
|
*/
|
||||||
|
fun removeNotificationSound(): PushRule {
|
||||||
|
return copy(
|
||||||
|
actions = getActions().filter { it !is Action.Sound }.toJson()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the highlight status.
|
||||||
|
*
|
||||||
|
* @param highlight the highlight status
|
||||||
|
*/
|
||||||
|
fun setHighlight(highlight: Boolean): PushRule {
|
||||||
|
return copy(
|
||||||
|
actions = (getActions().filter { it !is Action.Highlight } + Action.Highlight(highlight)).toJson()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the notification status.
|
||||||
|
*
|
||||||
|
* @param notify true to notify
|
||||||
|
*/
|
||||||
|
fun setNotify(notify: Boolean): PushRule {
|
||||||
|
val mutableActions = actions.toMutableList()
|
||||||
|
|
||||||
|
mutableActions.remove(ACTION_DONT_NOTIFY)
|
||||||
|
mutableActions.remove(ACTION_NOTIFY)
|
||||||
|
|
||||||
|
if (notify) {
|
||||||
|
mutableActions.add(ACTION_NOTIFY)
|
||||||
|
} else {
|
||||||
|
mutableActions.add(ACTION_DONT_NOTIFY)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy(actions = mutableActions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the rule should highlight the event.
|
||||||
|
*
|
||||||
|
* @return true if the rule should play sound
|
||||||
|
*/
|
||||||
|
fun shouldNotify() = actions.contains(ACTION_NOTIFY)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the rule should not highlight the event.
|
||||||
|
*
|
||||||
|
* @return true if the rule should not play sound
|
||||||
|
*/
|
||||||
|
fun shouldNotNotify() = actions.contains(ACTION_DONT_NOTIFY)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/* ==========================================================================================
|
||||||
|
* Rule id
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
const val RULE_ID_DISABLE_ALL = ".m.rule.master"
|
||||||
|
const val RULE_ID_CONTAIN_USER_NAME = ".m.rule.contains_user_name"
|
||||||
|
const val RULE_ID_CONTAIN_DISPLAY_NAME = ".m.rule.contains_display_name"
|
||||||
|
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
|
||||||
|
const val RULE_ID_INVITE_ME = ".m.rule.invite_for_me"
|
||||||
|
const val RULE_ID_PEOPLE_JOIN_LEAVE = ".m.rule.member_event"
|
||||||
|
const val RULE_ID_CALL = ".m.rule.call"
|
||||||
|
const val RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS = ".m.rule.suppress_notices"
|
||||||
|
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
|
||||||
|
const val RULE_ID_AT_ROOMS = ".m.rule.roomnotif"
|
||||||
|
const val RULE_ID_TOMBSTONE = ".m.rule.tombstone"
|
||||||
|
const val RULE_ID_E2E_ONE_TO_ONE_ROOM = ".m.rule.encrypted_room_one_to_one"
|
||||||
|
const val RULE_ID_E2E_GROUP = ".m.rule.encrypted"
|
||||||
|
const val RULE_ID_REACTION = ".m.rule.reaction"
|
||||||
|
const val RULE_ID_FALLBACK = ".m.rule.fallback"
|
||||||
|
|
||||||
|
/* ==========================================================================================
|
||||||
|
* Actions
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
const val ACTION_NOTIFY = "notify"
|
||||||
|
const val ACTION_DONT_NOTIFY = "dont_notify"
|
||||||
|
const val ACTION_COALESCE = "coalesce"
|
||||||
|
|
||||||
|
const val ACTION_SET_TWEAK_SOUND_VALUE = "sound"
|
||||||
|
const val ACTION_SET_TWEAK_HIGHLIGHT_VALUE = "highlight"
|
||||||
|
|
||||||
|
const val ACTION_PARAMETER_SET_TWEAK = "set_tweak"
|
||||||
|
const val ACTION_PARAMETER_VALUE = "value"
|
||||||
|
|
||||||
|
const val ACTION_VALUE_DEFAULT = "default"
|
||||||
|
const val ACTION_VALUE_RING = "ring"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.pushrules.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.pushrules.RuleSetKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RuleSet(
|
||||||
|
@Json(name = "content")
|
||||||
|
val content: List<PushRule>? = null,
|
||||||
|
@Json(name = "override")
|
||||||
|
val override: List<PushRule>? = null,
|
||||||
|
@Json(name = "room")
|
||||||
|
val room: List<PushRule>? = null,
|
||||||
|
@Json(name = "sender")
|
||||||
|
val sender: List<PushRule>? = null,
|
||||||
|
@Json(name = "underride")
|
||||||
|
val underride: List<PushRule>? = null
|
||||||
|
) {
|
||||||
|
fun getAllRules(): List<PushRule> {
|
||||||
|
// Ref. for the order: https://matrix.org/docs/spec/client_server/latest#push-rules
|
||||||
|
return override.orEmpty() + content.orEmpty() + room.orEmpty() + sender.orEmpty() + underride.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a rule from its ruleID.
|
||||||
|
*
|
||||||
|
* @param ruleId a RULE_ID_XX value
|
||||||
|
* @return the matched bing rule or null it doesn't exist.
|
||||||
|
*/
|
||||||
|
fun findDefaultRule(ruleId: String?): PushRuleAndKind? {
|
||||||
|
var result: PushRuleAndKind? = null
|
||||||
|
// sanity check
|
||||||
|
if (null != ruleId) {
|
||||||
|
if (PushRule.RULE_ID_CONTAIN_USER_NAME == ruleId) {
|
||||||
|
result = findRule(content, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.CONTENT) }
|
||||||
|
} else {
|
||||||
|
// assume that the ruleId is unique.
|
||||||
|
result = findRule(override, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.OVERRIDE) }
|
||||||
|
if (null == result) {
|
||||||
|
result = findRule(underride, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.UNDERRIDE) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a rule from its rule Id.
|
||||||
|
*
|
||||||
|
* @param rules the rules list.
|
||||||
|
* @param ruleId the rule Id.
|
||||||
|
* @return the bing rule if it exists, else null.
|
||||||
|
*/
|
||||||
|
private fun findRule(rules: List<PushRule>?, ruleId: String): PushRule? {
|
||||||
|
return rules?.firstOrNull { it.ruleId == ruleId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class PushRuleAndKind(
|
||||||
|
val pushRule: PushRule,
|
||||||
|
val kind: RuleSetKey
|
||||||
|
)
|
@@ -25,8 +25,8 @@ sealed class QueryStringValue {
|
|||||||
object IsNotNull : QueryStringValue()
|
object IsNotNull : QueryStringValue()
|
||||||
object IsEmpty : QueryStringValue()
|
object IsEmpty : QueryStringValue()
|
||||||
object IsNotEmpty : QueryStringValue()
|
object IsNotEmpty : QueryStringValue()
|
||||||
data class Equals(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) : QueryStringValue()
|
data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||||
|
|
||||||
enum class Case {
|
enum class Case {
|
||||||
SENSITIVE,
|
SENSITIVE,
|
||||||
|
@@ -21,14 +21,18 @@ import androidx.lifecycle.LiveData
|
|||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.failure.GlobalError
|
import im.vector.matrix.android.api.failure.GlobalError
|
||||||
import im.vector.matrix.android.api.pushrules.PushRuleService
|
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.accountdata.AccountDataService
|
||||||
import im.vector.matrix.android.api.session.cache.CacheService
|
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.ContentUploadStateTracker
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
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.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.file.FileService
|
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.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
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.profile.ProfileService
|
||||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||||
@@ -38,7 +42,9 @@ 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.signout.SignOutService
|
||||||
import im.vector.matrix.android.api.session.sync.FilterService
|
import im.vector.matrix.android.api.session.sync.FilterService
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
|
import im.vector.matrix.android.api.session.terms.TermsService
|
||||||
import im.vector.matrix.android.api.session.user.UserService
|
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.
|
* This interface defines interactions with a session.
|
||||||
@@ -53,13 +59,15 @@ interface Session :
|
|||||||
SignOutService,
|
SignOutService,
|
||||||
FilterService,
|
FilterService,
|
||||||
FileService,
|
FileService,
|
||||||
|
TermsService,
|
||||||
ProfileService,
|
ProfileService,
|
||||||
PushRuleService,
|
PushRuleService,
|
||||||
PushersService,
|
PushersService,
|
||||||
InitialSyncProgressService,
|
InitialSyncProgressService,
|
||||||
HomeServerCapabilitiesService,
|
HomeServerCapabilitiesService,
|
||||||
SecureStorageService,
|
SecureStorageService,
|
||||||
AccountDataService {
|
AccountDataService,
|
||||||
|
AccountService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The params associated to the session
|
* The params associated to the session
|
||||||
@@ -75,7 +83,7 @@ interface Session :
|
|||||||
* Useful shortcut to get access to the userId
|
* Useful shortcut to get access to the userId
|
||||||
*/
|
*/
|
||||||
val myUserId: String
|
val myUserId: String
|
||||||
get() = sessionParams.credentials.userId
|
get() = sessionParams.userId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sessionId
|
* The sessionId
|
||||||
@@ -143,6 +151,26 @@ interface Session :
|
|||||||
*/
|
*/
|
||||||
fun cryptoService(): CryptoService
|
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener to the session.
|
* Add a listener to the session.
|
||||||
* @param listener the listener to add.
|
* @param listener the listener to add.
|
||||||
|
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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.account
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
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?
|
||||||
|
)
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.content
|
package im.vector.matrix.android.api.session.content
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
@@ -29,9 +30,8 @@ data class ContentAttachmentData(
|
|||||||
val width: Long? = 0,
|
val width: Long? = 0,
|
||||||
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
|
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val queryUri: String,
|
val queryUri: Uri,
|
||||||
val path: String,
|
private val mimeType: String?,
|
||||||
val mimeType: String?,
|
|
||||||
val type: Type
|
val type: Type
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
@@ -41,4 +41,6 @@ data class ContentAttachmentData(
|
|||||||
AUDIO,
|
AUDIO,
|
||||||
VIDEO
|
VIDEO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSafeMimeType() = if (mimeType == "image/jpg") "image/jpeg" else mimeType
|
||||||
}
|
}
|
||||||
|
@@ -26,6 +26,11 @@ interface ContentUrlResolver {
|
|||||||
SCALE("scale")
|
SCALE("scale")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL to use to upload content
|
||||||
|
*/
|
||||||
|
val uploadUrl: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the actual URL for accessing the full-size image of a Matrix media content URI.
|
* Get the actual URL for accessing the full-size image of a Matrix media content URI.
|
||||||
*
|
*
|
||||||
|
@@ -22,12 +22,14 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||||
import im.vector.matrix.android.api.session.events.model.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||||
|
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
@@ -86,15 +88,19 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||||
|
|
||||||
|
fun requestRoomKeyForEvent(event: Event)
|
||||||
|
|
||||||
fun reRequestRoomKeyForEvent(event: Event)
|
fun reRequestRoomKeyForEvent(event: Event)
|
||||||
|
|
||||||
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
|
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
|
||||||
|
|
||||||
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener)
|
fun addRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||||
|
|
||||||
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener)
|
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>)
|
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||||
|
|
||||||
@@ -107,6 +113,8 @@ interface CryptoService {
|
|||||||
roomId: String,
|
roomId: String,
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
callback: MatrixCallback<MXEncryptEventContentResult>)
|
||||||
|
|
||||||
|
fun discardOutboundSession(roomId: String)
|
||||||
|
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||||
|
|
||||||
@@ -129,4 +137,10 @@ interface CryptoService {
|
|||||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||||
|
|
||||||
fun removeSessionListener(listener: NewSessionListener)
|
fun removeSessionListener(listener: NewSessionListener)
|
||||||
|
|
||||||
|
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||||
|
|
||||||
|
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||||
|
|
||||||
|
fun getGossipingEventsTrail(): List<Event>
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.util.Optional
|
|||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
|
||||||
|
|
||||||
interface CrossSigningService {
|
interface CrossSigningService {
|
||||||
|
|
||||||
@@ -52,6 +53,10 @@ interface CrossSigningService {
|
|||||||
|
|
||||||
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
||||||
|
|
||||||
|
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
|
||||||
|
|
||||||
|
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
|
||||||
|
|
||||||
fun canCrossSign(): Boolean
|
fun canCrossSign(): Boolean
|
||||||
|
|
||||||
fun trustUser(otherUserId: String,
|
fun trustUser(otherUserId: String,
|
||||||
@@ -68,4 +73,7 @@ interface CrossSigningService {
|
|||||||
fun checkDeviceTrust(otherUserId: String,
|
fun checkDeviceTrust(otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String,
|
||||||
locallyTrusted: Boolean?): DeviceTrustResult
|
locallyTrusted: Boolean?): DeviceTrustResult
|
||||||
|
|
||||||
|
fun onSecretSSKGossip(sskPrivateKey: String)
|
||||||
|
fun onSecretUSKGossip(uskPrivateKey: String)
|
||||||
}
|
}
|
||||||
|
@@ -21,3 +21,5 @@ const val MASTER_KEY_SSSS_NAME = "m.cross_signing.master"
|
|||||||
const val USER_SIGNING_KEY_SSSS_NAME = "m.cross_signing.user_signing"
|
const val USER_SIGNING_KEY_SSSS_NAME = "m.cross_signing.user_signing"
|
||||||
|
|
||||||
const val SELF_SIGNING_KEY_SSSS_NAME = "m.cross_signing.self_signing"
|
const val SELF_SIGNING_KEY_SSSS_NAME = "m.cross_signing.self_signing"
|
||||||
|
|
||||||
|
const val KEYBACKUP_SECRET_SSSS_NAME = "m.megolm_backup.v1"
|
||||||
|
@@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCre
|
|||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
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.keysbackup.model.rest.KeysVersionResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||||
|
|
||||||
interface KeysBackupService {
|
interface KeysBackupService {
|
||||||
/**
|
/**
|
||||||
@@ -172,6 +173,8 @@ interface KeysBackupService {
|
|||||||
password: String,
|
password: String,
|
||||||
callback: MatrixCallback<Unit>)
|
callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
fun onSecretKeyGossip(secret: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore a backup with a recovery key from a given backup version stored on the homeserver.
|
* Restore a backup with a recovery key from a given backup version stored on the homeserver.
|
||||||
*
|
*
|
||||||
@@ -210,4 +213,10 @@ interface KeysBackupService {
|
|||||||
val isEnabled: Boolean
|
val isEnabled: Boolean
|
||||||
val isStucked: Boolean
|
val isStucked: Boolean
|
||||||
val state: KeysBackupState
|
val state: KeysBackupState
|
||||||
|
|
||||||
|
// For gossiping
|
||||||
|
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
||||||
|
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
|
||||||
|
|
||||||
|
fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>)
|
||||||
}
|
}
|
||||||
|
@@ -17,12 +17,13 @@
|
|||||||
package im.vector.matrix.android.api.session.crypto.keyshare
|
package im.vector.matrix.android.api.session.crypto.keyshare
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation
|
import im.vector.matrix.android.internal.crypto.IncomingRequestCancellation
|
||||||
|
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Room keys events listener
|
* Room keys events listener
|
||||||
*/
|
*/
|
||||||
interface RoomKeysRequestListener {
|
interface GossipingRequestListener {
|
||||||
/**
|
/**
|
||||||
* An room key request has been received.
|
* An room key request has been received.
|
||||||
*
|
*
|
||||||
@@ -30,10 +31,16 @@ interface RoomKeysRequestListener {
|
|||||||
*/
|
*/
|
||||||
fun onRoomKeyRequest(request: IncomingRoomKeyRequest)
|
fun onRoomKeyRequest(request: IncomingRoomKeyRequest)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the secret value to be shared
|
||||||
|
* @return true if is handled
|
||||||
|
*/
|
||||||
|
fun onSecretShareRequest(request: IncomingSecretShareRequest) : Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A room key request cancellation has been received.
|
* A room key request cancellation has been received.
|
||||||
*
|
*
|
||||||
* @param request the cancellation request
|
* @param request the cancellation request
|
||||||
*/
|
*/
|
||||||
fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation)
|
fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation)
|
||||||
}
|
}
|
@@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.crypto.verification
|
package im.vector.matrix.android.api.session.crypto.verification
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
data class EmojiRepresentation(val emoji: String,
|
data class EmojiRepresentation(val emoji: String,
|
||||||
@StringRes val nameResId: Int)
|
@StringRes val nameResId: Int,
|
||||||
|
@DrawableRes val drawableRes: Int? = null
|
||||||
|
)
|
||||||
|
@@ -13,10 +13,9 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.api.session.crypto.verification
|
||||||
|
|
||||||
import im.vector.matrix.android.api.extensions.orFalse
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||||
@@ -24,17 +23,16 @@ import java.util.UUID
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores current pending verification requests
|
* Stores current pending verification requests
|
||||||
* TODO We should not expose this whole object to the app. Create an interface
|
|
||||||
*/
|
*/
|
||||||
data class PendingVerificationRequest(
|
data class PendingVerificationRequest(
|
||||||
val ageLocalTs: Long,
|
val ageLocalTs: Long,
|
||||||
val isIncoming: Boolean = false,
|
val isIncoming: Boolean = false,
|
||||||
val localID: String = UUID.randomUUID().toString(),
|
val localId: String = UUID.randomUUID().toString(),
|
||||||
val otherUserId: String,
|
val otherUserId: String,
|
||||||
val roomId: String?,
|
val roomId: String?,
|
||||||
val transactionId: String? = null,
|
val transactionId: String? = null,
|
||||||
val requestInfo: VerificationInfoRequest? = null,
|
val requestInfo: ValidVerificationInfoRequest? = null,
|
||||||
val readyInfo: VerificationInfoReady? = null,
|
val readyInfo: ValidVerificationInfoReady? = null,
|
||||||
val cancelConclusion: CancelCode? = null,
|
val cancelConclusion: CancelCode? = null,
|
||||||
val isSuccessful: Boolean = false,
|
val isSuccessful: Boolean = false,
|
||||||
val handledByOtherSession: Boolean = false,
|
val handledByOtherSession: Boolean = false,
|
@@ -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.crypto.verification
|
||||||
|
|
||||||
|
data class ValidVerificationInfoReady(
|
||||||
|
val transactionId: String,
|
||||||
|
val fromDevice: String,
|
||||||
|
val methods: List<String>
|
||||||
|
)
|
@@ -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.api.session.crypto.verification
|
||||||
|
|
||||||
|
data class ValidVerificationInfoRequest(
|
||||||
|
val transactionId: String,
|
||||||
|
val fromDevice: String,
|
||||||
|
val methods: List<String>,
|
||||||
|
val timestamp: Long?
|
||||||
|
)
|
@@ -17,8 +17,8 @@
|
|||||||
package im.vector.matrix.android.api.session.crypto.verification
|
package im.vector.matrix.android.api.session.crypto.verification
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
||||||
@@ -60,6 +60,8 @@ interface VerificationService {
|
|||||||
roomId: String,
|
roomId: String,
|
||||||
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
||||||
|
|
||||||
|
fun cancelVerificationRequest(request: PendingVerificationRequest)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request a key verification from another user using toDevice events.
|
* Request a key verification from another user using toDevice events.
|
||||||
*/
|
*/
|
||||||
@@ -137,4 +139,6 @@ interface VerificationService {
|
|||||||
return age in tooInThePast..tooInTheFuture
|
return age in tooInThePast..tooInTheFuture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event)
|
||||||
}
|
}
|
||||||
|
@@ -43,6 +43,7 @@ sealed class VerificationTxState {
|
|||||||
|
|
||||||
// Will be used to ask the user if the other user has correctly scanned
|
// Will be used to ask the user if the other user has correctly scanned
|
||||||
object QrScannedByOther : VerificationQrTxState()
|
object QrScannedByOther : VerificationQrTxState()
|
||||||
|
object WaitingOtherReciprocateConfirm : VerificationQrTxState()
|
||||||
|
|
||||||
// Terminal states
|
// Terminal states
|
||||||
abstract class TerminalTxState : VerificationTxState()
|
abstract class TerminalTxState : VerificationTxState()
|
||||||
|
@@ -98,7 +98,7 @@ data class Event(
|
|||||||
* @return true if event is state event.
|
* @return true if event is state event.
|
||||||
*/
|
*/
|
||||||
fun isStateEvent(): Boolean {
|
fun isStateEvent(): Boolean {
|
||||||
return EventType.isStateEvent(getClearType())
|
return stateKey != null
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
@@ -142,12 +142,12 @@ data class Event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun toContentStringWithIndent(): String {
|
fun toContentStringWithIndent(): String {
|
||||||
val contentMap = toContent().toMutableMap()
|
val contentMap = toContent()
|
||||||
return JSONObject(contentMap).toString(4)
|
return JSONObject(contentMap).toString(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toClearContentStringWithIndent(): String? {
|
fun toClearContentStringWithIndent(): String? {
|
||||||
val contentMap = this.mxDecryptionResult?.payload?.toMutableMap()
|
val contentMap = this.mxDecryptionResult?.payload
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||||
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) }
|
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) }
|
||||||
}
|
}
|
||||||
@@ -162,6 +162,8 @@ data class Event(
|
|||||||
*/
|
*/
|
||||||
fun isRedactedBySameUser() = senderId == unsignedData?.redactedEvent?.senderId
|
fun isRedactedBySameUser() = senderId == unsignedData?.redactedEvent?.senderId
|
||||||
|
|
||||||
|
fun resolvedPrevContent(): Content? = prevContent ?: unsignedData?.prevContent
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
@@ -220,3 +222,11 @@ fun Event.isImageMessage(): Boolean {
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Event.isVideoMessage(): Boolean {
|
||||||
|
return getClearType() == EventType.MESSAGE
|
||||||
|
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||||
|
MessageType.MSGTYPE_VIDEO -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -38,6 +38,8 @@ object EventType {
|
|||||||
|
|
||||||
// State Events
|
// 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_NAME = "m.room.name"
|
||||||
const val STATE_ROOM_TOPIC = "m.room.topic"
|
const val STATE_ROOM_TOPIC = "m.room.topic"
|
||||||
const val STATE_ROOM_AVATAR = "m.room.avatar"
|
const val STATE_ROOM_AVATAR = "m.room.avatar"
|
||||||
@@ -56,7 +58,6 @@ object EventType {
|
|||||||
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
||||||
|
|
||||||
// Call Events
|
// Call Events
|
||||||
|
|
||||||
const val CALL_INVITE = "m.call.invite"
|
const val CALL_INVITE = "m.call.invite"
|
||||||
const val CALL_CANDIDATES = "m.call.candidates"
|
const val CALL_CANDIDATES = "m.call.candidates"
|
||||||
const val CALL_ANSWER = "m.call.answer"
|
const val CALL_ANSWER = "m.call.answer"
|
||||||
@@ -81,28 +82,8 @@ object EventType {
|
|||||||
// Relation Events
|
// Relation Events
|
||||||
const val REACTION = "m.reaction"
|
const val REACTION = "m.reaction"
|
||||||
|
|
||||||
private val STATE_EVENTS = listOf(
|
// Unwedging
|
||||||
STATE_ROOM_NAME,
|
internal const val DUMMY = "m.dummy"
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isCallEvent(type: String): Boolean {
|
fun isCallEvent(type: String): Boolean {
|
||||||
return type == CALL_INVITE
|
return type == CALL_INVITE
|
||||||
|
@@ -34,7 +34,11 @@ interface FileService {
|
|||||||
/**
|
/**
|
||||||
* Download file in cache
|
* Download file in cache
|
||||||
*/
|
*/
|
||||||
FOR_INTERNAL_USE
|
FOR_INTERNAL_USE,
|
||||||
|
/**
|
||||||
|
* Download file in file provider path
|
||||||
|
*/
|
||||||
|
FOR_EXTERNAL_SHARE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -17,10 +17,22 @@
|
|||||||
package im.vector.matrix.android.api.session.homeserver
|
package im.vector.matrix.android.api.session.homeserver
|
||||||
|
|
||||||
data class HomeServerCapabilities(
|
data class HomeServerCapabilities(
|
||||||
|
/**
|
||||||
|
* True if it is possible to change the password of the account.
|
||||||
|
*/
|
||||||
|
val canChangePassword: Boolean = true,
|
||||||
/**
|
/**
|
||||||
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
|
* 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 {
|
companion object {
|
||||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.api.auth.data
|
|
||||||
|
|
||||||
data class WellKnownManagerConfig(
|
package im.vector.matrix.android.api.session.identity
|
||||||
val apiUrl : String,
|
|
||||||
val uiUrl: String
|
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
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user