forked from GitHub-Mirror/riotX-android
Compare commits
933 Commits
feature/cs
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
f2c8d4ad02 | ||
|
be524472ec | ||
|
1b82a1a24d | ||
|
cf0b331c3b | ||
|
2a92a3dc80 | ||
|
012840abba | ||
|
a5975a099e | ||
|
38da4b9ee5 | ||
|
242e60fcaa | ||
|
a23be05cbf | ||
|
ed39b02924 | ||
|
fe931b5361 | ||
|
90d9cd0587 | ||
|
9cedb18921 | ||
|
e89ba7b87b | ||
|
902657c22a | ||
|
eec2abf164 | ||
|
6879cc8ca8 | ||
|
fd6bbbd3b5 | ||
|
0ff0b014a9 | ||
|
fdc9e84dd5 | ||
|
58f878fca9 | ||
|
88095e4bd9 | ||
|
47d22a3d5e | ||
|
28e82cb8ea | ||
|
35817245cb | ||
|
75266f42bb | ||
|
95c4c9ce56 | ||
|
ce5570105d | ||
|
188a9aebfa | ||
|
c95223f5d2 | ||
|
ef0362ba9c | ||
|
ea242f6737 | ||
|
cbc08d834b | ||
|
0ab6b33fb6 | ||
|
1b394527b6 | ||
|
a8f1388721 | ||
|
166be4e289 | ||
|
b49ccefe63 | ||
|
825760d17e | ||
|
b5af62c3ea | ||
|
a51d96bf00 | ||
|
7e142d201d | ||
|
2be6058971 | ||
|
49d73f360e | ||
|
bd88d85a21 | ||
|
be4fc5cce6 | ||
|
704da1be55 | ||
|
5d002532d3 | ||
|
d4161e9a1a | ||
|
7966ebef03 | ||
|
ed5faca5d2 | ||
|
8ca829d538 | ||
|
e7819ce678 | ||
|
5402902bc2 | ||
|
bc1350aaf5 | ||
|
fd74e3dfb1 | ||
|
e0628da1cb | ||
|
aa4e74e986 | ||
|
6cc0c0672e | ||
|
501474b720 | ||
|
e11c66035c | ||
|
3d2d219d79 | ||
|
63af03bedd | ||
|
d3827b8673 | ||
|
4ca2531e47 | ||
|
4e8dc72439 | ||
|
25a4240a5a | ||
|
b9cfda23b6 | ||
|
06dcf75a32 | ||
|
21deb2551d | ||
|
70639f180c | ||
|
1dbb02a80d | ||
|
825463d9cd | ||
|
c313ce78cb | ||
|
39f58d048b | ||
|
3f792c7a84 | ||
|
347dcb469a | ||
|
79fb1985aa | ||
|
e216cd15a8 | ||
|
37fde374b3 | ||
|
f7b471f141 | ||
|
93fd56a7ca | ||
|
5a9d88e791 | ||
|
eaf6a9923a | ||
|
d98567045c | ||
|
b4ce8748cb | ||
|
9d5433a857 | ||
|
6d4ee83e65 | ||
|
6e44cca17d | ||
|
0a73887c70 | ||
|
7fef063e15 | ||
|
24f391dac0 | ||
|
81c7f694d6 | ||
|
80e2fc0ca3 | ||
|
9f53406e99 | ||
|
3584658c36 | ||
|
12a0cbb400 | ||
|
20437446b4 | ||
|
35229882e3 | ||
|
a04f4421f6 | ||
|
af1e81f65e | ||
|
63f6081fa5 | ||
|
ee2e575211 | ||
|
0949d29f9c | ||
|
23466fb5a4 | ||
|
7f09e64d63 | ||
|
585f0ba4b7 | ||
|
245fbe86d9 | ||
|
456908c851 | ||
|
b79fdf6a85 | ||
|
d9f448c9aa | ||
|
7a6fc4936b | ||
|
d82fd10f3b | ||
|
4009f2c176 | ||
|
15c4b03340 | ||
|
7b5dff3dcf | ||
|
357123743f | ||
|
bb04af1e2c | ||
|
2f94fbd7eb | ||
|
f2a3bdb68e | ||
|
097e9714ff | ||
|
acae0fad3e | ||
|
4deb7eb865 | ||
|
f910cd6f97 | ||
|
652ac81fa1 | ||
|
99f4196388 | ||
|
c0b94f4111 | ||
|
1462fa0484 | ||
|
dafdc1d3ad | ||
|
394b89e76b | ||
|
0db8e7da43 | ||
|
dae8b5c196 | ||
|
215324a03e | ||
|
d3ce4c491c | ||
|
ed6d28bd3b | ||
|
c2e053b62b | ||
|
c450849cc3 | ||
|
fe884dba2d | ||
|
3fa4dbaa25 | ||
|
4a74f58516 | ||
|
c413321a22 | ||
|
d696bd2830 | ||
|
a2b6bd0f62 | ||
|
9cc922a8a2 | ||
|
c36d1bcd06 | ||
|
85499c6b33 | ||
|
8076eab4b5 | ||
|
d47c0f5ebc | ||
|
fd09a1224e | ||
|
c300c50093 | ||
|
77c4355aed | ||
|
1a92562182 | ||
|
9c390dcc0c | ||
|
95089b91b8 | ||
|
eb446d7b49 | ||
|
dc4786ecf0 | ||
|
e245023add | ||
|
90fad23493 | ||
|
000db4b192 | ||
|
2a16c36a59 | ||
|
ef6c1cfc63 | ||
|
4b4156996d | ||
|
087cc0e6e3 | ||
|
f4df27c2dc | ||
|
ab25980c4e | ||
|
77b402ce70 | ||
|
2763fbb496 | ||
|
6deba31111 | ||
|
ff6ce8a4b7 | ||
|
d0cff219aa | ||
|
65f0af918f | ||
|
ac38a6461c | ||
|
9a1e16a170 | ||
|
9e5c70dda3 | ||
|
0255696c88 | ||
|
76a9625f25 | ||
|
5af6bf3762 | ||
|
507bc2f622 | ||
|
125eacb20b | ||
|
6176520805 | ||
|
3aea0a50ca | ||
|
ab87a3caea | ||
|
c58328f94e | ||
|
03974c8bdf | ||
|
151ae7f4dd | ||
|
a34b053efe | ||
|
02e342849f | ||
|
b59017938b | ||
|
2c81e41288 | ||
|
cb44ab547c | ||
|
6d01a570fd | ||
|
4a2bf0d6c6 | ||
|
36af8a6a9f | ||
|
40a68c3e9f | ||
|
1a4ec34bb2 | ||
|
10490e3aa6 | ||
|
cd6624a8a6 | ||
|
3965218bf9 | ||
|
d78ff7ab08 | ||
|
cb274d6a33 | ||
|
c00dbce536 | ||
|
db88caf7fa | ||
|
c3d945d6bb | ||
|
4c128602b2 | ||
|
d609c49b31 | ||
|
001603cf9a | ||
|
d87ee32422 | ||
|
f0671b9e73 | ||
|
e218691bf2 | ||
|
9c67036c08 | ||
|
62657538af | ||
|
5438207fba | ||
|
fe88aaffbd | ||
|
21ba72e5e7 | ||
|
d48ae967bd | ||
|
0afde3b021 | ||
|
49ae954183 | ||
|
64bee91f7a | ||
|
4341b0d0f5 | ||
|
51fdccb393 | ||
|
7e3b300130 | ||
|
a98b324c89 | ||
|
977721881f | ||
|
838003b68a | ||
|
7d41352918 | ||
|
077396a832 | ||
|
32b79bd50e | ||
|
844f6d16a4 | ||
|
fc9ef579ca | ||
|
77fa5af1b8 | ||
|
2948018453 | ||
|
90d25ff45e | ||
|
173452d38c | ||
|
a9f9083745 | ||
|
22dc2a6790 | ||
|
927cd7285d | ||
|
4d5bdecec6 | ||
|
0be987ac0d | ||
|
4bfaa00be4 | ||
|
8e78d8a58d | ||
|
e3e86c0a41 | ||
|
8a5fddd952 | ||
|
208460850e | ||
|
0ddef67cc9 | ||
|
896e582a9c | ||
|
477920f411 | ||
|
c647648e79 | ||
|
b654025a3b | ||
|
786a7d7560 | ||
|
b935b9311e | ||
|
8e12f71535 | ||
|
7eea2ccfb4 | ||
|
c32ef02a12 | ||
|
3651ec4870 | ||
|
87de7bd3e6 | ||
|
9494174c33 | ||
|
b7e0b400fb | ||
|
a8f06f609b | ||
|
d469299f42 | ||
|
9bdea5b325 | ||
|
2f01ad99b3 | ||
|
bb3b5788ba | ||
|
45f7d3e9c4 | ||
|
0f7a56d005 | ||
|
63d2861bc8 | ||
|
6bbc784c29 | ||
|
c6fd625761 | ||
|
d8092abc4e | ||
|
6effb90361 | ||
|
42584fc55a | ||
|
30d9ddb3e8 | ||
|
020c32bb1a | ||
|
efd973208f | ||
|
30a6c98c08 | ||
|
1440080d04 | ||
|
61bb4c0427 | ||
|
3c25088243 | ||
|
fc1c0caea3 | ||
|
8901a5e09a | ||
|
25f1d21bc7 | ||
|
4d2ab9fa31 | ||
|
0289d2ee87 | ||
|
222201cc64 | ||
|
b15dea6de3 | ||
|
2ba83e456d | ||
|
1822fc4fbb | ||
|
e6dd1fbfec | ||
|
e2ea76f871 | ||
|
9182f2ce4e | ||
|
34d14eb304 | ||
|
3625c462f0 | ||
|
fe69206340 | ||
|
f9885fd04c | ||
|
316c8ec27e | ||
|
41465450d8 | ||
|
bd009caaf1 | ||
|
33252c3b65 | ||
|
10e4d0190f | ||
|
b77310fe92 | ||
|
919dec4a56 | ||
|
43b3680774 | ||
|
bfb5fce809 | ||
|
1f3731aae7 | ||
|
52dced43ff | ||
|
ff80c3c8d5 | ||
|
34e4d27573 | ||
|
6522148e63 | ||
|
252b2ea30a | ||
|
f493ce44f2 | ||
|
c4c5069ee5 | ||
|
423125b5d9 | ||
|
9e3d29b7d7 | ||
|
f65becf7c0 | ||
|
80a61cf6b5 | ||
|
77056aff94 | ||
|
65e123d87f | ||
|
d0b145d031 | ||
|
98306e223b | ||
|
c9fe1adb77 | ||
|
1b95336ad3 | ||
|
f007fb04b8 | ||
|
141434e8f8 | ||
|
b8669d5ed2 | ||
|
7a08a11b19 | ||
|
54b1d18812 | ||
|
3aa30e5f15 | ||
|
ddf4a81905 | ||
|
794fd650a4 | ||
|
9a57a02996 | ||
|
7e8cd07e1e | ||
|
d613abf4b4 | ||
|
06699eaefc | ||
|
e5082f662c | ||
|
c8ab53e39c | ||
|
d424a135a9 | ||
|
e6409d4c60 | ||
|
19c7de687e | ||
|
1918302297 | ||
|
92e3a02389 | ||
|
0a54801fcc | ||
|
228ee52563 | ||
|
e6c74dc1fe | ||
|
fe82ad2002 | ||
|
f66739491a | ||
|
c5dc9d4a9a | ||
|
8f858f8119 | ||
|
6e036c24b8 | ||
|
5e832e07cd | ||
|
e9700e04d8 | ||
|
c19b1f917f | ||
|
4281b5967a | ||
|
aa743d8469 | ||
|
a09850b16c | ||
|
6cb94dd4d6 | ||
|
c9931e3ba3 | ||
|
fc302c1b5a | ||
|
34ac987494 | ||
|
24b2387703 | ||
|
8a0c9ae9b0 | ||
|
a79227424f | ||
|
ffe0b9712c | ||
|
d92c090c30 | ||
|
1a4157a663 | ||
|
fa81d1a9c7 | ||
|
dba4df6836 | ||
|
4aae1f78d8 | ||
|
8159a52bd7 | ||
|
95d83db90c | ||
|
ac5b0af63e | ||
|
e80473903e | ||
|
d08778c674 | ||
|
0919b9460d | ||
|
66a018c79e | ||
|
dcd64de4b8 | ||
|
a0bd206308 | ||
|
ba589e7961 | ||
|
5dc83d64c1 | ||
|
9a4eb8e9a4 | ||
|
058e7153a1 | ||
|
d7b2371854 | ||
|
b0c939866f | ||
|
a07f8b615e | ||
|
12bd85e0a9 | ||
|
1b82ed5abb | ||
|
c13ab62187 | ||
|
ea77686746 | ||
|
8a5612be3d | ||
|
d24ce27903 | ||
|
2099965508 | ||
|
829e8da8dc | ||
|
e149ee53de | ||
|
b73d3b15f8 | ||
|
61d7f23870 | ||
|
b5650b2b8f | ||
|
8777d13d8b | ||
|
d52613d723 | ||
|
7ce476f858 | ||
|
dd07f5c2a6 | ||
|
7e6e09bc19 | ||
|
1d11a163af | ||
|
57bd103de8 | ||
|
25bc5001f9 | ||
|
e4c52484b1 | ||
|
a30da07fd1 | ||
|
ee27d3e047 | ||
|
7096094224 | ||
|
443fb41d18 | ||
|
94b4351e19 | ||
|
e90aeff417 | ||
|
e50dd265d4 | ||
|
4521ea14ee | ||
|
535b41d818 | ||
|
21357a1ec7 | ||
|
8c872caf78 | ||
|
62a81a556e | ||
|
568e8c8bc0 | ||
|
98a7652403 | ||
|
78951b9155 | ||
|
8c86a653b2 | ||
|
ea0526821e | ||
|
c503445092 | ||
|
205af8b122 | ||
|
3abb7c8de6 | ||
|
a40510da3b | ||
|
a6ab4a349d | ||
|
79a704d240 | ||
|
e5adf174a8 | ||
|
f01e796271 | ||
|
302d23ba96 | ||
|
03050c3f25 | ||
|
cbfd2af74b | ||
|
f3fab0dc08 | ||
|
4a512d2425 | ||
|
07f80f43bd | ||
|
87dec337d8 | ||
|
b37877746a | ||
|
b0e5612bdc | ||
|
25b0cd0e4b | ||
|
2800d86a57 | ||
|
01bc0de2c2 | ||
|
857a4c5a26 | ||
|
063c35380a | ||
|
5322251bc0 | ||
|
c21b9df9a5 | ||
|
f2a52f0253 | ||
|
baaf493cb4 | ||
|
6cbd6d3a33 | ||
|
72e5aa981a | ||
|
c0f085cdf8 | ||
|
10bc2297d4 | ||
|
8fa5e63b07 | ||
|
9d0c50907c | ||
|
e5958983d8 | ||
|
ab23ec3f35 | ||
|
a79a6443e7 | ||
|
9ff24cbf2a | ||
|
2eee25bbc1 | ||
|
2a2431e490 | ||
|
4041e2e8ca | ||
|
031c4e5746 | ||
|
b4ea85fc76 | ||
|
480f14902d | ||
|
20c8e8d922 | ||
|
9cdecced57 | ||
|
60d46538de | ||
|
223295c2f1 | ||
|
f789fb275d | ||
|
a7c12aeb93 | ||
|
0ca9a5f68b | ||
|
842345df9b | ||
|
7d5c31c510 | ||
|
1ee1c31b9c | ||
|
e9eada77f9 | ||
|
93ce0cc5e9 | ||
|
eefd09d022 | ||
|
ef597cc67a | ||
|
5d171e0240 | ||
|
39070820be | ||
|
1fdad38b9d | ||
|
f41c0311fa | ||
|
a476ac71da | ||
|
4b971a9e67 | ||
|
bc2d321a84 | ||
|
9adeab6bae | ||
|
0f3a63e366 | ||
|
d90698fe92 | ||
|
af0af6e260 | ||
|
6e71fb565a | ||
|
6c66ab1568 | ||
|
77fd7cd33c | ||
|
0d329f0338 | ||
|
2f66321c2a | ||
|
5b102485bc | ||
|
313055b96d | ||
|
cbe8236036 | ||
|
698fc35704 | ||
|
37199da52f | ||
|
1c69d8e425 | ||
|
ec5ec3375a | ||
|
156d88e7e2 | ||
|
504009499f | ||
|
11bf00030d | ||
|
73277c5b08 | ||
|
f21f4dbe91 | ||
|
504d7e95fd | ||
|
62d2443b85 | ||
|
bc1edcf33d | ||
|
363f52b10c | ||
|
498b1f2b06 | ||
|
92222c269e | ||
|
ee9440c1cb | ||
|
fe81145680 | ||
|
0980a41752 | ||
|
18a821f3f6 | ||
|
14a2570ea4 | ||
|
994ee1d23f | ||
|
f0e43d31f5 | ||
|
85e850bcde | ||
|
6cf24cc43b | ||
|
c7df433a44 | ||
|
9378d30601 | ||
|
58a80ec543 | ||
|
95ea6db946 | ||
|
a47af4d915 | ||
|
0c2d3f36c3 | ||
|
014d03893a | ||
|
164c8dab09 | ||
|
b54ca5a8a0 | ||
|
707a4712fc | ||
|
8e76700c8d | ||
|
3f74c4e933 | ||
|
41ed4b23d8 | ||
|
ce9fa15bcb | ||
|
f9880283e9 | ||
|
f4b124d29f | ||
|
fb1f107911 | ||
|
8e653979fd | ||
|
a5584d27af | ||
|
de9a5a3d12 | ||
|
95d64008aa | ||
|
19202cfca6 | ||
|
ca2223f201 | ||
|
a1ddd73d7d | ||
|
01e3e71f98 | ||
|
8fefdc1019 | ||
|
07309c90e1 | ||
|
0bdde4d994 | ||
|
b25098c52d | ||
|
5e9ecfbcc0 | ||
|
e1b6f4bd74 | ||
|
4d79485fee | ||
|
9224fcabfa | ||
|
0d433b2620 | ||
|
6a829caf0f | ||
|
d643abbb22 | ||
|
e838794587 | ||
|
b0ad568df0 | ||
|
4739aea793 | ||
|
3960742f38 | ||
|
2eef0a6162 | ||
|
466a39d239 | ||
|
e78b703387 | ||
|
9df928e709 | ||
|
a734c699ad | ||
|
b402bf829b | ||
|
4d7f1b4fee | ||
|
38ceb6f52a | ||
|
419ef7b46f | ||
|
4ad23f0f37 | ||
|
0f039fce32 | ||
|
fef1c7cc45 | ||
|
7ff2477a4f | ||
|
7af55a48f6 | ||
|
ee402fd328 | ||
|
75c1718252 | ||
|
f83491fdfc | ||
|
a9dd06562a | ||
|
cea8abb9b1 | ||
|
13a0b809e1 | ||
|
d28e9862a2 | ||
|
bf68a6bafc | ||
|
b14a6224ba | ||
|
b92cc524b6 | ||
|
17a4a86ad1 | ||
|
0a908136b6 | ||
|
83ceb36a12 | ||
|
2ef53e2066 | ||
|
519f49b50d | ||
|
a550743f2f | ||
|
40bf3a15cd | ||
|
4422ebb77b | ||
|
99271ce5d6 | ||
|
2c9280dca6 | ||
|
7187cc23a1 | ||
|
10353c9871 | ||
|
f410538e2f | ||
|
76fc455d93 | ||
|
22c005d05a | ||
|
604de7eebc | ||
|
f18bc9bd00 | ||
|
f91959ea96 | ||
|
2063a3e535 | ||
|
a1c22c9aa6 | ||
|
d5625b95fe | ||
|
d205f63928 | ||
|
bd8d6f92da | ||
|
f9c8e4f85a | ||
|
1fa7b7367a | ||
|
0765d6d1da | ||
|
f98f0e1a87 | ||
|
fb7ada72dd | ||
|
338de3ebf5 | ||
|
fb43c87107 | ||
|
f092c40999 | ||
|
f414f46cba | ||
|
9ec364b60e | ||
|
ae7e617fdd | ||
|
d1642c928a | ||
|
5e619f2593 | ||
|
679a4c7f31 | ||
|
76b890fe06 | ||
|
b2d2582e0f | ||
|
715b44ec79 | ||
|
25b7bf76bf | ||
|
b69940a5da | ||
|
28f2bb3ebd | ||
|
6bf940bedf | ||
|
56fc223930 | ||
|
3e00576230 | ||
|
6e7adaec59 | ||
|
9fd9124643 | ||
|
ad3d303405 | ||
|
7c47c6a033 | ||
|
33f17e4c5c | ||
|
289b2a4eb1 | ||
|
e63f51821f | ||
|
8370f4fc76 | ||
|
5feebeba01 | ||
|
0c0ef38b4f | ||
|
bec5ca1420 | ||
|
895f0f0079 | ||
|
134c2fcd42 | ||
|
2625e11508 | ||
|
9fa3a75fb6 | ||
|
b388be93c8 | ||
|
5a1242109d | ||
|
4f0ed402bf | ||
|
f6c500d120 | ||
|
785f33177d | ||
|
328f090723 | ||
|
ce3242c748 | ||
|
71ae99012b | ||
|
ab0141a5c6 | ||
|
abb1c3f3c4 | ||
|
1feb1f9c3f | ||
|
090ee1d4e9 | ||
|
7821ca12fd | ||
|
c01af6ac78 | ||
|
74099be316 | ||
|
4e6b34b9d1 | ||
|
6743dc6273 | ||
|
288ebe48fd | ||
|
9ae9830de4 | ||
|
0584fc3666 | ||
|
2e417a9143 | ||
|
0e46fc4c0a | ||
|
79735c6338 | ||
|
4505d13385 | ||
|
8fe0bd5abe | ||
|
98176b9760 | ||
|
104ffc930d | ||
|
2741780553 | ||
|
625242a3d9 | ||
|
401f878a9c | ||
|
3e97503220 | ||
|
76ade2957e | ||
|
b1e009f8b4 | ||
|
90f420b287 | ||
|
73b55fd975 | ||
|
ad601c7d5a | ||
|
046aac74c2 | ||
|
0998ffb5f2 | ||
|
8ff6fbb153 | ||
|
9d3a8e7c40 | ||
|
56aaa9dce3 | ||
|
43ead66991 | ||
|
92eb7d55dc | ||
|
1cfc85a772 | ||
|
47968c9447 | ||
|
07fee8ed3d | ||
|
1eb374fa49 | ||
|
285da114e7 | ||
|
34870591b4 | ||
|
ee87c253fe | ||
|
9c1f870694 | ||
|
56e0680398 | ||
|
8c0a1ed37d | ||
|
625500212d | ||
|
b1f5b3ad96 | ||
|
02f84a3b53 | ||
|
7fe662598b | ||
|
5bfa67b442 | ||
|
a53e40e1ee | ||
|
273c8a19b8 | ||
|
53bdd58c1b | ||
|
51879845f2 | ||
|
f2372841f6 | ||
|
0497d14a08 | ||
|
0b6b95110f | ||
|
191d80e5f5 | ||
|
659ba34fb3 | ||
|
907a1d1a4b | ||
|
99d2e8388a | ||
|
38b1d24953 | ||
|
b682f3e982 | ||
|
5f0d1d9536 | ||
|
c2c2d0b21e | ||
|
8c8a4dcbd1 | ||
|
7e9275831b | ||
|
6266f9e6a1 | ||
|
9649e190ef | ||
|
1547045165 | ||
|
5b0cab3e8a | ||
|
97b066b8fa | ||
|
4be0ab87fc | ||
|
3d465f6fdf | ||
|
e3bc88e36c | ||
|
eaf1e080ba | ||
|
02ef1172ce | ||
|
8f6f72ca48 | ||
|
df4f0eac20 | ||
|
d353e9314b | ||
|
567c1fd7a5 | ||
|
ab95cbee92 | ||
|
9dc1684179 | ||
|
c20b256b24 | ||
|
02d3fea4a9 | ||
|
ca98ff5864 | ||
|
6cd3b4dd95 | ||
|
480d197ffa | ||
|
a7c0e87f40 | ||
|
af1a48d918 | ||
|
e17ffc85e7 | ||
|
9c654ba72c | ||
|
b47ef9220e | ||
|
0204bade8b | ||
|
481a25d4df | ||
|
3dd161d65a | ||
|
53dd9c3427 | ||
|
4827b76b80 | ||
|
1206107a73 | ||
|
5621f0661e | ||
|
875947dd61 | ||
|
61b1d83bbd | ||
|
64e5fed7ac | ||
|
f6c36670c3 | ||
|
2e39a678db | ||
|
6dacb9894e | ||
|
612b13808f | ||
|
90a011c4e4 | ||
|
56f1c726b2 | ||
|
6323183119 | ||
|
3439a9ca27 | ||
|
9772bbe157 | ||
|
fa297a7b6a | ||
|
b48c920292 | ||
|
c0be04f46c | ||
|
b437837809 | ||
|
33f8059846 | ||
|
4eab0a3704 | ||
|
933e06a7ef | ||
|
ad8baf8091 | ||
|
598245531a | ||
|
4f044c0cd6 | ||
|
77fc793e89 | ||
|
aa95ce3d02 | ||
|
4c5bffe0f5 | ||
|
ed18a504e4 | ||
|
aec7e72dcf | ||
|
f2722f4766 | ||
|
07c516ccdd | ||
|
81330d30cf | ||
|
8f2c005d82 | ||
|
a4a813708c | ||
|
664e5354d3 | ||
|
10251b906a | ||
|
c9240c2dce | ||
|
220e6224e7 | ||
|
d3518c4944 | ||
|
5f34e58bd3 | ||
|
438404b5ba | ||
|
651d0472cd | ||
|
5cf9deb329 | ||
|
7409003949 | ||
|
3f1bf00fdd | ||
|
04576ba7fd | ||
|
053dc1d8dd | ||
|
834a865dfa | ||
|
e22b555b58 | ||
|
297f202005 | ||
|
440442bb99 | ||
|
d2f648edec | ||
|
53c91dc0c2 | ||
|
adbfde94d6 | ||
|
1b3ec2d0fb | ||
|
ecccb80e04 | ||
|
11914ca188 | ||
|
fb9627b7c4 | ||
|
b782e5e8af | ||
|
b67c686d67 | ||
|
c4d7711d2f | ||
|
f2da46b5f9 | ||
|
a0b2d4c8f2 | ||
|
1f85f4a007 | ||
|
2c2f517e52 | ||
|
bfbb29b2cf | ||
|
dd563ec9ae | ||
|
e2d36aa213 | ||
|
2cc2844abf | ||
|
75b8932395 | ||
|
311d8484a2 | ||
|
917282303d | ||
|
8afe31192b | ||
|
bbbf64f543 | ||
|
6b0ab10231 | ||
|
acedff4e89 | ||
|
f9bfda059f | ||
|
94c91e0dae | ||
|
48fadd1a11 | ||
|
51f5594ea0 | ||
|
91114e2afe | ||
|
e058fa9069 | ||
|
2ba7ec48f6 | ||
|
e125862794 | ||
|
ab6220a4cb | ||
|
647a066c90 | ||
|
2b6eee4237 | ||
|
7eb1be4633 | ||
|
0ecc53f59c | ||
|
e3983deacc | ||
|
9357059cbc | ||
|
9061d5c972 | ||
|
471170a3e0 | ||
|
43521c6e09 | ||
|
fc5edcdf0f | ||
|
3d50393b33 | ||
|
424fd1347d | ||
|
3475b169ea | ||
|
c1fa728c24 | ||
|
3b12f5eec7 | ||
|
12b03a844d | ||
|
4716ceb950 | ||
|
c91a409258 | ||
|
784d55c16c | ||
|
99925d7cf9 | ||
|
ae38917a33 | ||
|
9da727b623 | ||
|
241ee1cb9d | ||
|
e959fe2e9d | ||
|
6978ec4246 | ||
|
2b8bbc550c | ||
|
25f6528049 | ||
|
3289cbd6e7 | ||
|
b91e7e9fb8 | ||
|
e70a483d6b | ||
|
6244913ab9 | ||
|
33fbcc8ba3 | ||
|
3f7d20ec5b | ||
|
466be1dca5 | ||
|
bdbd521257 | ||
|
deba756598 | ||
|
3cb99ff64f | ||
|
20e903914c | ||
|
71ea1c5f9b | ||
|
4a4c0a3da1 | ||
|
8f2754493c | ||
|
dde94c0d0f | ||
|
00d66ffd48 | ||
|
c7c7211978 | ||
|
02a81dd9e1 | ||
|
02555fcbac | ||
|
b9d76f5047 | ||
|
9f9f4c0755 | ||
|
1691537a1e | ||
|
eb2344a43f | ||
|
c0fd06fd2d | ||
|
275521db70 | ||
|
268730e71b | ||
|
0feb10315b | ||
|
39f69a6c3b | ||
|
d9fecabc1f | ||
|
14611d1f7b | ||
|
fe6e27fd6a | ||
|
0e06908a48 | ||
|
390c6a1977 | ||
|
af338b0607 | ||
|
b45cc0e63f | ||
|
128dea2677 | ||
|
cd5e808bb6 | ||
|
bbf2f96288 | ||
|
2404eeadf0 | ||
|
877de1f597 | ||
|
3519ad7c8d | ||
|
3c16701766 | ||
|
1da0b5be76 | ||
|
c6e428c047 | ||
|
45ea5c356e | ||
|
5da29e8063 | ||
|
99087019d2 | ||
|
71f8ce001d | ||
|
bb39db3f42 | ||
|
b0e80e49b3 | ||
|
ec53ce9d00 | ||
|
52d9adad70 | ||
|
118a4392a2 | ||
|
b8c3bdbbf6 | ||
|
d49007538b | ||
|
6f103101b6 | ||
|
a5a9fa3750 | ||
|
efcac6b3e4 | ||
|
8cb884f10e | ||
|
6d8000b957 | ||
|
f5bd215f36 | ||
|
532a028e41 | ||
|
e0e41d9e5c | ||
|
340830d45f | ||
|
de4662b9d5 | ||
|
c66e82c4ae | ||
|
a2210a6b0d | ||
|
102bc9c01b | ||
|
e70fd8e351 | ||
|
1436667e7d |
@ -5,18 +5,30 @@
|
||||
# Build debug version of the RiotX application, from the develop branch and the features branches
|
||||
|
||||
steps:
|
||||
- label: "Assemble Debug version"
|
||||
- label: "Assemble GPlay Debug version"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
||||
- "./gradlew lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/gplay/debug/*.apk"
|
||||
branches: "!master"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
||||
- label: "Assemble FDroid Debug version"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/fdroid/debug/*.apk"
|
||||
branches: "develop feature/*"
|
||||
branches: "!master"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
94
CHANGES.md
94
CHANGES.md
@ -1,24 +1,106 @@
|
||||
Changes in RiotX 0.XX (2019-XX-XX)
|
||||
Changes in RiotX 0.5.0 (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Contextual action menu for messages in room
|
||||
-
|
||||
|
||||
Improvements:
|
||||
-
|
||||
- Reduce default release build log level, and lab option to enable more logs.
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix:
|
||||
-
|
||||
- Fix crash due to missing informationData (#535)
|
||||
- Progress in initial sync dialog is decreasing for a step and should not (#532)
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
-
|
||||
- Fix issue with version name (#533)
|
||||
- Fix rendering issue of accepted third party invitation event
|
||||
|
||||
Changes in RiotX 0.4.0 (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Display read receipts in timeline (#81)
|
||||
|
||||
Improvements:
|
||||
- Reactions: Reinstate the ability to react with non-unicode keys (#307)
|
||||
|
||||
Bugfix:
|
||||
- Fix text diff linebreak display (#441)
|
||||
- Date change message repeats for each redaction until a normal message (#358)
|
||||
- Slide-in reply icon is distorted (#423)
|
||||
- Regression / e2e replies not encrypted
|
||||
- Some video won't play
|
||||
- Privacy: remove log of notifiable event (#519)
|
||||
- Fix crash with EmojiCompat (#530)
|
||||
|
||||
Changes in RiotX 0.3.0 (2019-08-08)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Create Direct Room flow
|
||||
- Handle `/markdown` command
|
||||
|
||||
Improvements:
|
||||
- UI for pending edits (#193)
|
||||
- UX image preview screen transition (#393)
|
||||
- Basic support for resending failed messages (retry/remove)
|
||||
- Enable proper cancellation of suspending functions (including db transaction)
|
||||
- Enhances network connectivity checks in SDK
|
||||
- Add "View Edit History" item in the message bottom sheet (#401)
|
||||
- Cancel sync request on pause and timeout to 0 after pause (#404)
|
||||
|
||||
Other changes:
|
||||
- Show sync progress also in room detail screen (#403)
|
||||
|
||||
Bugfix:
|
||||
- Edited message: link confusion when (edited) appears in body (#398)
|
||||
- Close detail room screen when the room is left with another client (#256)
|
||||
- Clear notification for a room left on another client
|
||||
- Fix messages with empty `in_reply_to` not rendering (#447)
|
||||
- Fix clear cache (#408) and Logout (#205)
|
||||
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||
|
||||
Build:
|
||||
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
||||
|
||||
|
||||
Changes in RiotX 0.2.0 (2019-07-18)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Message Editing: View edit history (#121)
|
||||
- Rooms filtering (#304)
|
||||
- Edit in encrypted room
|
||||
|
||||
Improvements:
|
||||
- Handle click on redacted events: view source and create permalink
|
||||
- Improve long tap menu: reply on top, more compact (#368)
|
||||
- Quick reply in timeline with swipe gesture (#167)
|
||||
- Improve edit of replies
|
||||
- Improve performance on Room Members and Users management (#381)
|
||||
|
||||
Other changes:
|
||||
- migrate from rxbinding 2 to rxbinding 3
|
||||
|
||||
Bugfix:
|
||||
- Fix regression on permalink click
|
||||
- Fix crash reported by the PlayStore (#341)
|
||||
- Fix Chat composer separator color in dark/black theme
|
||||
- Fix bad layout for room directory filter (#349)
|
||||
- Fix Copying link from a message shouldn't open context menu (#364)
|
||||
|
||||
Changes in RiotX 0.1.0 (2019-07-11)
|
||||
===================================================
|
||||
|
||||
First release!
|
||||
|
||||
Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-android-b17952e8f771
|
||||
|
||||
|
||||
=======================================================
|
||||
@ -26,7 +108,7 @@ Build:
|
||||
=======================================================
|
||||
|
||||
|
||||
Changes in RiotX 0.XX (2019-XX-XX)
|
||||
Changes in RiotX 0.0.0 (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
|
29
README.md
29
README.md
@ -1,4 +1,4 @@
|
||||
[![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android)
|
||||
[![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
[![Weblate](https://translate.riot.im/widgets/riot-android/-/svg-badge.svg)](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
||||
[![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org)
|
||||
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=alert_status)](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
@ -7,14 +7,31 @@
|
||||
|
||||
# RiotX Android
|
||||
|
||||
RiotX is an Android Matrix Client currently in development. The application is not yet available on the PlayStore.
|
||||
RiotX is an Android Matrix Client currently in beta but in active development.
|
||||
|
||||
It's based on a new Matrix SDK, written in Kotlin.
|
||||
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.
|
||||
|
||||
Download nightly build here: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
[<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)
|
||||
|
||||
Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
|
||||
# New Android SDK
|
||||
|
||||
RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the API is stable enough.
|
||||
|
||||
|
||||
# Roadmap
|
||||
|
||||
The current target is to release an application out of beta with the same level of features (and even more) as Riot.
|
||||
The roadmap has 3 phases:
|
||||
|
||||
- [phase 0](https://github.com/vector-im/riotX-android/labels/phase0): Prototyping / Project setup
|
||||
- [phase 1](https://github.com/vector-im/riotX-android/labels/phase1): Beta release to the Play Store
|
||||
- [phase 2](https://github.com/vector-im/riotX-android/labels/phase2): Out of beta
|
||||
|
||||
Matrix Room: [![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute the Matrix on Android projects!
|
||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
|
||||
|
||||
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#riotx:matrix.org).
|
||||
|
69
build.gradle
69
build.gradle
@ -1,8 +1,9 @@
|
||||
import javax.tools.JavaCompiler
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.21'
|
||||
ext.koin_version = '1.0.2'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
@ -11,11 +12,12 @@ buildscript {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'com.android.tools.build:gradle:3.4.1'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath "com.airbnb.okreplay:gradle-plugin:1.4.0"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
@ -24,12 +26,47 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url "http://dl.bintray.com/piasy/maven" }
|
||||
maven { url 'https://jitpack.io' }
|
||||
// For olm library. This has to be declared first, to ensure that Olm library is not downloaded from another repo
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
content {
|
||||
// Use this repo only for olm library
|
||||
includeGroupByRegex "org\\.matrix\\.gitlab\\.matrix-org"
|
||||
// And also for FilePicker
|
||||
includeGroupByRegex "com\\.github\\.jaiselrahman"
|
||||
// And monarchy
|
||||
includeGroupByRegex "com\\.github\\.Zhuinden"
|
||||
}
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/piasy/maven"
|
||||
content {
|
||||
includeGroupByRegex "com\\.github\\.piasy"
|
||||
}
|
||||
}
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://repo.adobe.com/nexus/content/repositories/public/'
|
||||
content {
|
||||
includeGroupByRegex "diff_match_patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).all {
|
||||
options.compilerArgs += [
|
||||
'-Adagger.gradle.incremental=enabled'
|
||||
]
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
extensions.findByName("kapt")?.arguments {
|
||||
arg("dagger.gradle.incremental", "enabled")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
@ -38,6 +75,10 @@ task clean(type: Delete) {
|
||||
|
||||
apply plugin: 'org.sonarqube'
|
||||
|
||||
// To run a sonar analysis:
|
||||
// Run './gradlew sonarqube -Dsonar.login=<REPLACE_WITH_SONAR_KEY>'
|
||||
// The SONAR_KEY is stored in passbolt
|
||||
|
||||
sonarqube {
|
||||
properties {
|
||||
property "sonar.projectName", "RiotX-Android"
|
||||
@ -63,3 +104,23 @@ project(":vector") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//project(":matrix-sdk-android") {
|
||||
// sonarqube {
|
||||
// properties {
|
||||
// property "sonar.sources", project(":matrix-sdk-android").android.sourceSets.main.java.srcDirs
|
||||
// // exclude source code from analyses separated by a colon (:)
|
||||
// // property "sonar.exclusions", "**/*.*"
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//project(":matrix-sdk-android-rx") {
|
||||
// sonarqube {
|
||||
// properties {
|
||||
// property "sonar.sources", project(":matrix-sdk-android-rx").android.sourceSets.main.java.srcDirs
|
||||
// // exclude source code from analyses separated by a colon (:)
|
||||
// // property "sonar.exclusions", "**/*.*"
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
281
docs/notifications.md
Normal file
281
docs/notifications.md
Normal file
@ -0,0 +1,281 @@
|
||||
This document aims to describe how RiotX android displays notifications to the end user. It also clarifies notifications and background settings in the app.
|
||||
|
||||
# Table of Contents
|
||||
1. [Prerequisites Knowledge](#prerequisites-knowledge)
|
||||
* [How does a matrix client gets a message from a Home Server?](#how-does-a-matrix-client-gets-a-message-from-a-home-server)
|
||||
* [How does a mobile app receives push notification?](#how-does-a-mobile-app-receives-push-notification)
|
||||
* [Push VS Notification](#push-vs-notification)
|
||||
* [Push in the matrix federated world](#push-in-the-matrix-federated-world)
|
||||
* [How does the Home Server knows when to notify a client?](#how-does-the-home-server-knows-when-to-notify-a-client)
|
||||
* [Push vs privacy, and mitigation](#push-vs-privacy-and-mitigation)
|
||||
* [Background processing limitations](#background-processing-limitations)
|
||||
2. [RiotX Notification implementations](#riotx-notification-implementations)
|
||||
* [Requirements](#requirements)
|
||||
* [Foreground sync mode (Gplay & F-Droid)](#foreground-sync-mode-gplay-f-droid)
|
||||
* [Push (FCM) received in background](#push-fcm-received-in-background)
|
||||
* [FCM Fallback mode](#fcm-fallback-mode)
|
||||
* [F-Droid background Mode](#f-droid-background-mode)
|
||||
3. [Application Settings](#application-settings)
|
||||
|
||||
|
||||
First let's start with some prerequisite knowledge
|
||||
|
||||
# Prerequisites Knowledge
|
||||
|
||||
## How does a matrix client gets a message from a Home Server?
|
||||
|
||||
In order to get messages from a home server, a matrix client need to perform a ``sync`` operation.
|
||||
|
||||
`To read events, the intended flow of operation is for clients to first call the /sync API without a since parameter. This returns the most recent message events for each room, as well as the state of the room at the start of the returned timeline. `
|
||||
|
||||
The client need to call the `sync`API periodically in order to get incremental updates of the server state (new messages).
|
||||
This mechanism is known as **HTTP long Polling**.
|
||||
|
||||
Using the **HTTP Long Polling** mechanism a client polls a server requesting new information.
|
||||
The server *holds the request open until new data is available*.
|
||||
Once available, the server responds and sends the new information.
|
||||
When the client receives the new information, it immediately sends another request, and the operation is repeated.
|
||||
This effectively emulates a server push feature.
|
||||
|
||||
The HTTP long Polling can be fine tuned in the **SDK** using two parameters:
|
||||
* timout (Sync request timeout)
|
||||
* delay (Delay between each sync)
|
||||
|
||||
**timeout** is a server paramter, defined by:
|
||||
```
|
||||
The maximum time to wait, in milliseconds, before returning this request.`
|
||||
If no events (or other data) become available before this time elapses, the server will return a response with empty fields.
|
||||
By default, this is 0, so the server will return immediately even if the response is empty.
|
||||
```
|
||||
|
||||
**delay** is a client preference. When the server responds to a sync request, the client waits for `delay`before calling a new sync.
|
||||
|
||||
When the RiotX Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0.
|
||||
|
||||
## How does a mobile app receives push notification
|
||||
|
||||
Push notification is used as a way to wake up a mobile application when some important information is available and should be processed.
|
||||
|
||||
Typically in order to get push notification, an application relies on a **Push Notification Service** or **Push Provider**.
|
||||
|
||||
For example iOS uses APNS (Apple Push Notification Service).
|
||||
Most of android devices relies on Google's Firebase Cloud Messaging (FCM).
|
||||
> FCM has replaced Google Cloud Messaging (GCM - deprecated April 10 2018)
|
||||
|
||||
FCM will only work on android devices that have Google plays services installed
|
||||
(In simple terms, Google Play Services is a background service that runs on Android, which in turn helps in integrating Google’s advanced functionalities to other applications)
|
||||
|
||||
De-Googlified devices need to rely on something else in order to stay up to date with a server.
|
||||
There some cases when devices with google services cannot use FCM (network infrastructure limitations -firewalls- ,
|
||||
privacy and or independency requirement, source code licence)
|
||||
|
||||
## Push VS Notification
|
||||
|
||||
This need some disambiguation, because it is the source of common confusion:
|
||||
|
||||
|
||||
*The fact that you see a notification on your screen does not mean that you have successfully configured your PUSH plateform.*
|
||||
|
||||
Technically there is a difference between a push and a notification. A notification is what you see on screen and/or in the notification Menu/Drawer (in the top bar of the phone).
|
||||
|
||||
Notifications are not always triggered by a push (One can display a notification locally triggered by an alarm)
|
||||
|
||||
|
||||
## Push in the matrix federated world
|
||||
|
||||
In order to send a push to a mobile, App developers need to have a server that will use the FCM APIs, and these APIs requires authentication!
|
||||
This server is called a **Push Gateway** in the matrix world
|
||||
|
||||
That means that RiotX Android, a matrix client created by New Vector, is using a **Push Gateway** with the needed credentials (FCM API secret Key) in order to send push to the New Vector client.
|
||||
|
||||
If you create your own matrix client, you will also need to deploy an instance of a **Push Gateway** with the credentials needed to use FCM for your app.
|
||||
|
||||
On registration, a matrix client must tell to it's Home Server what Push Gateway to use.
|
||||
|
||||
See [Sygnal](https://github.com/matrix-org/sygnal/) for a reference implementation.
|
||||
```
|
||||
|
||||
+--------------------+ +-------------------+
|
||||
Matrix HTTP | | | |
|
||||
Notification Protocol | App Developer | | Device Vendor |
|
||||
| | | |
|
||||
+-------------------+ | +----------------+ | | +---------------+ |
|
||||
| | | | | | | | | |
|
||||
| Matrix homeserver +-----> Push Gateway +------> Push Provider | |
|
||||
| | | | | | | | | |
|
||||
+-^-----------------+ | +----------------+ | | +----+----------+ |
|
||||
| | | | | |
|
||||
Matrix | | | | | |
|
||||
Client/Server API + | | | | |
|
||||
| | +--------------------+ +-------------------+
|
||||
| +--+-+ |
|
||||
| | <-------------------------------------------+
|
||||
+---+ |
|
||||
| | Provider Push Protocol
|
||||
+----+
|
||||
|
||||
Mobile Device or Client
|
||||
```
|
||||
|
||||
Recommended reading:
|
||||
* https://thomask.sdf.org/blog/2016/12/11/riots-magical-push-notifications-in-ios.html
|
||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id128
|
||||
|
||||
|
||||
## How does the Home Server knows when to notify a client?
|
||||
|
||||
This is defined by [**push rules**](https://matrix.org/docs/spec/client_server/r0.4.0.html#push-rules-).
|
||||
|
||||
`A push rule is a single rule that states under what conditions an event should be passed onto a push gateway and how the notification should be presented (sound / importance).`
|
||||
|
||||
A Home Server can be configured with default rules (for Direct messages, group messages, mentions, etc.. ).
|
||||
|
||||
There are different kind of push rules, it can be per room (each new message on this room should be notified), it can also define a pattern that a message should match (when you are mentioned, or key word based).
|
||||
|
||||
Notifications have 2 'levels' (`highlighted = true/false sound = default/custom`). In RiotX these notifications level are reflected as Noisy/Silent.
|
||||
|
||||
**What about encrypted messages?**
|
||||
|
||||
Of course, content patterns matching cannot be used for encrypted messages server side (as the content is encrypted).
|
||||
|
||||
That is why clients are able to **process the push rules client side** to decide what kind of notification should be presented for a given event.
|
||||
|
||||
## Push vs privacy, and mitigation
|
||||
|
||||
As seen previously, App developers don't directly send a push to the end user's device, they use a Push Provider as intermediary. So technically this intermediary is able to read the content of what is sent.
|
||||
|
||||
App developers usually mitigate this by sending a `silent notification`, that is a notification with no identifiable data, or with an encrypted payload. When the push is received the app can then synchronise to it's server in order to generate a local notification.
|
||||
|
||||
|
||||
## Background processing limitations
|
||||
|
||||
A mobile applications process live in a managed word, meaning that its process can be limited (e.g no network access), stopped or killed at almost anytime by the Operating System.
|
||||
|
||||
In order to improve the battery life of their devices some constructors started to implement mechanism to drastically limit background execution of applications (e.g MIUI/Xiaomi restrictions, Sony stamina mode).
|
||||
Then starting android M, android has also put more focus on improving device performances, introducing several IDLE modes, App-Standby, Light Doze, Doze.
|
||||
|
||||
In a nutshell, apps can't do much in background now.
|
||||
|
||||
If the devices is not plugged and stays IDLE for a certain amount of time, radio (mobile connectivity) and CPU can/will be turned off.
|
||||
|
||||
For an application like RiotX, where users can receive important information at anytime, the best option is to rely on a push system (Google's Firebase Message a.k.a FCM). FCM high priority push can wake up the device and whitelist an application to perform background task (for a limited but unspecified amount of time).
|
||||
|
||||
Notice that this is still evolving, and in future versions application that has been 'background restricted' by users won't be able to wake up even when a high priority push is received. Also high priority notifications could be rate limited (not defined anywhere)
|
||||
|
||||
It's getting a lot more complicated when you cannot rely on FCM (because: closed sources, network/firewall restrictions, privacy concerns).
|
||||
The documentation on this subject is vague, and as per our experiments not always exact, also device's behaviour is fragmented.
|
||||
|
||||
It is getting more and more complex to have reliable notifications when FCM is not used.
|
||||
|
||||
# RiotX Notification implementations
|
||||
|
||||
## Requirements
|
||||
|
||||
RiotX Android must work with and without FCM.
|
||||
* The RiotX android app published on F-Droid do not rely on FCM (all related dependencies are not present)
|
||||
* The RiotX android app published on google play rely on FCM, with a fallback mode when FCM registration has failed (e.g outdated or missing Google Play Services)
|
||||
|
||||
## Foreground sync mode (Gplay & F-Droid)
|
||||
|
||||
When in foreground, RiotX performs sync continuously with a timeout value set to 10 seconds (see HttpPooling).
|
||||
|
||||
As this mode does not need to live beyond the scope of the application, and as per Google recommendation, RiotX uses the internal app resources (Thread and Timers) to perform the syncs.
|
||||
|
||||
This mode is turned on when the app enters foreground, and off when enters background.
|
||||
|
||||
In background, and depending on wether push is available or not, RiotX will use different methods to perform the syncs (Workers / Alarms / Service)
|
||||
|
||||
## Push (FCM) received in background
|
||||
|
||||
In order to enable Push, RiotX must first get a push token from the firebase SDK, then register a pusher with this token on the HomeServer.
|
||||
|
||||
When a message should be notified to a user, the user's homeserver notifies the registered `push gateway` for RiotX, that is [sygnal](https://github.com/matrix-org/sygnal) _- The reference implementation for push gateways -_ hosted by matrix.org.
|
||||
|
||||
This sygnal instance is configured with the required FCM API authentication token, and will then use the FCM API in order to notify the user's device running riotX.
|
||||
|
||||
```
|
||||
Homeserver ----> Sygnal (configured for RiotX) ----> FCM ----> RiotX
|
||||
```
|
||||
|
||||
The push gateway is configured to only send `(eventId,roomId)` in the push payload (for better [privacy](#push-vs-privacy-and-mitigation)).
|
||||
|
||||
RiotX needs then to synchronise with the user's HomeServer, in order to resolve the event and create a notification.
|
||||
|
||||
As per [Google recommendation](https://android-developers.googleblog.com/2018/09/notifying-your-users-with-fcm.html), RiotX will then use the WorkManager API in order to trigger a background sync.
|
||||
|
||||
**Google recommendations:**
|
||||
> We recommend using FCM messages in combination with the WorkManager 1 or JobScheduler API
|
||||
|
||||
> Avoid background services. One common pitfall is using a background service to fetch data in the FCM message handler, since background service will be stopped by the system per recent changes to Google Play Policy
|
||||
|
||||
```
|
||||
Homeserver ----> Sygnal ----> FCM ----> RiotX
|
||||
(Sync) ----> Homeserver
|
||||
<----
|
||||
Display notification
|
||||
```
|
||||
|
||||
**Possible outcomes**
|
||||
|
||||
Upon reception of the FCM push, RiotX will perform a sync call to the Home Server, during this process it is possible that:
|
||||
* Happy path, the sync is performed, the message resolved and displayed in the notification drawer
|
||||
* The notified message is not in the sync. Can happen if a lot of things did happen since the push (`gappy sync`)
|
||||
* The sync generates additional notifications (e.g an encrypted message where the user is mentioned detected locally)
|
||||
* The sync takes too long and the process is killed before completion, or network is not reliable and the sync fails.
|
||||
|
||||
RiotX implements several strategies in these cases (TODO document)
|
||||
|
||||
## FCM Fallback mode
|
||||
|
||||
It is possible that RiotX is not able to get a FCM push token.
|
||||
Common errors (amoung several others) that can cause that:
|
||||
* Google Play Services is outdated
|
||||
* Google Play Service fails in someways with FCM servers (infamous `SERVICE_NOT_AVAILABLE`)
|
||||
|
||||
If RiotX is able to detect one of this cases, it will notifies it to the users and when possible help him fix it via a dedicated troubleshoot screen.
|
||||
|
||||
Meanwhile, in order to offer a minimal service, and as per Google's recommendation for background activities, RiotX will launch periodic background sync in order to stays in sync with servers.
|
||||
|
||||
The fallback mode is impacted by all the battery life saving mechanism implemented by android. Meaning that if the app is not used for a certain amount of time (`App-Standby`), or the device stays still and unplugged (`Light Doze`) , the sync will become less frequent.
|
||||
|
||||
And if the device stays unplugged and still for too long (`Doze Mode`), no background sync will be perform at all (the system's `Ignore Battery Optimization option` has no effect on that).
|
||||
|
||||
Also the time interval between sync is elastic, controlled by the system to group other apps background sync request and start radio/cpu only once for all.
|
||||
|
||||
Usually in this mode, what happen is when you take back your phone in your hand, you suddenly receive notifications.
|
||||
|
||||
The fallback mode is supposed to be a temporary state waiting for the user to fix issues for FCM, or for App Developers that has done a fork to correctly configure their FCM settings.
|
||||
|
||||
## F-Droid background Mode
|
||||
|
||||
The F-Droid RiotX flavor has no dependencies to FCM, therefore cannot relies on Push.
|
||||
|
||||
Also Google's recommended background processing method cannot be applied. This is because all of these methods are affected by IDLE modes, and will result on the user not being notified at all when the app is in a Doze mode (only in maintenance windows that could happens only after hours).
|
||||
|
||||
Only solution left is to use `AlarmManager`, that offers new API to allow launching some process even if the App is in IDLE modes.
|
||||
|
||||
Notice that these alarms, due to their potential impact on battery life, can still be restricted by the system. Documentation says that they will not be triggered more than every minutes under normal system operation, and when in low power mode about every 15 mn.
|
||||
|
||||
These restrictions can be relaxed by requirering the app to be white listed from battery optimization.
|
||||
|
||||
F-Droid version will schedule alarms that will then trigger a Broadcast Receiver, that in turn will launch a Service (in the classic android way), and the reschedule an alarm for next time.
|
||||
|
||||
Depending on the system status (or device make), it is still possible that the app is not given enough time to launch the service, or that the radio is still turned off thus preventing the sync to success (that's why Alarms are not recommended for network related tasks).
|
||||
|
||||
That is why on RiotX F-Droid, the broadcast receiver will acquire a temporary WAKE_LOCK for several seconds (thus securing cpu/network), and launch the service in foreground. The service performs the sync.
|
||||
|
||||
Note that foreground services require to put a notification informing the user that the app is doing something even if not launched).
|
||||
|
||||
|
||||
|
||||
# Application Settings
|
||||
|
||||
**Notifications > Enable notifications for this account**
|
||||
|
||||
Configure Sygnal to send or not notifications to all user devices.
|
||||
|
||||
**Notifications > Enable notifications for this device**
|
||||
|
||||
Disable notifications locally. The push server will continue to send notifications to the device but this one will ignore them.
|
||||
|
||||
|
@ -13,3 +13,11 @@ org.gradle.jvmargs=-Xmx1536m
|
||||
# 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
|
||||
# org.gradle.parallel=true
|
||||
|
||||
|
||||
vector.debugPrivateData=false
|
||||
vector.httpLogLevel=NONE
|
||||
|
||||
# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
|
||||
#vector.debugPrivateData=true
|
||||
#vector.httpLogLevel=BODY
|
||||
|
@ -33,16 +33,14 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation project(":matrix-sdk-android")
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
// Paging
|
||||
implementation 'androidx.paging:paging-runtime:2.0.0'
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.MainThreadDisposable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
private class LiveDataObservable<T>(
|
||||
private val liveData: LiveData<T>,
|
||||
@ -57,5 +59,5 @@ private class LiveDataObservable<T>(
|
||||
}
|
||||
|
||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||
return LiveDataObservable(this)
|
||||
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import io.reactivex.CompletableEmitter
|
||||
import io.reactivex.SingleEmitter
|
||||
|
||||
internal class MatrixCallbackCompletable<T>(private val completableEmitter: CompletableEmitter) : MatrixCallback<T> {
|
||||
|
||||
override fun onSuccess(data: T) {
|
||||
completableEmitter.onComplete()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
completableEmitter.tryOnError(failure)
|
||||
}
|
||||
}
|
||||
|
||||
fun Cancelable.toCompletable(completableEmitter: CompletableEmitter) {
|
||||
completableEmitter.setCancellable {
|
||||
this.cancel()
|
||||
}
|
||||
}
|
@ -14,23 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.list
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import io.reactivex.SingleEmitter
|
||||
|
||||
private const val SHARED_PREFS_SELECTED_ROOM_KEY = "SHARED_PREFS_SELECTED_ROOM_KEY"
|
||||
internal class MatrixCallbackSingle<T>(private val singleEmitter: SingleEmitter<T>) : MatrixCallback<T> {
|
||||
|
||||
class RoomSelectionRepository(private val sharedPreferences: SharedPreferences) {
|
||||
|
||||
fun lastSelectedRoom(): String? {
|
||||
return sharedPreferences.getString(SHARED_PREFS_SELECTED_ROOM_KEY, null)
|
||||
override fun onSuccess(data: T) {
|
||||
singleEmitter.onSuccess(data)
|
||||
}
|
||||
|
||||
fun saveLastSelectedRoom(roomId: String) {
|
||||
sharedPreferences.edit()
|
||||
.putString(SHARED_PREFS_SELECTED_ROOM_KEY, roomId)
|
||||
.apply()
|
||||
override fun onFailure(failure: Throwable) {
|
||||
singleEmitter.tryOnError(failure)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun <T> Cancelable.toSingle(singleEmitter: SingleEmitter<T>) {
|
||||
singleEmitter.setCancellable {
|
||||
this.cancel()
|
||||
}
|
||||
}
|
@ -17,19 +17,43 @@
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
class RxRoom(private val room: Room) {
|
||||
|
||||
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||
return room.roomSummary.asObservable()
|
||||
return room.liveRoomSummary().asObservable()
|
||||
}
|
||||
|
||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||
return room.getRoomMemberIdsLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||
return room.getEventSummaryLive(eventId).asObservable()
|
||||
}
|
||||
|
||||
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
||||
return room.liveTimeLineEvent(eventId).asObservable()
|
||||
}
|
||||
|
||||
fun loadRoomMembersIfNeeded(): Single<Unit> = Single.create {
|
||||
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
|
||||
}
|
||||
|
||||
fun joinRoom(viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||
}
|
||||
|
||||
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Room.rx(): RxRoom {
|
||||
|
@ -16,10 +16,16 @@
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
@ -31,6 +37,36 @@ class RxSession(private val session: Session) {
|
||||
return session.liveGroupSummaries().asObservable()
|
||||
}
|
||||
|
||||
fun liveSyncState(): Observable<SyncState> {
|
||||
return session.syncState().asObservable()
|
||||
}
|
||||
|
||||
fun livePushers(): Observable<List<Pusher>> {
|
||||
return session.livePushers().asObservable()
|
||||
}
|
||||
|
||||
fun liveUsers(): Observable<List<User>> {
|
||||
return session.liveUsers().asObservable()
|
||||
}
|
||||
|
||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||
return session.livePagedUsers(filter).asObservable()
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create {
|
||||
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
|
||||
}
|
||||
|
||||
fun searchUsersDirectory(search: String,
|
||||
limit: Int,
|
||||
excludedUserIds: Set<String>): Single<List<User>> = Single.create {
|
||||
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
|
||||
}
|
||||
|
||||
fun joinRoom(roomId: String, viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||
session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Session.rx(): RxSession {
|
||||
|
@ -6,20 +6,14 @@ apply plugin: 'realm-android'
|
||||
apply plugin: 'okreplay'
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "io.realm:realm-gradle-plugin:5.9.0"
|
||||
classpath "io.realm:realm-gradle-plugin:5.12.0"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
@ -32,10 +26,12 @@ android {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionName "0.0.1"
|
||||
// Multidex is useful for tests
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
||||
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
||||
@ -45,10 +41,9 @@ android {
|
||||
|
||||
debug {
|
||||
// Set to true to log privacy or sensible data, such as token
|
||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||
|
||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
|
||||
// Set to BODY instead of NONE to enable logging
|
||||
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." + project.property("vector.httpLogLevel")
|
||||
}
|
||||
|
||||
release {
|
||||
@ -67,6 +62,11 @@ android {
|
||||
lintOptions {
|
||||
lintConfig file("lint.xml")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
static def gitRevision() {
|
||||
@ -87,48 +87,53 @@ static def gitRevisionDate() {
|
||||
dependencies {
|
||||
|
||||
def arrow_version = "0.8.0"
|
||||
def support_version = '1.1.0-alpha03'
|
||||
def support_version = '1.1.0-beta01'
|
||||
def moshi_version = '1.8.0'
|
||||
def lifecycle_version = '2.0.0'
|
||||
def coroutines_version = "1.0.1"
|
||||
def markwon_version = '3.0.0'
|
||||
def daggerVersion = '2.23.1'
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$support_version"
|
||||
implementation "androidx.recyclerview:recyclerview:$support_version"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0-rc01"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0-beta01"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||
|
||||
// Network
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
|
||||
implementation 'com.novoda:merlin:1.1.6'
|
||||
implementation 'com.novoda:merlin:1.2.0'
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||
|
||||
implementation "ru.noties.markwon:core:$markwon_version"
|
||||
|
||||
// Database
|
||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||
|
||||
// Work
|
||||
implementation "android.arch.work:work-runtime-ktx:1.0.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
|
||||
implementation "io.arrow-kt:arrow-effects:$arrow_version"
|
||||
implementation "io.arrow-kt:arrow-effects-instances:$arrow_version"
|
||||
implementation "io.arrow-kt:arrow-integration-retrofit-adapter:$arrow_version"
|
||||
|
||||
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
|
||||
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'
|
||||
|
||||
// DI
|
||||
implementation "org.koin:koin-core:$koin_version"
|
||||
implementation "org.koin:koin-core-ext:$koin_version"
|
||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0'
|
||||
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'
|
||||
|
||||
// Logging
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
@ -140,18 +145,16 @@ dependencies {
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.robolectric:robolectric:4.0.2'
|
||||
testImplementation "org.koin:koin-test:$koin_version"
|
||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
|
||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
androidTestImplementation "org.koin:koin-test:$koin_version"
|
||||
androidTestImplementation 'androidx.test:core:1.1.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test:rules:1.1.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
|
||||
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||
|
Binary file not shown.
Binary file not shown.
@ -16,10 +16,10 @@
|
||||
|
||||
package im.vector.matrix.android;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -19,4 +19,4 @@ package im.vector.matrix.android
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main)
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main)
|
@ -19,36 +19,21 @@ package im.vector.matrix.android.auth
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.OkReplayRuleChainNoActivity
|
||||
import im.vector.matrix.android.api.auth.Authenticator
|
||||
import im.vector.matrix.android.internal.auth.AuthModule
|
||||
import im.vector.matrix.android.internal.di.MatrixModule
|
||||
import im.vector.matrix.android.internal.di.NetworkModule
|
||||
import okreplay.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.koin.standalone.StandAloneContext.loadKoinModules
|
||||
import org.koin.standalone.inject
|
||||
import org.koin.test.KoinTest
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class AuthenticatorTest : InstrumentedTest, KoinTest {
|
||||
internal class AuthenticatorTest : InstrumentedTest {
|
||||
|
||||
init {
|
||||
Monarchy.init(context())
|
||||
val matrixModule = MatrixModule(context()).definition
|
||||
val networkModule = NetworkModule().definition
|
||||
val authModule = AuthModule().definition
|
||||
loadKoinModules(listOf(matrixModule, networkModule, authModule))
|
||||
}
|
||||
|
||||
private val authenticator: Authenticator by inject()
|
||||
private val okReplayInterceptor: OkReplayInterceptor by inject()
|
||||
lateinit var authenticator: Authenticator
|
||||
lateinit var okReplayInterceptor: OkReplayInterceptor
|
||||
|
||||
private val okReplayConfig = OkReplayConfig.Builder()
|
||||
.tapeRoot(AndroidTapeRoot(
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2018 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 im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class CryptoStoreHelper {
|
||||
|
||||
fun createStore(): IMXCryptoStore {
|
||||
return RealmCryptoStore(
|
||||
realmConfiguration = RealmConfiguration.Builder()
|
||||
.name("test.realm")
|
||||
.modules(RealmCryptoStoreModule())
|
||||
.build(),
|
||||
credentials = createCredential())
|
||||
}
|
||||
|
||||
fun createCredential() = Credentials(
|
||||
userId = "userId_" + Random.nextInt(),
|
||||
homeServer = "http://matrix.org",
|
||||
accessToken = "access_token",
|
||||
refreshToken = null,
|
||||
deviceId = "deviceId_sample"
|
||||
)
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2018 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 im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmManager
|
||||
import org.matrix.olm.OlmSession
|
||||
|
||||
private const val DUMMY_DEVICE_KEY = "DeviceKey"
|
||||
|
||||
class CryptoStoreTest {
|
||||
|
||||
private val cryptoStoreHelper = CryptoStoreHelper()
|
||||
|
||||
@Test
|
||||
fun test_metadata_realm_ok() {
|
||||
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||
|
||||
assertFalse(cryptoStore.hasData())
|
||||
|
||||
cryptoStore.open()
|
||||
|
||||
assertEquals("deviceId_sample", cryptoStore.getDeviceId())
|
||||
|
||||
assertTrue(cryptoStore.hasData())
|
||||
|
||||
// Cleanup
|
||||
cryptoStore.close()
|
||||
cryptoStore.deleteStore()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_lastSessionUsed() {
|
||||
// Ensure Olm is initialized
|
||||
OlmManager()
|
||||
|
||||
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||
|
||||
assertNull(cryptoStore.getLastUsedSessionId(DUMMY_DEVICE_KEY))
|
||||
|
||||
val olmAccount1 = OlmAccount().apply {
|
||||
generateOneTimeKeys(1)
|
||||
}
|
||||
|
||||
val olmSession1 = OlmSession().apply {
|
||||
initOutboundSession(olmAccount1,
|
||||
olmAccount1.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY],
|
||||
olmAccount1.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first())
|
||||
}
|
||||
|
||||
val sessionId1 = olmSession1.sessionIdentifier()
|
||||
val olmSessionWrapper1 = OlmSessionWrapper(olmSession1)
|
||||
|
||||
cryptoStore.storeSession(olmSessionWrapper1, DUMMY_DEVICE_KEY)
|
||||
|
||||
assertEquals(sessionId1, cryptoStore.getLastUsedSessionId(DUMMY_DEVICE_KEY))
|
||||
|
||||
val olmAccount2 = OlmAccount().apply {
|
||||
generateOneTimeKeys(1)
|
||||
}
|
||||
|
||||
val olmSession2 = OlmSession().apply {
|
||||
initOutboundSession(olmAccount2,
|
||||
olmAccount2.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY],
|
||||
olmAccount2.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first())
|
||||
}
|
||||
|
||||
val sessionId2 = olmSession2.sessionIdentifier()
|
||||
val olmSessionWrapper2 = OlmSessionWrapper(olmSession2)
|
||||
|
||||
cryptoStore.storeSession(olmSessionWrapper2, DUMMY_DEVICE_KEY)
|
||||
|
||||
// Ensure sessionIds are distinct
|
||||
assertNotEquals(sessionId1, sessionId2)
|
||||
|
||||
// Note: we cannot be sure what will be the result of getLastUsedSessionId() here
|
||||
|
||||
olmSessionWrapper2.onMessageReceived()
|
||||
cryptoStore.storeSession(olmSessionWrapper2, DUMMY_DEVICE_KEY)
|
||||
|
||||
// sessionId2 is returned now
|
||||
assertEquals(sessionId2, cryptoStore.getLastUsedSessionId(DUMMY_DEVICE_KEY))
|
||||
|
||||
Thread.sleep(2)
|
||||
|
||||
olmSessionWrapper1.onMessageReceived()
|
||||
cryptoStore.storeSession(olmSessionWrapper1, DUMMY_DEVICE_KEY)
|
||||
|
||||
// sessionId1 is returned now
|
||||
assertEquals(sessionId1, cryptoStore.getLastUsedSessionId(DUMMY_DEVICE_KEY))
|
||||
|
||||
// Cleanup
|
||||
olmSession1.releaseSession()
|
||||
olmSession2.releaseSession()
|
||||
|
||||
olmAccount1.releaseAccount()
|
||||
olmAccount2.releaseAccount()
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.util
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun identityTest() {
|
||||
listOf(
|
||||
"{}",
|
||||
"""{"a":true}""",
|
||||
"""{"a":false}""",
|
||||
"""{"a":1}""",
|
||||
"""{"a":1.2}""",
|
||||
"""{"a":null}""",
|
||||
"""{"a":[]}""",
|
||||
"""{"a":["b":"c"]}""",
|
||||
"""{"a":["c":"b","d":"e"]}""",
|
||||
"""{"a":["d":"b","c":"e"]}"""
|
||||
).forEach {
|
||||
assertEquals(it,
|
||||
JsonCanonicalizer.canonicalize(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reorderTest() {
|
||||
assertEquals("""{"a":true,"b":false}""",
|
||||
JsonCanonicalizer.canonicalize("""{"b":false,"a":true}"""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun realSampleTest() {
|
||||
assertEquals("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX\/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""",
|
||||
JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}"""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleQuoteTest() {
|
||||
assertEquals("{\"a\":\"\\\"\"}",
|
||||
JsonCanonicalizer.canonicalize("{\"a\":\"\\\"\"}"))
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ==========================================================================================
|
||||
* Test from https://matrix.org/docs/spec/appendices.html#examples
|
||||
* ========================================================================================== */
|
||||
|
||||
@Test
|
||||
fun matrixOrg001Test() {
|
||||
assertEquals("""{}""",
|
||||
JsonCanonicalizer.canonicalize("""{}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg002Test() {
|
||||
assertEquals("""{"one":1,"two":"Two"}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
"one": 1,
|
||||
"two": "Two"
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg003Test() {
|
||||
assertEquals("""{"a":"1","b":"2"}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
"b": "2",
|
||||
"a": "1"
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg004Test() {
|
||||
assertEquals("""{"a":"1","b":"2"}""",
|
||||
JsonCanonicalizer.canonicalize("""{"b":"2","a":"1"}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg005Test() {
|
||||
assertEquals("""{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
"auth": {
|
||||
"success": true,
|
||||
"mxid": "@john.doe:example.com",
|
||||
"profile": {
|
||||
"display_name": "John Doe",
|
||||
"three_pids": [
|
||||
{
|
||||
"medium": "email",
|
||||
"address": "john.doe@example.org"
|
||||
},
|
||||
{
|
||||
"medium": "msisdn",
|
||||
"address": "123456789"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg006Test() {
|
||||
assertEquals("""{"a":"日本語"}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
"a": "日本語"
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg007Test() {
|
||||
assertEquals("""{"日":1,"本":2}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
"本": 2,
|
||||
"日": 1
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg008Test() {
|
||||
assertEquals("""{"a":"日"}""",
|
||||
JsonCanonicalizer.canonicalize("{\"a\": \"\u65E5\"}"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg009Test() {
|
||||
assertEquals("""{"a":null}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
"a": null
|
||||
}"""))
|
||||
}
|
||||
}
|
@ -19,11 +19,7 @@ package im.vector.matrix.android.session.room.timeline
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.internal.database.helper.add
|
||||
import im.vector.matrix.android.internal.database.helper.addAll
|
||||
import im.vector.matrix.android.internal.database.helper.isUnlinked
|
||||
import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
||||
import im.vector.matrix.android.internal.database.helper.merge
|
||||
import im.vector.matrix.android.internal.database.helper.*
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||
@ -59,7 +55,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeMessageEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.events.size shouldEqual 1
|
||||
chunk.timelineEvents.size shouldEqual 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +66,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
val fakeEvent = createFakeMessageEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.events.size shouldEqual 1
|
||||
chunk.timelineEvents.size shouldEqual 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,7 +122,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.events.size shouldEqual 60
|
||||
chunk1.timelineEvents.size shouldEqual 60
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +138,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
|
||||
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.events.size shouldEqual 40
|
||||
chunk1.timelineEvents.size shouldEqual 40
|
||||
chunk1.isLastForward.shouldBeTrue()
|
||||
}
|
||||
}
|
||||
|
@ -16,16 +16,14 @@
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class FakeGetContextOfEventTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
|
||||
internal class FakeGetContextOfEventTask constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
|
||||
|
||||
override fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
|
||||
override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
|
||||
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||
val tokenChunkEvent = FakeTokenChunkEvent(
|
||||
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||
|
@ -16,14 +16,14 @@
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class FakePaginationTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
||||
internal class FakePaginationTask @Inject constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
||||
|
||||
override fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
|
||||
override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
|
||||
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||
|
@ -59,7 +59,7 @@ object RoomDataHelper {
|
||||
eventId = Random.nextLong().toString(),
|
||||
content = content,
|
||||
prevContent = prevContent,
|
||||
sender = sender,
|
||||
senderId = sender,
|
||||
stateKey = stateKey
|
||||
)
|
||||
}
|
||||
|
@ -18,24 +18,6 @@ package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
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.internal.session.room.EventRelationExtractor
|
||||
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
|
||||
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.testCoroutineDispatchers
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import org.amshove.kluent.shouldEqual
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
internal class TimelineTest : InstrumentedTest {
|
||||
|
||||
@ -45,59 +27,60 @@ internal class TimelineTest : InstrumentedTest {
|
||||
|
||||
private lateinit var monarchy: Monarchy
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
Realm.init(context())
|
||||
val testConfiguration = RealmConfiguration.Builder().name("test-realm").build()
|
||||
Realm.deleteRealm(testConfiguration)
|
||||
monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build()
|
||||
RoomDataHelper.fakeInitialSync(monarchy, ROOM_ID)
|
||||
}
|
||||
|
||||
private fun createTimeline(initialEventId: String? = null): Timeline {
|
||||
val taskExecutor = TaskExecutor(testCoroutineDispatchers)
|
||||
val erau = EventRelationsAggregationUpdater(Credentials("", "", "", null, null))
|
||||
val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy, erau)
|
||||
val paginationTask = FakePaginationTask(tokenChunkEventPersistor)
|
||||
val getContextOfEventTask = FakeGetContextOfEventTask(tokenChunkEventPersistor)
|
||||
val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID)
|
||||
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor())
|
||||
return DefaultTimeline(
|
||||
ROOM_ID,
|
||||
initialEventId,
|
||||
monarchy.realmConfiguration,
|
||||
taskExecutor,
|
||||
getContextOfEventTask,
|
||||
timelineEventFactory,
|
||||
paginationTask,
|
||||
null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun backPaginate_shouldLoadMoreEvents_whenPaginateIsCalled() {
|
||||
val timeline = createTimeline()
|
||||
timeline.start()
|
||||
val paginationCount = 30
|
||||
var initialLoad = 0
|
||||
val latch = CountDownLatch(2)
|
||||
var timelineEvents: List<TimelineEvent> = emptyList()
|
||||
timeline.listener = object : Timeline.Listener {
|
||||
override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||
if (snapshot.isNotEmpty()) {
|
||||
if (initialLoad == 0) {
|
||||
initialLoad = snapshot.size
|
||||
}
|
||||
timelineEvents = snapshot
|
||||
latch.countDown()
|
||||
timeline.paginate(Timeline.Direction.BACKWARDS, paginationCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
timelineEvents.size shouldEqual initialLoad + paginationCount
|
||||
timeline.dispose()
|
||||
}
|
||||
// @Before
|
||||
// fun setup() {
|
||||
// Timber.plant(Timber.DebugTree())
|
||||
// Realm.init(context())
|
||||
// val testConfiguration = RealmConfiguration.Builder().name("test-realm")
|
||||
// .modules(SessionRealmModule()).build()
|
||||
//
|
||||
// Realm.deleteRealm(testConfiguration)
|
||||
// monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build()
|
||||
// RoomDataHelper.fakeInitialSync(monarchy, ROOM_ID)
|
||||
// }
|
||||
//
|
||||
// private fun createTimeline(initialEventId: String? = null): Timeline {
|
||||
// val taskExecutor = TaskExecutor(testCoroutineDispatchers)
|
||||
// val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy)
|
||||
// val paginationTask = FakePaginationTask @Inject constructor(tokenChunkEventPersistor)
|
||||
// val getContextOfEventTask = FakeGetContextOfEventTask @Inject constructor(tokenChunkEventPersistor)
|
||||
// val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID)
|
||||
// val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor())
|
||||
// return DefaultTimeline(
|
||||
// ROOM_ID,
|
||||
// initialEventId,
|
||||
// monarchy.realmConfiguration,
|
||||
// taskExecutor,
|
||||
// getContextOfEventTask,
|
||||
// timelineEventFactory,
|
||||
// paginationTask,
|
||||
// null)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun backPaginate_shouldLoadMoreEvents_whenPaginateIsCalled() {
|
||||
// val timeline = createTimeline()
|
||||
// timeline.start()
|
||||
// val paginationCount = 30
|
||||
// var initialLoad = 0
|
||||
// val latch = CountDownLatch(2)
|
||||
// var timelineEvents: List<TimelineEvent> = emptyList()
|
||||
// timeline.listener = object : Timeline.Listener {
|
||||
// override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||
// if (snapshot.isNotEmpty()) {
|
||||
// if (initialLoad == 0) {
|
||||
// initialLoad = snapshot.size
|
||||
// }
|
||||
// timelineEvents = snapshot
|
||||
// latch.countDown()
|
||||
// timeline.paginate(Timeline.Direction.BACKWARDS, paginationCount)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// latch.await()
|
||||
// timelineEvents.size shouldEqual initialLoad + paginationCount
|
||||
// timeline.dispose()
|
||||
// }
|
||||
|
||||
|
||||
}
|
@ -17,12 +17,14 @@
|
||||
|
||||
package im.vector.matrix.android.internal.network.interceptors
|
||||
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okio.Buffer
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* An OkHttp interceptor that logs requests as curl shell commands. They can then
|
||||
@ -33,7 +35,8 @@ import java.nio.charset.Charset
|
||||
* information. It should only be used in a controlled manner or in a
|
||||
* non-production environment.
|
||||
*/
|
||||
internal class CurlLoggingInterceptor(private val logger: HttpLoggingInterceptor.Logger = HttpLoggingInterceptor.Logger.DEFAULT)
|
||||
@MatrixScope
|
||||
internal class CurlLoggingInterceptor @Inject constructor(private val logger: HttpLoggingInterceptor.Logger)
|
||||
: Interceptor {
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="im.vector.matrix.android">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
@ -7,9 +8,10 @@
|
||||
|
||||
<application>
|
||||
|
||||
<provider
|
||||
android:name=".internal.MatrixInitProvider"
|
||||
android:authorities="im.vector.matrix.android.MatrixInitProvider" />
|
||||
<provider android:name="androidx.work.impl.WorkManagerInitializer"
|
||||
android:authorities="${applicationId}.workmanager-init"
|
||||
android:exported="false"
|
||||
tools:node="remove" />
|
||||
|
||||
</application>
|
||||
|
||||
|
@ -18,74 +18,85 @@ package im.vector.matrix.android.api
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.auth.Authenticator
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.internal.auth.AuthModule
|
||||
import im.vector.matrix.android.internal.di.MatrixKoinComponent
|
||||
import im.vector.matrix.android.internal.di.MatrixKoinHolder
|
||||
import im.vector.matrix.android.internal.di.MatrixModule
|
||||
import im.vector.matrix.android.internal.di.NetworkModule
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
||||
import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import org.koin.standalone.inject
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
data class MatrixConfiguration(
|
||||
val applicationFlavor: String = "Default-application-flavor"
|
||||
) {
|
||||
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main entry point to the matrix sdk.
|
||||
* This class is automatically init by a provider.
|
||||
* To get the singleton instance, use getInstance static method.
|
||||
*/
|
||||
class Matrix private constructor(context: Context) : MatrixKoinComponent {
|
||||
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
private val authenticator by inject<Authenticator>()
|
||||
private val userAgentHolder by inject<UserAgentHolder>()
|
||||
private val backgroundDetectionObserver by inject<BackgroundDetectionObserver>()
|
||||
var currentSession: Session? = null
|
||||
@Inject internal lateinit var authenticator: Authenticator
|
||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@Inject internal lateinit var olmManager: OlmManager
|
||||
@Inject internal lateinit var sessionManager: SessionManager
|
||||
|
||||
init {
|
||||
Monarchy.init(context)
|
||||
val matrixModule = MatrixModule(context).definition
|
||||
val networkModule = NetworkModule().definition
|
||||
val authModule = AuthModule().definition
|
||||
MatrixKoinHolder.instance.loadModules(listOf(matrixModule, networkModule, authModule))
|
||||
DaggerMatrixComponent.factory().create(context).inject(this)
|
||||
if (context.applicationContext !is Configuration.Provider) {
|
||||
WorkManager.initialize(context, Configuration.Builder().build())
|
||||
}
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
authenticator.getLastActiveSession()?.also {
|
||||
currentSession = it
|
||||
it.open()
|
||||
it.setFilter(FilterService.FilterPreset.RiotFilter)
|
||||
it.startSync()
|
||||
}
|
||||
userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor)
|
||||
}
|
||||
|
||||
fun getUserAgent() = userAgentHolder.userAgent
|
||||
|
||||
fun authenticator(): Authenticator {
|
||||
return authenticator
|
||||
}
|
||||
|
||||
/**
|
||||
* Set application flavor, to alter user agent.
|
||||
*/
|
||||
fun setApplicationFlavor(flavor: String) {
|
||||
userAgentHolder.setApplicationFlavor(flavor)
|
||||
}
|
||||
|
||||
fun getUserAgent() = userAgentHolder.userAgent
|
||||
|
||||
companion object {
|
||||
|
||||
private lateinit var instance: Matrix
|
||||
private val isInit = AtomicBoolean(false)
|
||||
|
||||
internal fun initialize(context: Context) {
|
||||
fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
instance = Matrix(context.applicationContext)
|
||||
instance = Matrix(context.applicationContext, matrixConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstance(): Matrix {
|
||||
fun getInstance(context: Context): Matrix {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
val appContext = context.applicationContext
|
||||
if (appContext is MatrixConfiguration.Provider) {
|
||||
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
|
||||
instance = Matrix(appContext, matrixConfiguration)
|
||||
} else {
|
||||
throw IllegalStateException("Matrix is not initialized properly." +
|
||||
" You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
|
||||
}
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
fun getSdkVersion(): String {
|
||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,8 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.api
|
||||
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* This class contains pattern to match the different Matrix ids
|
||||
@ -25,53 +23,57 @@ import java.util.regex.Pattern
|
||||
object MatrixPatterns {
|
||||
|
||||
// Note: TLD is not mandatory (localhost, IP address...)
|
||||
private val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?"
|
||||
private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?"
|
||||
|
||||
// regex pattern to find matrix user ids in a string.
|
||||
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
|
||||
private val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
|
||||
val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = Pattern.compile(MATRIX_USER_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
|
||||
val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find room ids in a string.
|
||||
private val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
||||
val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = Pattern.compile(MATRIX_ROOM_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find room aliases in a string.
|
||||
private val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX"
|
||||
val PATTERN_CONTAIN_MATRIX_ALIAS = Pattern.compile(MATRIX_ROOM_ALIAS_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find message ids in a string.
|
||||
private val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX"
|
||||
val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = Pattern.compile(MATRIX_EVENT_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private const val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find message ids in a string.
|
||||
private val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+"
|
||||
val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V3_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// Ref: https://matrix.org/docs/spec/rooms/v4#event-ids
|
||||
private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find group ids in a string.
|
||||
private val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX"
|
||||
val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = Pattern.compile(MATRIX_GROUP_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private const val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = MATRIX_GROUP_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find permalink with message id.
|
||||
// Android does not support in URL so extract it.
|
||||
private val PERMALINK_BASE_REGEX = "https://matrix\\.to/#/"
|
||||
private val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/[A-Z]{3,}/#/room/"
|
||||
val SEP_REGEX = "/"
|
||||
private const val PERMALINK_BASE_REGEX = "https://matrix\\.to/#/"
|
||||
private const val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/[A-Z]{3,}/#/room/"
|
||||
const val SEP_REGEX = "/"
|
||||
|
||||
private val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE)
|
||||
private const val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = LINK_TO_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
private val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE)
|
||||
private const val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = LINK_TO_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
private val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_APP_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE)
|
||||
private const val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = LINK_TO_APP_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
private val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_APP_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE)
|
||||
private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// list of patterns to find some matrix item.
|
||||
val MATRIX_PATTERNS = Arrays.asList(
|
||||
val MATRIX_PATTERNS = listOf(
|
||||
PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID,
|
||||
PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS,
|
||||
PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID,
|
||||
@ -90,7 +92,7 @@ object MatrixPatterns {
|
||||
* @return true if the string is a valid user id
|
||||
*/
|
||||
fun isUserId(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER.matcher(str).matches()
|
||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +102,7 @@ object MatrixPatterns {
|
||||
* @return true if the string is a valid room Id
|
||||
*/
|
||||
fun isRoomId(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER.matcher(str).matches()
|
||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,7 +112,7 @@ object MatrixPatterns {
|
||||
* @return true if the string is a valid room alias.
|
||||
*/
|
||||
fun isRoomAlias(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_ALIAS.matcher(str).matches()
|
||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,7 +123,9 @@ object MatrixPatterns {
|
||||
*/
|
||||
fun isEventId(str: String?): Boolean {
|
||||
return str != null
|
||||
&& (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches() || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches())
|
||||
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|
||||
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|
||||
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,6 +135,25 @@ object MatrixPatterns {
|
||||
* @return true if the string is a valid group id.
|
||||
*/
|
||||
fun isGroupId(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER.matcher(str).matches()
|
||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
||||
}
|
||||
}// Cannot be instantiated
|
||||
|
||||
/**
|
||||
* Extract server name from a matrix id
|
||||
*
|
||||
* @param matrixId
|
||||
* @return null if not found or if matrixId is null
|
||||
*/
|
||||
fun extractServerNameFromId(matrixId: String?): String? {
|
||||
if (matrixId == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val index = matrixId.indexOf(":")
|
||||
|
||||
return if (index == -1) {
|
||||
null
|
||||
} else matrixId.substring(index + 1)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
@ -35,19 +36,24 @@ interface Authenticator {
|
||||
*/
|
||||
fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
//TODO remove this method. Shouldn't be managed like that.
|
||||
/**
|
||||
* Check if there is an active [Session].
|
||||
* Check if there is an authenticated [Session].
|
||||
* @return true if there is at least one active session.
|
||||
*/
|
||||
fun hasActiveSessions(): Boolean
|
||||
fun hasAuthenticatedSessions(): Boolean
|
||||
|
||||
//TODO remove this method. Shouldn't be managed like that.
|
||||
/**
|
||||
* Get the last active [Session], if there is an active session.
|
||||
* @return the lastActive session if any, or null
|
||||
* Get the last authenticated [Session], if there is an active session.
|
||||
* @return the last active session if any, or null
|
||||
*/
|
||||
fun getLastActiveSession(): 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?
|
||||
}
|
@ -31,7 +31,7 @@ import okhttp3.TlsVersion
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class HomeServerConnectionConfig(
|
||||
val homeServerUri: Uri,
|
||||
val identityServerUri: Uri,
|
||||
val identityServerUri: Uri? = null,
|
||||
val antiVirusServerUri: Uri? = null,
|
||||
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(),
|
||||
val shouldPin: Boolean = false,
|
||||
@ -48,7 +48,7 @@ data class HomeServerConnectionConfig(
|
||||
class Builder {
|
||||
|
||||
private lateinit var homeServerUri: Uri
|
||||
private lateinit var identityServerUri: Uri
|
||||
private var identityServerUri: Uri? = null
|
||||
private var antiVirusServerUri: Uri? = null
|
||||
private val allowedFingerprints: MutableList<Fingerprint> = ArrayList()
|
||||
private var shouldPin: Boolean = false
|
||||
@ -70,11 +70,11 @@ data class HomeServerConnectionConfig(
|
||||
if (hsUri.scheme != "http" && hsUri.scheme != "https") {
|
||||
throw RuntimeException("Invalid home server URI: " + hsUri)
|
||||
}
|
||||
// remove trailing /
|
||||
homeServerUri = if (hsUri.toString().endsWith("/")) {
|
||||
// ensure trailing /
|
||||
homeServerUri = if (!hsUri.toString().endsWith("/")) {
|
||||
try {
|
||||
val url = hsUri.toString()
|
||||
Uri.parse(url.substring(0, url.length - 1))
|
||||
Uri.parse("$url/")
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||
}
|
||||
@ -96,11 +96,11 @@ data class HomeServerConnectionConfig(
|
||||
if (identityServerUri.scheme != "http" && identityServerUri.scheme != "https") {
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
// remove trailing /
|
||||
if (identityServerUri.toString().endsWith("/")) {
|
||||
// ensure trailing /
|
||||
if (!identityServerUri.toString().endsWith("/")) {
|
||||
try {
|
||||
val url = identityServerUri.toString()
|
||||
this.identityServerUri = Uri.parse(url.substring(0, url.length - 1))
|
||||
this.identityServerUri = Uri.parse("$url/")
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.comparators
|
||||
|
||||
import im.vector.matrix.android.api.interfaces.DatedObject
|
||||
|
||||
object DatedObjectComparators {
|
||||
|
||||
/**
|
||||
* Comparator to sort DatedObjects from the oldest to the latest.
|
||||
*/
|
||||
val ascComparator by lazy {
|
||||
Comparator<DatedObject> { datedObject1, datedObject2 ->
|
||||
(datedObject1.date - datedObject2.date).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator to sort DatedObjects from the latest to the oldest.
|
||||
*/
|
||||
val descComparator by lazy {
|
||||
Comparator<DatedObject> { datedObject1, datedObject2 ->
|
||||
(datedObject2.date - datedObject1.date).toInt()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2018 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 im.vector.matrix.android.api.comparators.DatedObjectComparators
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import java.util.Collections
|
||||
|
||||
/* ==========================================================================================
|
||||
* MXDeviceInfo
|
||||
* ========================================================================================== */
|
||||
|
||||
fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||
?.chunked(4)
|
||||
?.joinToString(separator = " ")
|
||||
|
||||
|
||||
fun List<DeviceInfo>.sortByLastSeen() {
|
||||
Collections.sort(this, DatedObjectComparators.descComparator)
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
|
||||
package im.vector.matrix.android.api.failure
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
@ -30,7 +32,13 @@ import java.io.IOException
|
||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||
data class ServerError(val error: MatrixError) : Failure(RuntimeException(error.toString()))
|
||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
||||
|
||||
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
||||
|
||||
data class CryptoError(val error: MXCryptoError) : Failure(error)
|
||||
|
||||
abstract class FeatureFailure : Failure()
|
||||
|
||||
|
@ -26,8 +26,13 @@ import com.squareup.moshi.JsonClass
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MatrixError(
|
||||
@Json(name = "errcode") val code: String,
|
||||
@Json(name = "error") val message: String
|
||||
) {
|
||||
@Json(name = "error") val message: String,
|
||||
|
||||
@Json(name = "consent_uri") val consentUri: String? = null,
|
||||
// RESOURCE_LIMIT_EXCEEDED data
|
||||
@Json(name = "limit_type") val limitType: String? = null,
|
||||
@Json(name = "admin_contact") val adminUri: String? = null) {
|
||||
|
||||
|
||||
companion object {
|
||||
const val FORBIDDEN = "M_FORBIDDEN"
|
||||
@ -54,5 +59,9 @@ data class MatrixError(
|
||||
const val TOO_LARGE = "M_TOO_LARGE"
|
||||
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
|
||||
// Possible value for "limit_type"
|
||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2018 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.interfaces
|
||||
|
||||
/**
|
||||
* Can be implemented by any object containing a timestamp.
|
||||
* This interface can be use to sort such object
|
||||
*/
|
||||
interface DatedObject {
|
||||
val date: Long
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.listeners
|
||||
|
||||
/**
|
||||
* Interface to send a progress info
|
||||
*/
|
||||
interface ProgressListener {
|
||||
/**
|
||||
* @param progress from 0 to total by contract
|
||||
* @param total
|
||||
*/
|
||||
fun onProgress(progress: Int, total: Int)
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.listeners
|
||||
|
||||
/**
|
||||
* Interface to send a progress info
|
||||
*/
|
||||
interface StepProgressListener {
|
||||
|
||||
sealed class Step {
|
||||
data class ComputingKey(val progress: Int, val total: Int) : Step()
|
||||
object DownloadingKey : Step()
|
||||
data class ImportingKey(val progress: Int, val total: Int) : Step()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param step The current step, containing progress data if available. Else you should consider progress as indeterminate
|
||||
*/
|
||||
fun onStepProgress(step: Step)
|
||||
}
|
@ -17,9 +17,6 @@
|
||||
package im.vector.matrix.android.api.permalinks
|
||||
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.widget.TextView
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
|
||||
/**
|
||||
@ -40,15 +37,13 @@ object MatrixLinkify {
|
||||
}
|
||||
val text = spannable.toString()
|
||||
var hasMatch = false
|
||||
for (index in MatrixPatterns.MATRIX_PATTERNS.indices) {
|
||||
val pattern = MatrixPatterns.MATRIX_PATTERNS[index]
|
||||
val matcher = pattern.matcher(spannable)
|
||||
while (matcher.find()) {
|
||||
for (pattern in MatrixPatterns.MATRIX_PATTERNS) {
|
||||
for (match in pattern.findAll(spannable)) {
|
||||
hasMatch = true
|
||||
val startPos = matcher.start(0)
|
||||
val startPos = match.range.first
|
||||
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||
val endPos = matcher.end(0)
|
||||
val url = text.substring(matcher.start(0), matcher.end(0))
|
||||
val endPos = match.range.last + 1
|
||||
val url = text.substring(match.range)
|
||||
val span = MatrixPermalinkSpan(url, callback)
|
||||
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
@ -57,36 +52,4 @@ object MatrixLinkify {
|
||||
return hasMatch
|
||||
}
|
||||
|
||||
fun addLinks(textView: TextView, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||
val text = textView.text
|
||||
if (text is Spannable) {
|
||||
if (addLinks(text, callback)) {
|
||||
addLinkMovementMethod(textView)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
} else {
|
||||
val spannableString = SpannableString.valueOf(text)
|
||||
if (addLinks(spannableString, callback)) {
|
||||
addLinkMovementMethod(textView)
|
||||
textView.text = spannableString
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add linkMovementMethod on textview if not already set
|
||||
* @param textView the textView on which the movementMethod is set
|
||||
*/
|
||||
fun addLinkMovementMethod(textView: TextView) {
|
||||
val movementMethod = textView.movementMethod
|
||||
if (movementMethod == null || movementMethod !is LinkMovementMethod) {
|
||||
if (textView.linksClickable) {
|
||||
textView.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.permalinks
|
||||
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan.Callback
|
||||
|
||||
/**
|
||||
* This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back.
|
||||
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||
*/
|
||||
object PermalinkFactory {
|
||||
|
||||
private val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
|
||||
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
|
||||
|
||||
/**
|
||||
* Creates a permalink for an event.
|
||||
|
@ -36,12 +36,20 @@ object PermalinkParser {
|
||||
* Turns an uri to a [PermalinkData]
|
||||
*/
|
||||
fun parse(uri: Uri): PermalinkData {
|
||||
if (!uri.toString().startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) {
|
||||
return PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
|
||||
val fragment = uri.fragment
|
||||
if (fragment.isNullOrEmpty()) {
|
||||
return PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
|
||||
val indexOfQuery = fragment.indexOf("?")
|
||||
val safeFragment = if (indexOfQuery != -1) fragment.substring(0, indexOfQuery) else fragment
|
||||
|
||||
// we are limiting to 2 params
|
||||
val params = fragment
|
||||
val params = safeFragment
|
||||
.split(MatrixPatterns.SEP_REGEX.toRegex())
|
||||
.filter { it.isNotEmpty() }
|
||||
.take(2)
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
class Action(val type: Type) {
|
||||
|
||||
enum class Type(val value: String) {
|
||||
NOTIFY("notify"),
|
||||
DONT_NOTIFY("dont_notify"),
|
||||
COALESCE("coalesce"),
|
||||
SET_TWEAK("set_tweak");
|
||||
|
||||
companion object {
|
||||
|
||||
fun safeValueOf(value: String): Type? {
|
||||
try {
|
||||
return valueOf(value)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tweak_action: String? = null
|
||||
var stringValue: String? = null
|
||||
var boolValue: Boolean? = null
|
||||
|
||||
companion object {
|
||||
fun mapFrom(pushRule: PushRule): List<Action>? {
|
||||
val actions = ArrayList<Action>()
|
||||
pushRule.actions.forEach { actionStrOrObj ->
|
||||
if (actionStrOrObj is String) {
|
||||
when (actionStrOrObj) {
|
||||
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
|
||||
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
|
||||
else -> {
|
||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||
null
|
||||
}
|
||||
}?.let {
|
||||
actions.add(it)
|
||||
}
|
||||
} else if (actionStrOrObj is Map<*, *>) {
|
||||
val tweakAction = actionStrOrObj["set_tweak"] as? String
|
||||
when (tweakAction) {
|
||||
"sound" -> {
|
||||
(actionStrOrObj["value"] as? String)?.let { stringValue ->
|
||||
Action(Action.Type.SET_TWEAK).also {
|
||||
it.tweak_action = "sound"
|
||||
it.stringValue = stringValue
|
||||
actions.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
"highlight" -> {
|
||||
(actionStrOrObj["value"] as? Boolean)?.let { boolValue ->
|
||||
Action(Action.Type.SET_TWEAK).also {
|
||||
it.tweak_action = "highlight"
|
||||
it.boolValue = boolValue
|
||||
actions.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
return if (actions.isEmpty()) null else actions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
abstract class Condition(val kind: Kind) {
|
||||
|
||||
enum class Kind(val value: String) {
|
||||
event_match("event_match"),
|
||||
contains_display_name("contains_display_name"),
|
||||
room_member_count("room_member_count"),
|
||||
sender_notification_permission("sender_notification_permission"),
|
||||
UNRECOGNIZE("");
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromString(value: String): Kind {
|
||||
return when (value) {
|
||||
"event_match" -> event_match
|
||||
"contains_display_name" -> contains_display_name
|
||||
"room_member_count" -> room_member_count
|
||||
"sender_notification_permission" -> sender_notification_permission
|
||||
else -> UNRECOGNIZE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean
|
||||
|
||||
open fun technicalDescription(): String {
|
||||
return "Kind: $kind"
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
/**
|
||||
* Acts like a visitor on Conditions.
|
||||
* This class as all required context needed to evaluate rules
|
||||
*/
|
||||
interface ConditionResolver {
|
||||
|
||||
fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean
|
||||
fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean
|
||||
fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean
|
||||
fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.text.TextUtils
|
||||
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.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import timber.log.Timber
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveContainsDisplayNameCondition(this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
return "User is mentioned"
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, displayName: String): Boolean {
|
||||
var message = when (event.type) {
|
||||
EventType.MESSAGE -> {
|
||||
event.content.toModel<MessageContent>()
|
||||
}
|
||||
//TODO the spec says:
|
||||
// Matches any message whose content is unencrypted and contains the user's current display name
|
||||
// EventType.ENCRYPTED -> {
|
||||
// event.root.getClearContent()?.toModel<MessageContent>()
|
||||
// }
|
||||
else -> null
|
||||
} ?: return false
|
||||
|
||||
return caseInsensitiveFind(displayName, message.body)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Returns whether a string contains an occurrence of another, as a standalone word, regardless of case.
|
||||
*
|
||||
* @param subString the string to search for
|
||||
* @param longString the string to search in
|
||||
* @return whether a match was found
|
||||
*/
|
||||
fun caseInsensitiveFind(subString: String, longString: String): Boolean {
|
||||
// add sanity checks
|
||||
if (TextUtils.isEmpty(subString) || TextUtils.isEmpty(longString)) {
|
||||
return false
|
||||
}
|
||||
|
||||
var res = false
|
||||
|
||||
try {
|
||||
val pattern = Pattern.compile("(\\W|^)" + Pattern.quote(subString) + "(\\W|$)", Pattern.CASE_INSENSITIVE)
|
||||
res = pattern.matcher(longString).find()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## caseInsensitiveFind() : failed")
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import timber.log.Timber
|
||||
|
||||
class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind.event_match) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver) : Boolean {
|
||||
return conditionResolver.resolveEventMatchCondition(this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
return "'$key' Matches '$pattern'"
|
||||
}
|
||||
|
||||
|
||||
fun isSatisfied(event: Event): Boolean {
|
||||
//TODO encrypted events?
|
||||
val rawJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJsonValue(event) as? Map<*, *>
|
||||
?: return false
|
||||
val value = extractField(rawJson, key) ?: return false
|
||||
|
||||
//Patterns with no special glob characters should be treated as having asterisks prepended
|
||||
// and appended when testing the condition.
|
||||
try {
|
||||
val modPattern = if (hasSpecialGlobChar(pattern)) simpleGlobToRegExp(pattern) else simpleGlobToRegExp("*$pattern*")
|
||||
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL)
|
||||
return regex.containsMatchIn(value)
|
||||
} catch (e: Throwable) {
|
||||
//e.g PatternSyntaxException
|
||||
Timber.e(e, "Failed to evaluate push condition")
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun extractField(jsonObject: Map<*, *>, fieldPath: String): String? {
|
||||
val fieldParts = fieldPath.split(".")
|
||||
if (fieldParts.isEmpty()) return null
|
||||
|
||||
var jsonElement: Map<*, *> = jsonObject
|
||||
fieldParts.forEachIndexed { index, pathSegment ->
|
||||
if (index == fieldParts.lastIndex) {
|
||||
return jsonElement[pathSegment]?.toString()
|
||||
} else {
|
||||
val sub = jsonElement[pathSegment] ?: return null
|
||||
if (sub is Map<*, *>) {
|
||||
jsonElement = sub
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun hasSpecialGlobChar(glob: String): Boolean {
|
||||
return glob.contains("*") || glob.contains("?")
|
||||
}
|
||||
|
||||
//Very simple glob to regexp converter
|
||||
private fun simpleGlobToRegExp(glob: String): String {
|
||||
var out = ""//"^"
|
||||
for (i in 0 until glob.length) {
|
||||
val c = glob[i]
|
||||
when (c) {
|
||||
'*' -> out += ".*"
|
||||
'?' -> out += '.'.toString()
|
||||
'.' -> out += "\\."
|
||||
'\\' -> out += "\\\\"
|
||||
else -> out += c
|
||||
}
|
||||
}
|
||||
out += ""//'$'.toString()
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface PushRuleService {
|
||||
|
||||
/**
|
||||
* Fetch the push rules from the server
|
||||
*/
|
||||
fun fetchPushRules(scope: String = "global")
|
||||
|
||||
//TODO get push rule set
|
||||
fun getPushRules(scope: String = "global"): List<PushRule>
|
||||
|
||||
//TODO update rule
|
||||
|
||||
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun addPushRuleListener(listener: PushRuleListener)
|
||||
|
||||
fun removePushRuleListener(listener: PushRuleListener)
|
||||
|
||||
// fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?
|
||||
|
||||
interface PushRuleListener {
|
||||
fun onMatchRule(event: Event, actions: List<Action>)
|
||||
fun onRoomLeft(roomId: String)
|
||||
fun batchFinish()
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import timber.log.Timber
|
||||
|
||||
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
|
||||
class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveRoomMemberCountCondition(this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
return "Room member count is $iz"
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
||||
// sanity check^
|
||||
val roomId = event.roomId ?: return false
|
||||
val room = session?.getRoom(roomId) ?: return false
|
||||
|
||||
// Parse the is field into prefix and number the first time
|
||||
val (prefix, count) = parseIsField() ?: return false
|
||||
|
||||
val numMembers = room.getNumberOfJoinedMembers()
|
||||
|
||||
return when (prefix) {
|
||||
"<" -> numMembers < count
|
||||
">" -> numMembers > count
|
||||
"<=" -> numMembers <= count
|
||||
">=" -> numMembers >= count
|
||||
else -> numMembers == count
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the is field to extract meaningful information.
|
||||
*/
|
||||
private fun parseIsField(): Pair<String?, Int>? {
|
||||
try {
|
||||
val match = regex.find(iz) ?: return null
|
||||
val (prefix, count) = match.destructured
|
||||
return prefix to count.toInt()
|
||||
} catch (t: Throwable) {
|
||||
Timber.d(t)
|
||||
}
|
||||
return null
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Known rule ids
|
||||
*
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#predefined-rules
|
||||
*/
|
||||
object RuleIds {
|
||||
// Default Override Rules
|
||||
const val RULE_ID_DISABLE_ALL = ".m.rule.master"
|
||||
const val RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS = ".m.rule.suppress_notices"
|
||||
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_CONTAIN_DISPLAY_NAME = ".m.rule.contains_display_name"
|
||||
|
||||
const val RULE_ID_TOMBSTONE = ".m.rule.tombstone"
|
||||
const val RULE_ID_ROOM_NOTIF = ".m.rule.roomnotif"
|
||||
|
||||
// Default Content Rules
|
||||
const val RULE_ID_CONTAIN_USER_NAME = ".m.rule.contains_user_name"
|
||||
|
||||
// Default Underride Rules
|
||||
const val RULE_ID_CALL = ".m.rule.call"
|
||||
const val RULE_ID_one_to_one_encrypted_room = ".m.rule.encrypted_room_one_to_one"
|
||||
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
|
||||
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
|
||||
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"
|
||||
|
||||
// Not documented
|
||||
const val RULE_ID_FALLBACK = ".m.rule.fallback"
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
|
||||
enum class RulesetKey(val value: String) {
|
||||
CONTENT("content"),
|
||||
OVERRIDE("override"),
|
||||
ROOM("room"),
|
||||
SENDER("sender"),
|
||||
UNDERRIDE("underride"),
|
||||
UNKNOWN("")
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevels
|
||||
|
||||
|
||||
class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveSenderNotificationPermissionCondition(this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
return "User power level <$key>"
|
||||
}
|
||||
|
||||
|
||||
fun isSatisfied(event: Event, powerLevels: PowerLevels): Boolean {
|
||||
return event.senderId != null && powerLevels.getUserPowerLevel(event.senderId) >= powerLevels.notificationLevel(key)
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* All push rulesets for a user.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetPushRulesResponse(
|
||||
/**
|
||||
* Global rules, account level applying to all devices
|
||||
*/
|
||||
@Json(name = "global")
|
||||
val global: Ruleset,
|
||||
|
||||
/**
|
||||
* Device specific rules, apply only to current device
|
||||
*/
|
||||
@Json(name = "device")
|
||||
val device: Ruleset? = null
|
||||
)
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.*
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PushCondition(
|
||||
/**
|
||||
* Required. The kind of condition to apply.
|
||||
*/
|
||||
val kind: String,
|
||||
|
||||
/**
|
||||
* Required for event_match conditions. The dot- separated field of the event to match.
|
||||
*/
|
||||
|
||||
val key: String? = null,
|
||||
/**
|
||||
*Required for event_match conditions.
|
||||
*/
|
||||
|
||||
val pattern: String? = null,
|
||||
/**
|
||||
* Required for room_member_count conditions.
|
||||
* A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
|
||||
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth.
|
||||
* If no prefix is present, this parameter defaults to ==.
|
||||
*/
|
||||
@Json(name = "is") val iz: String? = null
|
||||
) {
|
||||
|
||||
fun asExecutableCondition(): Condition? {
|
||||
return when (Condition.Kind.fromString(this.kind)) {
|
||||
Condition.Kind.event_match -> {
|
||||
if (this.key != null && this.pattern != null) {
|
||||
EventMatchCondition(key, pattern)
|
||||
} else {
|
||||
Timber.e("Malformed Event match condition")
|
||||
null
|
||||
}
|
||||
}
|
||||
Condition.Kind.contains_display_name -> {
|
||||
ContainsDisplayNameCondition()
|
||||
}
|
||||
Condition.Kind.room_member_count -> {
|
||||
if (this.iz.isNullOrBlank()) {
|
||||
Timber.e("Malformed ROOM_MEMBER_COUNT condition")
|
||||
null
|
||||
} else {
|
||||
RoomMemberCountCondition(this.iz)
|
||||
}
|
||||
}
|
||||
Condition.Kind.sender_notification_permission -> {
|
||||
this.key?.let { SenderNotificationPermissionCondition(it) }
|
||||
}
|
||||
Condition.Kind.UNRECOGNIZE -> {
|
||||
Timber.e("Unknwon kind $kind")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PushRule(
|
||||
/**
|
||||
* Required. The actions to perform when this rule is matched.
|
||||
*/
|
||||
val actions: List<Any>,
|
||||
/**
|
||||
* Required. Whether this is a default rule, or has been set explicitly.
|
||||
*/
|
||||
val default: Boolean? = false,
|
||||
/**
|
||||
* Required. Whether the push rule is enabled or not.
|
||||
*/
|
||||
val enabled: Boolean,
|
||||
/**
|
||||
* Required. The ID of this rule.
|
||||
*/
|
||||
@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
|
||||
*/
|
||||
val conditions: List<PushCondition>? = null,
|
||||
/**
|
||||
* The glob-style pattern to match against. Only applicable to content rules.
|
||||
*/
|
||||
val pattern: String? = null
|
||||
)
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Ruleset(
|
||||
val content: List<PushRule>? = null,
|
||||
val override: List<PushRule>? = null,
|
||||
val room: List<PushRule>? = null,
|
||||
val sender: List<PushRule>? = null,
|
||||
val underride: List<PushRule>? = null
|
||||
)
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.LiveData
|
||||
|
||||
interface InitialSyncProgressService {
|
||||
|
||||
fun getInitialSyncProgressStatus() : LiveData<Status?>
|
||||
|
||||
data class Status(
|
||||
@StringRes val statusText: Int,
|
||||
val percentProgress: Int = 0
|
||||
)
|
||||
}
|
@ -17,15 +17,21 @@
|
||||
package im.vector.matrix.android.api.session
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.pushrules.PushRuleService
|
||||
import im.vector.matrix.android.api.session.cache.CacheService
|
||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
|
||||
/**
|
||||
@ -34,40 +40,69 @@ import im.vector.matrix.android.api.session.user.UserService
|
||||
*/
|
||||
interface Session :
|
||||
RoomService,
|
||||
RoomDirectoryService,
|
||||
GroupService,
|
||||
UserService,
|
||||
CryptoService,
|
||||
CacheService,
|
||||
SignOutService,
|
||||
FilterService {
|
||||
FilterService,
|
||||
FileService,
|
||||
PushRuleService,
|
||||
PushersService,
|
||||
InitialSyncProgressService {
|
||||
|
||||
/**
|
||||
* The params associated to the session
|
||||
*/
|
||||
val sessionParams: SessionParams
|
||||
|
||||
/**
|
||||
* Useful shortcut to get access to the userId
|
||||
*/
|
||||
val myUserId: String
|
||||
get() = sessionParams.credentials.userId
|
||||
|
||||
|
||||
/**
|
||||
* This method allow to open a session. It does start some service on the background.
|
||||
*/
|
||||
@MainThread
|
||||
fun open()
|
||||
|
||||
/**
|
||||
* Requires a one time background sync
|
||||
*/
|
||||
fun requireBackgroundSync()
|
||||
|
||||
/**
|
||||
* Launches infinite periodic background syncs
|
||||
* THis does not work in doze mode :/
|
||||
* If battery optimization is on it can work in app standby but that's all :/
|
||||
*/
|
||||
fun startAutomaticBackgroundSync(repeatDelay: Long = 30_000L)
|
||||
|
||||
fun stopAnyBackgroundSync()
|
||||
|
||||
/**
|
||||
* This method start the sync thread.
|
||||
*/
|
||||
@MainThread
|
||||
fun startSync()
|
||||
fun startSync(fromForeground : Boolean)
|
||||
|
||||
/**
|
||||
* This method stop the sync thread.
|
||||
*/
|
||||
@MainThread
|
||||
fun stopSync()
|
||||
|
||||
/**
|
||||
* This method allows to listen the sync state.
|
||||
* @return a [LiveData] of [SyncState].
|
||||
*/
|
||||
fun syncState(): LiveData<SyncState>
|
||||
|
||||
/**
|
||||
* This method allow to close a session. It does stop some services.
|
||||
*/
|
||||
@MainThread
|
||||
fun close()
|
||||
|
||||
/**
|
||||
@ -100,6 +135,7 @@ interface Session :
|
||||
* The access token is not valid anymore
|
||||
*/
|
||||
fun onInvalidToken()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -22,22 +22,17 @@ interface ContentUploadStateTracker {
|
||||
|
||||
fun untrack(key: String, updateListener: UpdateListener)
|
||||
|
||||
fun setFailure(key: String)
|
||||
|
||||
fun setSuccess(key: String)
|
||||
|
||||
fun setProgress(key: String, current: Long, total: Long)
|
||||
|
||||
interface UpdateListener {
|
||||
fun onUpdate(state: State)
|
||||
}
|
||||
|
||||
sealed class State {
|
||||
object Idle : State()
|
||||
data class ProgressData(val current: Long, val total: Long) : State()
|
||||
object EncryptingThumbnail : State()
|
||||
data class UploadingThumbnail(val current: Long, val total: Long) : State()
|
||||
object Encrypting : State()
|
||||
data class Uploading(val current: Long, val total: Long) : State()
|
||||
object Success : State()
|
||||
object Failure : State()
|
||||
data class Failure(val throwable: Throwable) : State()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -16,8 +16,103 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
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.sas.SasVerificationService
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
|
||||
interface CryptoService {
|
||||
|
||||
// Not supported for the moment
|
||||
fun isCryptoEnabled() = false
|
||||
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
||||
|
||||
fun isCryptoEnabled(): Boolean
|
||||
|
||||
fun getSasVerificationService(): SasVerificationService
|
||||
|
||||
fun getKeysBackupService(): KeysBackupService
|
||||
|
||||
fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean
|
||||
|
||||
fun setWarnOnUnknownDevices(warn: Boolean)
|
||||
|
||||
fun setDeviceVerification(verificationStatus: Int, deviceId: String, userId: String)
|
||||
|
||||
fun getUserDevices(userId: String): MutableList<MXDeviceInfo>
|
||||
|
||||
fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?)
|
||||
|
||||
fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo?
|
||||
|
||||
fun getMyDevice(): MXDeviceInfo
|
||||
|
||||
fun getGlobalBlacklistUnverifiedDevices(): Boolean
|
||||
|
||||
fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
|
||||
|
||||
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
|
||||
|
||||
fun getDeviceTrackingStatus(userId: String): Int
|
||||
|
||||
fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
|
||||
|
||||
fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>)
|
||||
|
||||
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
||||
|
||||
fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo?
|
||||
|
||||
fun reRequestRoomKeyForEvent(event: Event)
|
||||
|
||||
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
|
||||
|
||||
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener)
|
||||
|
||||
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener)
|
||||
|
||||
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||
|
||||
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
||||
|
||||
fun isRoomEncrypted(roomId: String): Boolean
|
||||
|
||||
fun encryptEventContent(eventContent: Content,
|
||||
eventType: String,
|
||||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
||||
|
||||
@Throws(MXCryptoError::class)
|
||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
|
||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
||||
|
||||
fun getEncryptionAlgorithm(roomId: String): String?
|
||||
|
||||
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
||||
|
||||
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>)
|
||||
|
||||
fun clearCryptoCache(callback: MatrixCallback<Unit>)
|
||||
|
||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||
|
||||
fun removeSessionListener(listener: NewSessionListener)
|
||||
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2016 OpenMarket Ltd
|
||||
* Copyright 2017 Vector Creations Ltd
|
||||
* Copyright 2018 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
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.olm.OlmException
|
||||
|
||||
/**
|
||||
* Represents a crypto error response.
|
||||
*/
|
||||
sealed class MXCryptoError : Throwable() {
|
||||
|
||||
data class Base(val errorType: ErrorType,
|
||||
val technicalMessage: String,
|
||||
/**
|
||||
* Describe the error with more details
|
||||
*/
|
||||
val detailedErrorDescription: String? = null) : MXCryptoError()
|
||||
|
||||
data class OlmError(val olmException: OlmException) : MXCryptoError()
|
||||
|
||||
data class UnknownDevice(val deviceList: MXUsersDevicesMap<MXDeviceInfo>) : MXCryptoError()
|
||||
|
||||
enum class ErrorType {
|
||||
ENCRYPTING_NOT_ENABLED,
|
||||
UNABLE_TO_ENCRYPT,
|
||||
UNABLE_TO_DECRYPT,
|
||||
UNKNOWN_INBOUND_SESSION_ID,
|
||||
INBOUND_SESSION_MISMATCH_ROOM_ID,
|
||||
MISSING_FIELDS,
|
||||
BAD_EVENT_FORMAT,
|
||||
MISSING_SENDER_KEY,
|
||||
MISSING_CIPHER_TEXT,
|
||||
BAD_DECRYPTED_FORMAT,
|
||||
NOT_INCLUDE_IN_RECIPIENTS,
|
||||
BAD_RECIPIENT,
|
||||
BAD_RECIPIENT_KEY,
|
||||
FORWARDED_MESSAGE,
|
||||
BAD_ROOM,
|
||||
BAD_ENCRYPTED_MESSAGE,
|
||||
DUPLICATED_MESSAGE_INDEX,
|
||||
MISSING_PROPERTY,
|
||||
OLM,
|
||||
UNKNOWN_DEVICES,
|
||||
UNKNOWN_MESSAGE_INDEX
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Resource for technicalMessage
|
||||
*/
|
||||
const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s"
|
||||
const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s"
|
||||
const val OLM_REASON = "OLM error: %1\$s"
|
||||
const val DETAILED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s"
|
||||
const val UNKNOWN_INBOUND_SESSION_ID_REASON = "Unknown inbound session id"
|
||||
const val INBOUND_SESSION_MISMATCH_ROOM_ID_REASON = "Mismatched room_id for inbound group session (expected %1\$s, was %2\$s)"
|
||||
const val MISSING_FIELDS_REASON = "Missing fields in input"
|
||||
const val BAD_EVENT_FORMAT_TEXT_REASON = "Bad event format"
|
||||
const val MISSING_SENDER_KEY_TEXT_REASON = "Missing senderKey"
|
||||
const val MISSING_CIPHER_TEXT_REASON = "Missing ciphertext"
|
||||
const val BAD_DECRYPTED_FORMAT_TEXT_REASON = "Bad decrypted event format"
|
||||
const val NOT_INCLUDED_IN_RECIPIENT_REASON = "Not included in recipients"
|
||||
const val BAD_RECIPIENT_REASON = "Message was intended for %1\$s"
|
||||
const val BAD_RECIPIENT_KEY_REASON = "Message not intended for this device"
|
||||
const val FORWARDED_MESSAGE_REASON = "Message forwarded from %1\$s"
|
||||
const val BAD_ROOM_REASON = "Message intended for room %1\$s"
|
||||
const val BAD_ENCRYPTED_MESSAGE_REASON = "Bad Encrypted Message"
|
||||
const val DUPLICATE_MESSAGE_INDEX_REASON = "Duplicate message index, possible replay attack %1\$s"
|
||||
const val ERROR_MISSING_PROPERTY_REASON = "No '%1\$s' property. Cannot prevent unknown-key attack"
|
||||
const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" +
|
||||
"We strongly recommend you verify them before continuing."
|
||||
const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." +
|
||||
" Perhaps the homeserver is hiding the configuration event."
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.listeners.StepProgressListener
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
|
||||
interface KeysBackupService {
|
||||
/**
|
||||
* Retrieve the current version of the backup from the home server
|
||||
*
|
||||
* It can be different than keysBackupVersion.
|
||||
* @param callback onSuccess(null) will be called if there is no backup on the server
|
||||
*/
|
||||
fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>)
|
||||
|
||||
/**
|
||||
* Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion].
|
||||
*
|
||||
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
|
||||
* @param callback Asynchronous callback
|
||||
*/
|
||||
fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
callback: MatrixCallback<KeysVersion>)
|
||||
|
||||
/**
|
||||
* Facility method to get the total number of locally stored keys
|
||||
*/
|
||||
fun getTotalNumbersOfKeys(): Int
|
||||
|
||||
/**
|
||||
* Facility method to get the number of backed up keys
|
||||
*/
|
||||
fun getTotalNumbersOfBackedUpKeys(): Int
|
||||
|
||||
/**
|
||||
* Start to back up keys immediately.
|
||||
*
|
||||
* @param progressListener the callback to follow the progress
|
||||
* @param callback the main callback
|
||||
*/
|
||||
fun backupAllGroupSessions(progressListener: ProgressListener?,
|
||||
callback: MatrixCallback<Unit>?)
|
||||
|
||||
/**
|
||||
* Check trust on a key backup version.
|
||||
*
|
||||
* @param keysBackupVersion the backup version to check.
|
||||
* @param callback block called when the operations completes.
|
||||
*/
|
||||
fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult,
|
||||
callback: MatrixCallback<KeysBackupVersionTrust>)
|
||||
|
||||
/**
|
||||
* Return the current progress of the backup
|
||||
*/
|
||||
fun getBackupProgress(progressListener: ProgressListener)
|
||||
|
||||
/**
|
||||
* Get information about a backup version defined on the homeserver.
|
||||
*
|
||||
* It can be different than keysBackupVersion.
|
||||
* @param version the backup version
|
||||
* @param callback
|
||||
*/
|
||||
fun getVersion(version: String,
|
||||
callback: MatrixCallback<KeysVersionResult?>)
|
||||
|
||||
/**
|
||||
* This method fetches the last backup version on the server, then compare to the currently backup version use.
|
||||
* If versions are not the same, the current backup is deleted (on server or locally), then the backup may be started again, using the last version.
|
||||
*
|
||||
* @param callback true if backup is already using the last version, and false if it is not the case
|
||||
*/
|
||||
fun forceUsingLastVersion(callback: MatrixCallback<Boolean>)
|
||||
|
||||
/**
|
||||
* Check the server for an active key backup.
|
||||
*
|
||||
* If one is present and has a valid signature from one of the user's verified
|
||||
* devices, start backing up to it.
|
||||
*/
|
||||
fun checkAndStartKeysBackup()
|
||||
|
||||
fun addListener(listener: KeysBackupStateListener)
|
||||
|
||||
fun removeListener(listener: KeysBackupStateListener)
|
||||
|
||||
/**
|
||||
* Set up the data required to create a new backup version.
|
||||
* The backup version will not be created and enabled until [createKeysBackupVersion]
|
||||
* is called.
|
||||
* The returned [MegolmBackupCreationInfo] object has a `recoveryKey` member with
|
||||
* the user-facing recovery key string.
|
||||
*
|
||||
* @param password an optional passphrase string that can be entered by the user
|
||||
* when restoring the backup as an alternative to entering the recovery key.
|
||||
* @param progressListener a progress listener, as generating private key from password may take a while
|
||||
* @param callback Asynchronous callback
|
||||
*/
|
||||
fun prepareKeysBackupVersion(password: String?,
|
||||
progressListener: ProgressListener?,
|
||||
callback: MatrixCallback<MegolmBackupCreationInfo>)
|
||||
|
||||
/**
|
||||
* Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself.
|
||||
* If we are backing up to this version. Backup will be stopped.
|
||||
*
|
||||
* @param version the backup version to delete.
|
||||
* @param callback Asynchronous callback
|
||||
*/
|
||||
fun deleteBackup(version: String,
|
||||
callback: MatrixCallback<Unit>?)
|
||||
|
||||
/**
|
||||
* Ask if the backup on the server contains keys that we may do not have locally.
|
||||
* This should be called when entering in the state READY_TO_BACKUP
|
||||
*/
|
||||
fun canRestoreKeys(): Boolean
|
||||
|
||||
/**
|
||||
* Set trust on a keys backup version.
|
||||
* It adds (or removes) the signature of the current device to the authentication part of the keys backup version.
|
||||
*
|
||||
* @param keysBackupVersion the backup version to check.
|
||||
* @param trust the trust to set to the keys backup.
|
||||
* @param callback block called when the operations completes.
|
||||
*/
|
||||
fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult,
|
||||
trust: Boolean,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Set trust on a keys backup version.
|
||||
*
|
||||
* @param keysBackupVersion the backup version to check.
|
||||
* @param recoveryKey the recovery key to challenge with the key backup public key.
|
||||
* @param callback block called when the operations completes.
|
||||
*/
|
||||
fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
||||
recoveryKey: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Set trust on a keys backup version.
|
||||
*
|
||||
* @param keysBackupVersion the backup version to check.
|
||||
* @param password the pass phrase to challenge with the keyBackupVersion public key.
|
||||
* @param callback block called when the operations completes.
|
||||
*/
|
||||
fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
||||
password: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Restore a backup with a recovery key from a given backup version stored on the homeserver.
|
||||
*
|
||||
* @param keysVersionResult the backup version to restore from.
|
||||
* @param recoveryKey the recovery key to decrypt the retrieved backup.
|
||||
* @param roomId the id of the room to get backup data from.
|
||||
* @param sessionId the id of the session to restore.
|
||||
* @param stepProgressListener the step progress listener
|
||||
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
|
||||
*/
|
||||
fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
||||
recoveryKey: String, roomId: String?,
|
||||
sessionId: String?,
|
||||
stepProgressListener: StepProgressListener?,
|
||||
callback: MatrixCallback<ImportRoomKeysResult>)
|
||||
|
||||
/**
|
||||
* Restore a backup with a password from a given backup version stored on the homeserver.
|
||||
*
|
||||
* @param keysBackupVersion the backup version to restore from.
|
||||
* @param password the password to decrypt the retrieved backup.
|
||||
* @param roomId the id of the room to get backup data from.
|
||||
* @param sessionId the id of the session to restore.
|
||||
* @param stepProgressListener the step progress listener
|
||||
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
|
||||
*/
|
||||
fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
||||
password: String,
|
||||
roomId: String?,
|
||||
sessionId: String?,
|
||||
stepProgressListener: StepProgressListener?,
|
||||
callback: MatrixCallback<ImportRoomKeysResult>)
|
||||
|
||||
val keysBackupVersion: KeysVersionResult?
|
||||
val currentBackupVersion: String?
|
||||
val isEnabled: Boolean
|
||||
val isStucked: Boolean
|
||||
val state: KeysBackupState
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.keysbackup
|
||||
|
||||
/**
|
||||
* E2e keys backup states.
|
||||
*
|
||||
* <pre>
|
||||
* |
|
||||
* V deleteKeyBackupVersion (on current backup)
|
||||
* +----------------------> UNKNOWN <-------------
|
||||
* | |
|
||||
* | | checkAndStartKeysBackup (at startup or on new verified device or a new detected backup)
|
||||
* | V
|
||||
* | CHECKING BACKUP
|
||||
* | |
|
||||
* | Network error |
|
||||
* +<----------+----------------+-------> DISABLED <----------------------+
|
||||
* | | | | |
|
||||
* | | | | createKeysBackupVersion |
|
||||
* | V | V |
|
||||
* +<--- WRONG VERSION | ENABLING |
|
||||
* | ^ | | |
|
||||
* | | V ok | error |
|
||||
* | | +------> READY <--------+----------------------------+
|
||||
* V | | |
|
||||
* NOT TRUSTED | | | on new key
|
||||
* | | V
|
||||
* | | WILL BACK UP (waiting a random duration)
|
||||
* | | |
|
||||
* | | |
|
||||
* | | ok V
|
||||
* | +----- BACKING UP
|
||||
* | |
|
||||
* | Error |
|
||||
* +<---------------+
|
||||
* </pre>
|
||||
*/
|
||||
enum class KeysBackupState {
|
||||
// Need to check the current backup version on the homeserver
|
||||
Unknown,
|
||||
// Checking if backup is enabled on home server
|
||||
CheckingBackUpOnHomeserver,
|
||||
// Backup has been stopped because a new backup version has been detected on the homeserver
|
||||
WrongBackUpVersion,
|
||||
// Backup from this device is not enabled
|
||||
Disabled,
|
||||
// There is a backup available on the homeserver but it is not trusted.
|
||||
// It is not trusted because the signature is invalid or the device that created it is not verified
|
||||
// Use [KeysBackup.getKeysBackupTrust()] to get trust details.
|
||||
// Consequently, the backup from this device is not enabled.
|
||||
NotTrusted,
|
||||
// Backup is being enabled: the backup version is being created on the homeserver
|
||||
Enabling,
|
||||
// Backup is enabled and ready to send backup to the homeserver
|
||||
ReadyToBackUp,
|
||||
// e2e keys are going to be sent to the homeserver
|
||||
WillBackUp,
|
||||
// e2e keys are being sent to the homeserver
|
||||
BackingUp
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.keysbackup
|
||||
|
||||
interface KeysBackupStateListener {
|
||||
|
||||
/**
|
||||
* The keys backup state has changed
|
||||
* @param newState the new state
|
||||
*/
|
||||
fun onStateChange(newState: KeysBackupState)
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2018 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.keyshare
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation
|
||||
|
||||
/**
|
||||
* Room keys events listener
|
||||
*/
|
||||
interface RoomKeysRequestListener {
|
||||
/**
|
||||
* An room key request has been received.
|
||||
*
|
||||
* @param request the request
|
||||
*/
|
||||
fun onRoomKeyRequest(request: IncomingRoomKeyRequest)
|
||||
|
||||
/**
|
||||
* A room key request cancellation has been received.
|
||||
*
|
||||
* @param request the cancellation request
|
||||
*/
|
||||
fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation)
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
enum class CancelCode(val value: String, val humanReadable: String) {
|
||||
User("m.user", "the user cancelled the verification"),
|
||||
Timeout("m.timeout", "the verification process timed out"),
|
||||
UnknownTransaction("m.unknown_transaction", "the device does not know about that transaction"),
|
||||
UnknownMethod("m.unknown_method", "the device can’t agree on a key agreement, hash, MAC, or SAS method"),
|
||||
MismatchedCommitment("m.mismatched_commitment", "the hash commitment did not match"),
|
||||
MismatchedSas("m.mismatched_sas", "the SAS did not match"),
|
||||
UnexpectedMessage("m.unexpected_message", "the device received an unexpected message"),
|
||||
InvalidMessage("m.invalid_message", "an invalid message was received"),
|
||||
MismatchedKeys("m.key_mismatch", "Key mismatch"),
|
||||
UserMismatchError("m.user_error", "User mismatch")
|
||||
}
|
||||
|
||||
fun safeValueOf(code: String?): CancelCode {
|
||||
return CancelCode.values().firstOrNull { code == it.value } ?: CancelCode.User
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
data class EmojiRepresentation(val emoji: String,
|
||||
@StringRes val nameResId: Int)
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
interface IncomingSasVerificationTransaction {
|
||||
val uxState: UxState
|
||||
|
||||
fun performAccept()
|
||||
|
||||
enum class UxState {
|
||||
UNKNOWN,
|
||||
SHOW_ACCEPT,
|
||||
WAIT_FOR_KEY_AGREEMENT,
|
||||
SHOW_SAS,
|
||||
WAIT_FOR_VERIFICATION,
|
||||
VERIFIED,
|
||||
CANCELLED_BY_ME,
|
||||
CANCELLED_BY_OTHER
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
object SasMode {
|
||||
const val DECIMAL = "decimal"
|
||||
const val EMOJI = "emoji"
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
interface OutgoingSasVerificationRequest {
|
||||
val uxState: UxState
|
||||
|
||||
enum class UxState {
|
||||
UNKNOWN,
|
||||
WAIT_FOR_START,
|
||||
WAIT_FOR_KEY_AGREEMENT,
|
||||
SHOW_SAS,
|
||||
WAIT_FOR_VERIFICATION,
|
||||
VERIFIED,
|
||||
CANCELLED_BY_ME,
|
||||
CANCELLED_BY_OTHER
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
interface SasVerificationService {
|
||||
fun addListener(listener: SasVerificationListener)
|
||||
|
||||
fun removeListener(listener: SasVerificationListener)
|
||||
|
||||
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
||||
|
||||
fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction?
|
||||
|
||||
fun beginKeyVerificationSAS(userId: String, deviceID: String): String?
|
||||
|
||||
fun beginKeyVerification(method: String, userId: String, deviceID: String): String?
|
||||
|
||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
||||
|
||||
interface SasVerificationListener {
|
||||
fun transactionCreated(tx: SasVerificationTransaction)
|
||||
fun transactionUpdated(tx: SasVerificationTransaction)
|
||||
fun markedAsManuallyVerified(userId: String, deviceId: String)
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
interface SasVerificationTransaction {
|
||||
val state: SasVerificationTxState
|
||||
|
||||
val cancelledReason: CancelCode?
|
||||
|
||||
val transactionId: String
|
||||
|
||||
val otherUserId: String
|
||||
|
||||
var otherDeviceId: String?
|
||||
|
||||
val isIncoming: Boolean
|
||||
|
||||
fun supportsEmoji(): Boolean
|
||||
|
||||
fun supportsDecimal(): Boolean
|
||||
|
||||
fun getEmojiCodeRepresentation(): List<EmojiRepresentation>
|
||||
|
||||
fun getDecimalCodeRepresentation(): String
|
||||
|
||||
/**
|
||||
* User wants to cancel the transaction
|
||||
*/
|
||||
fun cancel()
|
||||
|
||||
/**
|
||||
* To be called by the client when the user has verified that
|
||||
* both short codes do match
|
||||
*/
|
||||
fun userHasVerifiedShortCode()
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
enum class SasVerificationTxState {
|
||||
None,
|
||||
// I have started a verification request
|
||||
SendingStart,
|
||||
Started,
|
||||
// Other user/device sent me a request
|
||||
OnStarted,
|
||||
// I have accepted a request started by the other user/device
|
||||
SendingAccept,
|
||||
Accepted,
|
||||
// My request has been accepted by the other user/device
|
||||
OnAccepted,
|
||||
// I have sent my public key
|
||||
SendingKey,
|
||||
KeySent,
|
||||
// The other user/device has sent me his public key
|
||||
OnKeyReceived,
|
||||
// Short code is ready to be displayed
|
||||
ShortCodeReady,
|
||||
// I have compared the code and manually said that they match
|
||||
ShortCodeAccepted,
|
||||
|
||||
SendingMac,
|
||||
MacSent,
|
||||
Verifying,
|
||||
Verified,
|
||||
|
||||
//Global: The verification has been cancelled (by me or other), see cancelReason for details
|
||||
Cancelled,
|
||||
OnCancelled
|
||||
}
|
@ -16,22 +16,38 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.events.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Types
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
|
||||
typealias Content = Map<String, @JvmSuppressWildcards Any>
|
||||
typealias Content = JsonDict
|
||||
|
||||
/**
|
||||
* This methods is a facility method to map a json content to a model.
|
||||
*/
|
||||
inline fun <reified T> Content?.toModel(): T? {
|
||||
inline fun <reified T> Content?.toModel(catchError: Boolean = true): T? {
|
||||
return this?.let {
|
||||
val moshi = MoshiProvider.providesMoshi()
|
||||
val moshiAdapter = moshi.adapter(T::class.java)
|
||||
return moshiAdapter.fromJsonValue(it)
|
||||
return try {
|
||||
moshiAdapter.fromJsonValue(it)
|
||||
} catch (e: Exception) {
|
||||
if (catchError) {
|
||||
Timber.e(e, "To model failed : $e")
|
||||
null
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,23 +74,144 @@ data class Event(
|
||||
@Json(name = "content") val content: Content? = null,
|
||||
@Json(name = "prev_content") val prevContent: Content? = null,
|
||||
@Json(name = "origin_server_ts") val originServerTs: Long? = null,
|
||||
@Json(name = "sender") val sender: String? = null,
|
||||
@Json(name = "sender") val senderId: String? = null,
|
||||
@Json(name = "state_key") val stateKey: String? = null,
|
||||
@Json(name = "room_id") val roomId: String? = null,
|
||||
@Json(name = "unsigned") val unsignedData: UnsignedData? = null,
|
||||
@Json(name = "redacts") val redacts: String? = null
|
||||
|
||||
) {
|
||||
|
||||
|
||||
@Transient
|
||||
var mxDecryptionResult: OlmDecryptionResult? = null
|
||||
|
||||
@Transient
|
||||
var mCryptoError: MXCryptoError.ErrorType? = null
|
||||
|
||||
@Transient
|
||||
var sendState: SendState = SendState.UNKNOWN
|
||||
|
||||
|
||||
/**
|
||||
* Check if event is a state event.
|
||||
* @return true if event is state event.
|
||||
*/
|
||||
fun isStateEvent(): Boolean {
|
||||
return EventType.isStateEvent(type)
|
||||
return EventType.isStateEvent(getClearType())
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val CONTENT_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)
|
||||
//==============================================================================================================
|
||||
// Crypto
|
||||
//==============================================================================================================
|
||||
|
||||
/**
|
||||
* @return true if this event is encrypted.
|
||||
*/
|
||||
fun isEncrypted(): Boolean {
|
||||
return TextUtils.equals(type, EventType.ENCRYPTED)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The curve25519 key that sent this event.
|
||||
*/
|
||||
fun getSenderKey(): String? {
|
||||
return mxDecryptionResult?.senderKey
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The additional keys the sender of this encrypted event claims to possess.
|
||||
*/
|
||||
fun getKeysClaimed(): Map<String, String> {
|
||||
return mxDecryptionResult?.keysClaimed ?: HashMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the event type
|
||||
*/
|
||||
fun getClearType(): String {
|
||||
return mxDecryptionResult?.payload?.get("type")?.toString() ?: type
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the event content
|
||||
*/
|
||||
fun getClearContent(): Content? {
|
||||
return mxDecryptionResult?.payload?.get("content") as? Content ?: content
|
||||
}
|
||||
|
||||
fun toContentStringWithIndent(): String {
|
||||
val contentMap = toContent()?.toMutableMap() ?: HashMap()
|
||||
return JSONObject(contentMap).toString(4)
|
||||
}
|
||||
|
||||
fun toClearContentStringWithIndent(): String? {
|
||||
val contentMap = this.mxDecryptionResult?.payload?.toMutableMap()
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the event is redacted
|
||||
*/
|
||||
fun isRedacted() = unsignedData?.redactedEvent != null
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Event
|
||||
|
||||
if (type != other.type) return false
|
||||
if (eventId != other.eventId) return false
|
||||
if (content != other.content) return false
|
||||
if (prevContent != other.prevContent) return false
|
||||
if (originServerTs != other.originServerTs) return false
|
||||
if (senderId != other.senderId) return false
|
||||
if (stateKey != other.stateKey) return false
|
||||
if (roomId != other.roomId) return false
|
||||
if (unsignedData != other.unsignedData) return false
|
||||
if (redacts != other.redacts) return false
|
||||
if (mxDecryptionResult != other.mxDecryptionResult) return false
|
||||
if (mCryptoError != other.mCryptoError) return false
|
||||
if (sendState != other.sendState) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = type.hashCode()
|
||||
result = 31 * result + (eventId?.hashCode() ?: 0)
|
||||
result = 31 * result + (content?.hashCode() ?: 0)
|
||||
result = 31 * result + (prevContent?.hashCode() ?: 0)
|
||||
result = 31 * result + (originServerTs?.hashCode() ?: 0)
|
||||
result = 31 * result + (senderId?.hashCode() ?: 0)
|
||||
result = 31 * result + (stateKey?.hashCode() ?: 0)
|
||||
result = 31 * result + (roomId?.hashCode() ?: 0)
|
||||
result = 31 * result + (unsignedData?.hashCode() ?: 0)
|
||||
result = 31 * result + (redacts?.hashCode() ?: 0)
|
||||
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
|
||||
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
|
||||
result = 31 * result + sendState.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun Event.isTextMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||
MessageType.MSGTYPE_TEXT,
|
||||
MessageType.MSGTYPE_EMOTE,
|
||||
MessageType.MSGTYPE_NOTICE -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.isImageMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.type) {
|
||||
MessageType.MSGTYPE_IMAGE -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
@ -36,8 +36,6 @@ object EventType {
|
||||
const val FULLY_READ = "m.fully_read"
|
||||
const val PLUMBING = "m.room.plumbing"
|
||||
const val BOT_OPTIONS = "m.room.bot.options"
|
||||
const val KEY_REQUEST = "m.room_key_request"
|
||||
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
|
||||
const val PREVIEW_URLS = "org.matrix.room.preview_urls"
|
||||
|
||||
// State Events
|
||||
@ -65,11 +63,20 @@ object EventType {
|
||||
const val CALL_ANSWER = "m.call.answer"
|
||||
const val CALL_HANGUP = "m.call.hangup"
|
||||
|
||||
// Key share events
|
||||
const val ROOM_KEY_REQUEST = "m.room_key_request"
|
||||
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
|
||||
|
||||
// Interactive key verification
|
||||
const val KEY_VERIFICATION_START = "m.key.verification.start"
|
||||
const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept"
|
||||
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
||||
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
||||
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
||||
|
||||
// Relation Events
|
||||
|
||||
const val REACTION = "m.reaction"
|
||||
|
||||
|
||||
private val STATE_EVENTS = listOf(
|
||||
STATE_ROOM_NAME,
|
||||
STATE_ROOM_TOPIC,
|
||||
|
@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.events.model
|
||||
|
||||
|
||||
/**
|
||||
* Constants defining known event relation types from Matrix specifications.
|
||||
* Constants defining known event relation types from Matrix specifications
|
||||
*/
|
||||
object RelationType {
|
||||
|
||||
@ -25,7 +25,7 @@ object RelationType {
|
||||
const val ANNOTATION = "m.annotation"
|
||||
/** Lets you define an event which replaces an existing event.*/
|
||||
const val REPLACE = "m.replace"
|
||||
/** ets you define an event which references an existing event.*/
|
||||
/** Lets you define an event which references an existing event.*/
|
||||
const val REFERENCE = "m.reference"
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.file
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import java.io.File
|
||||
|
||||
|
||||
/**
|
||||
* This interface defines methods to get files.
|
||||
*/
|
||||
interface FileService {
|
||||
|
||||
enum class DownloadMode {
|
||||
/**
|
||||
* Download file in external storage
|
||||
*/
|
||||
TO_EXPORT,
|
||||
/**
|
||||
* Download file in cache
|
||||
*/
|
||||
FOR_INTERNAL_USE
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file.
|
||||
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
|
||||
* You can pass the eventId
|
||||
*/
|
||||
fun downloadFile(
|
||||
downloadMode: DownloadMode,
|
||||
id: String,
|
||||
fileName: String,
|
||||
url: String?,
|
||||
elementToDecrypt: ElementToDecrypt?,
|
||||
callback: MatrixCallback<File>)
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.pushers
|
||||
|
||||
data class Pusher(
|
||||
|
||||
val userId: String,
|
||||
|
||||
val pushKey: String,
|
||||
val kind: String,
|
||||
val appId: String,
|
||||
val appDisplayName: String?,
|
||||
val deviceDisplayName: String?,
|
||||
val profileTag: String? = null,
|
||||
val lang: String?,
|
||||
val data: PusherData,
|
||||
|
||||
val state: PusherState
|
||||
)
|
||||
|
||||
enum class PusherState {
|
||||
UNREGISTERED,
|
||||
REGISTERING,
|
||||
UNREGISTERING,
|
||||
REGISTERED,
|
||||
FAILED_TO_REGISTER
|
||||
}
|
||||
|
||||
data class PusherData(
|
||||
val url: String? = null,
|
||||
val format: String? = null
|
||||
)
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.pushers
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
interface PushersService {
|
||||
|
||||
/**
|
||||
* Refresh pushers from server state
|
||||
*/
|
||||
fun refreshPushers()
|
||||
|
||||
/**
|
||||
* Add a new HTTP pusher.
|
||||
*
|
||||
* @param pushkey the pushkey
|
||||
* @param appId the application id
|
||||
* @param profileTag the profile tag
|
||||
* @param lang the language
|
||||
* @param appDisplayName a human-readable application name
|
||||
* @param deviceDisplayName a human-readable device name
|
||||
* @param url the URL that should be used to send notifications
|
||||
* @param append append the pusher
|
||||
* @param withEventIdOnly true to limit the push content
|
||||
*
|
||||
* @return A work request uuid. Can be used to listen to the status
|
||||
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
|
||||
*/
|
||||
fun addHttpPusher(pushkey: String,
|
||||
appId: String,
|
||||
profileTag: String,
|
||||
lang: String,
|
||||
appDisplayName: String,
|
||||
deviceDisplayName: String,
|
||||
url: String,
|
||||
append: Boolean,
|
||||
withEventIdOnly: Boolean): UUID
|
||||
|
||||
|
||||
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
companion object {
|
||||
const val EVENT_ID_ONLY = "event_id_only"
|
||||
}
|
||||
|
||||
fun livePushers(): LiveData<List<Pusher>>
|
||||
|
||||
fun pushers() : List<Pusher>
|
||||
}
|
@ -17,9 +17,10 @@
|
||||
package im.vector.matrix.android.api.session.room
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.session.room.crypto.RoomCryptoService
|
||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.annotation.ReactionService
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
@ -28,7 +29,14 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||
/**
|
||||
* This interface defines methods to interact within a room.
|
||||
*/
|
||||
interface Room : TimelineService, SendService, ReadService, MembershipService, StateService , ReactionService{
|
||||
interface Room :
|
||||
TimelineService,
|
||||
SendService,
|
||||
ReadService,
|
||||
MembershipService,
|
||||
StateService,
|
||||
RelationService,
|
||||
RoomCryptoService {
|
||||
|
||||
/**
|
||||
* The roomId of this room
|
||||
@ -39,6 +47,8 @@ interface Room : TimelineService, SendService, ReadService, MembershipService, S
|
||||
* A live [RoomSummary] associated with the room
|
||||
* You can observe this summary to get dynamic data from this room.
|
||||
*/
|
||||
val roomSummary: LiveData<RoomSummary>
|
||||
fun liveRoomSummary(): LiveData<RoomSummary>
|
||||
|
||||
fun roomSummary(): RoomSummary?
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
||||
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to get and join public rooms. It's implemented at the session level.
|
||||
*/
|
||||
interface RoomDirectoryService {
|
||||
|
||||
/**
|
||||
* Get rooms from directory
|
||||
*/
|
||||
fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
||||
|
||||
/**
|
||||
* Join a room by id
|
||||
*/
|
||||
fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Fetches the overall metadata about protocols supported by the homeserver.
|
||||
* Includes both the available protocols and all fields required for queries against each protocol.
|
||||
*/
|
||||
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable
|
||||
|
||||
}
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to get rooms. It's implemented at the session level.
|
||||
@ -27,10 +28,18 @@ import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
interface RoomService {
|
||||
|
||||
/**
|
||||
* Create a room
|
||||
* Create a room asynchronously
|
||||
*/
|
||||
fun createRoom(createRoomParams: CreateRoomParams,
|
||||
callback: MatrixCallback<String>)
|
||||
fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable
|
||||
|
||||
/**
|
||||
* Join a room by id
|
||||
* @param roomId the roomId of the room to join
|
||||
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
|
||||
*/
|
||||
fun joinRoom(roomId: String,
|
||||
viaServers: List<String> = emptyList(),
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Get a room from a roomId
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.crypto
|
||||
|
||||
interface RoomCryptoService {
|
||||
|
||||
fun isEncrypted(): Boolean
|
||||
|
||||
fun encryptionAlgorithm(): String?
|
||||
|
||||
fun shouldEncryptForInvitedMembers(): Boolean
|
||||
}
|
@ -14,11 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.core.epoxy
|
||||
package im.vector.matrix.android.api.session.room.failure
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.ProgressBar
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
|
||||
@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
|
||||
class LoadingItem(context: Context) : ProgressBar(context)
|
||||
sealed class CreateRoomFailure : Failure.FeatureFailure() {
|
||||
|
||||
object CreatedWithTimeout: CreateRoomFailure()
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.failure
|
||||
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
|
||||
sealed class JoinRoomFailure : Failure.FeatureFailure() {
|
||||
|
||||
object JoinedWithTimeout : JoinRoomFailure()
|
||||
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* * 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.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.members
|
||||
@ -32,7 +30,7 @@ interface MembershipService {
|
||||
* This methods load all room members if it was done yet.
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun loadRoomMembersIfNeeded(): Cancelable
|
||||
fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Return the roomMember with userId or null.
|
||||
@ -49,20 +47,22 @@ interface MembershipService {
|
||||
*/
|
||||
fun getRoomMemberIdsLive(): LiveData<List<String>>
|
||||
|
||||
fun getNumberOfJoinedMembers(): Int
|
||||
|
||||
/**
|
||||
* Invite a user in the room
|
||||
*/
|
||||
fun invite(userId: String, callback: MatrixCallback<Unit>)
|
||||
fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Join the room
|
||||
* Join the room, or accept an invitation.
|
||||
*/
|
||||
fun join(callback: MatrixCallback<Unit>)
|
||||
|
||||
fun join(viaServers: List<String> = emptyList(), callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Leave the room.
|
||||
*
|
||||
* Leave the room, or reject an invitation.
|
||||
*/
|
||||
fun leave(callback: MatrixCallback<Unit>)
|
||||
fun leave(callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
|
||||
data class EditAggregatedSummary(
|
||||
val aggregatedContent: Content? = null,
|
||||
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
|
||||
val sourceEvents: List<String>,
|
||||
val localEchos: List<String>,
|
||||
val lastEditTs: Long = 0
|
||||
)
|
@ -1,7 +1,22 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
|
||||
data class EventAnnotationsSummary(
|
||||
var eventId: String,
|
||||
var reactionsSummary: List<ReactionAggregatedSummary>
|
||||
var reactionsSummary: List<ReactionAggregatedSummary>,
|
||||
var editSummary: EditAggregatedSummary?
|
||||
)
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
data class ReactionAggregatedSummary(
|
||||
@ -5,5 +21,6 @@ data class ReactionAggregatedSummary(
|
||||
val count: Int, // 8
|
||||
val addedByMe: Boolean, // true
|
||||
val firstTimestamp: Long, // unix timestamp
|
||||
val sourceEvents: List<String>
|
||||
val sourceEvents: List<String>,
|
||||
val localEchoEvents: List<String>
|
||||
)
|
@ -16,8 +16,9 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
|
||||
data class ReadReceipt(
|
||||
val userId: String,
|
||||
val eventId: String,
|
||||
val user: User,
|
||||
val originServerTs: Long
|
||||
)
|
@ -22,5 +22,5 @@ enum class RoomHistoryVisibility {
|
||||
@Json(name = "shared") SHARED,
|
||||
@Json(name = "invited") INVITED,
|
||||
@Json(name = "joined") JOINED,
|
||||
@Json(name = "word_readable") WORLD_READABLE
|
||||
@Json(name = "world_readable") WORLD_READABLE
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* * 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.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
@ -23,5 +21,5 @@ import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RoomHistoryVisibilityContent(
|
||||
@Json(name = "history_visibility") val historyVisibility: RoomHistoryVisibility
|
||||
@Json(name = "history_visibility") val historyVisibility: RoomHistoryVisibility? = null
|
||||
)
|
@ -16,8 +16,8 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
|
||||
/**
|
||||
* This class holds some data of a room.
|
||||
@ -29,10 +29,15 @@ data class RoomSummary(
|
||||
val topic: String = "",
|
||||
val avatarUrl: String = "",
|
||||
val isDirect: Boolean = false,
|
||||
val lastMessage: Event? = null,
|
||||
val latestEvent: TimelineEvent? = null,
|
||||
val otherMemberIds: List<String> = emptyList(),
|
||||
val notificationCount: Int = 0,
|
||||
val highlightCount: Int = 0,
|
||||
val tags: List<RoomTag> = emptyList(),
|
||||
val membership: Membership = Membership.NONE
|
||||
)
|
||||
val membership: Membership = Membership.NONE,
|
||||
val versioningState: VersioningState = VersioningState.NONE
|
||||
) {
|
||||
|
||||
val isVersioned: Boolean
|
||||
get() = versioningState != VersioningState.NONE
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
enum class VersioningState {
|
||||
NONE,
|
||||
UPGRADED_ROOM_NOT_JOINED,
|
||||
UPGRADED_ROOM_JOINED
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package im.vector.matrix.android.api.session.room.model.annotation
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReactionContent(
|
||||
@Json(name = "m.relates_to") val relatesTo: ReactionInfo? = null
|
||||
)
|
@ -1,11 +0,0 @@
|
||||
package im.vector.matrix.android.api.session.room.model.annotation
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReactionInfo(
|
||||
@Json(name = "rel_type") override val type: String,
|
||||
@Json(name = "event_id") override val eventId: String,
|
||||
val key: String
|
||||
) : RelationContent
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.room.model.annotation
|
||||
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface ReactionService {
|
||||
|
||||
|
||||
/**
|
||||
* Sends a reaction (emoji) to the targetedEvent.
|
||||
* @param reaction the reaction (preferably emoji)
|
||||
* @param targetEventId the id of the event being reacted
|
||||
*/
|
||||
fun sendReaction(reaction: String, targetEventId: String): Cancelable
|
||||
|
||||
|
||||
/**
|
||||
* Undo a reaction (emoji) to the targetedEvent.
|
||||
* @param reaction the reaction (preferably emoji)
|
||||
* @param targetEventId the id of the event being reacted
|
||||
* @param myUserId used to know if a reaction event was made by the user
|
||||
*/
|
||||
fun undoReaction(reaction: String, targetEventId: String, myUserId: String)//: Cancelable
|
||||
|
||||
|
||||
/**
|
||||
* Update a quick reaction (toggle).
|
||||
* If you have reacted with agree and then you click on disagree, this call will delete(redact)
|
||||
* the disagree and add the agree
|
||||
* If you click on a reaction that you already reacted with, it will undo it
|
||||
* @param reaction the reaction (preferably emoji)
|
||||
* @param oppositeReaction the opposite reaction(preferably emoji)
|
||||
* @param targetEventId the id of the event being reacted
|
||||
* @param myUserId used to know if a reaction event was made by the user
|
||||
*/
|
||||
fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String)
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package im.vector.matrix.android.api.session.room.model.annotation
|
||||
|
||||
interface RelationContent {
|
||||
val type: String
|
||||
val eventId: String
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package im.vector.matrix.android.api.session.room.model.annotation
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
data class RelationDefaultContent(
|
||||
@Json(name = "rel_type") override val type: String,
|
||||
@Json(name = "event_id") override val eventId: String
|
||||
) : RelationContent
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user