forked from GitHub-Mirror/riotX-android
Compare commits
1395 Commits
feature/fi
...
v0.14.2
Author | SHA1 | Date | |
---|---|---|---|
cfee2f93f2 | |||
97aca28c0d | |||
b158729b53 | |||
367057cc29 | |||
5fb4f274f9 | |||
a35302eae0 | |||
6f60f1c6b4 | |||
435d8cbc55 | |||
bb0fafcb2f | |||
1d2928b3fb | |||
40b0f60964 | |||
96a556f449 | |||
d436d3b8d4 | |||
845f3f5ad1 | |||
fb838e5407 | |||
40c70f64a8 | |||
245b3717b9 | |||
9e15891053 | |||
637eba277f | |||
649ebf4a34 | |||
e9f220cca2 | |||
8a9bd97a88 | |||
26400e6372 | |||
282be29247 | |||
6ee7ee1460 | |||
2e0a84ccc9 | |||
9d6e7d7bd0 | |||
8f7b18239d | |||
10094a212c | |||
53e36dca9c | |||
6ec2dd8c00 | |||
f098d6cf5b | |||
cd606ba8a1 | |||
59abee10f8 | |||
64df9e23c2 | |||
fc4f5faffd | |||
256f8b77aa | |||
f2f775cb99 | |||
2616a889ef | |||
ccd4c1ed86 | |||
bb92882958 | |||
46dd17644f | |||
7e34b2a672 | |||
c3c88c387b | |||
51e0f945a7 | |||
fd3619b100 | |||
e18b9d5155 | |||
f5ecf4bd90 | |||
27c74c9118 | |||
4d91bc934b | |||
5c547794f2 | |||
43358cd86c | |||
87b76d10dd | |||
80f4f95f81 | |||
8e5c7239cf | |||
7b385c3b36 | |||
4fb59aadb1 | |||
850c830e1f | |||
9dde43f65b | |||
9d566e9352 | |||
eb46a4949f | |||
506c2dd262 | |||
ccd857016c | |||
225e4e0433 | |||
069bd3c258 | |||
fb98d6ef42 | |||
6282f81bc4 | |||
a5ca2b1d34 | |||
50d5ad3625 | |||
03c5e61b2e | |||
4ddd831d7f | |||
ff95392e10 | |||
e2c2c2418c | |||
e44dc347c6 | |||
fbd0bbc575 | |||
b848d0530f | |||
2bccd19f84 | |||
2111daea52 | |||
57a13fa30d | |||
c4649a5824 | |||
6f6c3184dd | |||
d7feb6dd5c | |||
e6c3f7c77b | |||
8b6ffc2fb1 | |||
ae36846aaf | |||
237da5bb16 | |||
a4abe5f552 | |||
e1ddde5501 | |||
754ca3c582 | |||
70b04dbaea | |||
b44b6726ed | |||
80ec199135 | |||
305dfd4c7a | |||
73bb4bb785 | |||
1d84ccd64a | |||
63e36b0403 | |||
2c568b4de9 | |||
27fe4e3680 | |||
007b0cabf2 | |||
b2338dfcd3 | |||
6d7d4993a6 | |||
da9b9f4864 | |||
ef0b438a89 | |||
9a79297e14 | |||
a57393cafa | |||
e12de3fba0 | |||
2eeeea3377 | |||
976a8fc568 | |||
b7ecfd997d | |||
e0b3ea7e48 | |||
9c829e62e6 | |||
20c7e4c3ad | |||
69ab5e43d5 | |||
f46023e84c | |||
d8d465f70b | |||
8659216955 | |||
39e746413a | |||
0aaba26f17 | |||
fc04833157 | |||
f80861bed8 | |||
9e796067cc | |||
fb5148fd43 | |||
be77017209 | |||
962b85b041 | |||
adc2d570eb | |||
df49ab8362 | |||
efc8cfb9a1 | |||
345824daa2 | |||
050eb0af9d | |||
ca4ed6e1bd | |||
f021f8110d | |||
109ff4f908 | |||
c9f0209ebf | |||
7daa088618 | |||
83e44ac96e | |||
f3e88c75cf | |||
ea6e8a6789 | |||
6cece03998 | |||
08ae0b485a | |||
665c577747 | |||
d60351bcb7 | |||
a758efc018 | |||
d0addc4c4f | |||
bb5179140c | |||
e9ea69f055 | |||
e47791f290 | |||
91ae96a153 | |||
0148949a4f | |||
65cb812fc6 | |||
e8a4f1fb90 | |||
632832a651 | |||
d530c64a84 | |||
426e291ce9 | |||
1276d1f39d | |||
4a1012cf81 | |||
5819790c1b | |||
b3089343ad | |||
37b950897f | |||
d2fab91e9d | |||
c323b61575 | |||
0e55f81879 | |||
cbf418c401 | |||
41c691f26c | |||
81337d1624 | |||
79df6b8402 | |||
537b1be0c5 | |||
c1259161e5 | |||
75e39535bc | |||
c971f18fc0 | |||
3c2fa40b58 | |||
0b74863c6d | |||
dca950140d | |||
a13a78ccc0 | |||
7327dc97b4 | |||
da3e547d82 | |||
a0aa1f34d3 | |||
e81c804ed6 | |||
be371f9279 | |||
a6364f0be5 | |||
390879e3fd | |||
6ab540045b | |||
98ba2d39a8 | |||
859c75df98 | |||
ea9166e0c6 | |||
d672313649 | |||
f5e6b4b857 | |||
28db05e509 | |||
56b140fcb4 | |||
eee6969b02 | |||
825427b7b0 | |||
8c32796d5c | |||
e45c1e6c2a | |||
188cd6beff | |||
aaeb54db7c | |||
92f26bc20a | |||
fc22b7988f | |||
878bae1c45 | |||
a8e81d95cf | |||
cd1665a8e8 | |||
7170471686 | |||
98020404ff | |||
b2348427bd | |||
832df59b58 | |||
32689facc5 | |||
d3071e5816 | |||
83a6f564c3 | |||
3b420dbb50 | |||
9fe155bafd | |||
3c982866d8 | |||
494ad83704 | |||
4543658ae0 | |||
689fd1ea90 | |||
a95410c118 | |||
8749e49e80 | |||
8400ab6efe | |||
d1699279fe | |||
f471d9cff8 | |||
115058124c | |||
7604748d3f | |||
d46a3ab87f | |||
b375463aec | |||
cfb752c220 | |||
c59712732a | |||
626ebd9c63 | |||
5148d7ab46 | |||
8c2a55a5a2 | |||
34b6dd4270 | |||
79a68a36bd | |||
78408fa0ec | |||
b56ee9a377 | |||
9eff459ed6 | |||
cd44b60bd5 | |||
17ed8da57a | |||
8ae51a95c5 | |||
f80f22fed2 | |||
fa57d0f6d5 | |||
1104b5d483 | |||
464462022c | |||
1c63d3f8f0 | |||
a916bd4ba8 | |||
15e2c9f3a3 | |||
f1a2fb51f5 | |||
b0aa9fbd8f | |||
d72f1ac576 | |||
ca157c7567 | |||
ae26bf3369 | |||
159c96681f | |||
96d6b75037 | |||
72e6181f00 | |||
4ae09b8716 | |||
f128ed437f | |||
11c8c8c2bd | |||
2c331671ee | |||
b44ddcfd61 | |||
7c0f2d6e32 | |||
52de14b1b5 | |||
d6e6092eea | |||
9671a77e5d | |||
d3415d345f | |||
df4df81ef3 | |||
a7c948815c | |||
9d5197b1c8 | |||
162f0949fa | |||
ae1a24e948 | |||
b5fead18fe | |||
3c682430d9 | |||
717965bc37 | |||
ecc463e920 | |||
f3e52b96c0 | |||
dd81fce8d8 | |||
7354eab061 | |||
1ae58aa6ad | |||
fb9abefe59 | |||
095216349e | |||
550908fa70 | |||
0dbca829ea | |||
03b5b098c7 | |||
171ec4fbdc | |||
32d2daee3c | |||
de84bb7535 | |||
81712ae736 | |||
289951ea4a | |||
8c9c65837d | |||
24a7ce7d98 | |||
d486e613d8 | |||
598d67234c | |||
fea4f2e129 | |||
687ea1b5b3 | |||
47e3b8ec46 | |||
dd8c908dc7 | |||
9775e8c32b | |||
e3205fb493 | |||
35f011ba37 | |||
ed773dbb96 | |||
fa821826d2 | |||
293e3e3ce6 | |||
4244c0e48d | |||
76e45431da | |||
f3fb07079e | |||
3ceac70536 | |||
0f7209df1f | |||
e177251ec0 | |||
6746f68411 | |||
fc6d845c0d | |||
93cdce6c3e | |||
ae3381227c | |||
b6a1ff1ca4 | |||
7ec0227528 | |||
15639b45cf | |||
f18ec8d021 | |||
898bf234da | |||
3d0d95c371 | |||
bd4a595f96 | |||
0f7d59a8c7 | |||
e14b9b3b20 | |||
43c4e20819 | |||
5312b44359 | |||
8c4d8763a2 | |||
383605274c | |||
8032490606 | |||
a458997ce0 | |||
29f152f349 | |||
6ca3ba6c9b | |||
f4492e570d | |||
587fefedb5 | |||
943be39e1a | |||
616f3d3345 | |||
2b8ecae8e3 | |||
17c4013383 | |||
d662b4a9b4 | |||
501ac36040 | |||
7575cb286e | |||
c60b4ddb5a | |||
9970d7ffa0 | |||
2dd2a8db6c | |||
8ef5c60e2e | |||
03c3c9ae57 | |||
38c198fe02 | |||
42c7421b05 | |||
19fb3ce032 | |||
5a7f4bed43 | |||
03734a7ad5 | |||
d710106bbb | |||
f09bf61750 | |||
f9487f8995 | |||
99c523b710 | |||
3cc15387ae | |||
6245763577 | |||
448552d287 | |||
9ecceafb96 | |||
92d7ebe94f | |||
0e5fcd071c | |||
c8e67f8ab4 | |||
5fa2acf60b | |||
9e73e95f55 | |||
8b4c51139d | |||
8597c2b9a2 | |||
d88e5d8af8 | |||
c4fe0bdb7f | |||
d73a1135ae | |||
ed097bcf37 | |||
01db856a5d | |||
a00f51a264 | |||
06b41af467 | |||
9e8217082c | |||
ce73007157 | |||
c2cd149299 | |||
08ed8d4fa7 | |||
f432d15757 | |||
215abea10a | |||
160927e7b5 | |||
c2e7e33050 | |||
455448806d | |||
a969443517 | |||
1bd85082c3 | |||
de1d79b637 | |||
8e478e78e1 | |||
96c9293edc | |||
5c26f66523 | |||
d1233e8470 | |||
5a24f78c05 | |||
bf28f14b8b | |||
703a1a034d | |||
7d744f7d7f | |||
8dff196716 | |||
52c25b803f | |||
6b2703f6ce | |||
b26318f15c | |||
f541661059 | |||
e32d242e38 | |||
5b210df7c5 | |||
787908287c | |||
935b3d7f3f | |||
8156b754c1 | |||
03fd474aa8 | |||
3c4506cb58 | |||
3eed9b5083 | |||
6bf3a703df | |||
2152af8851 | |||
5b33a42f8a | |||
a73cd61b96 | |||
4edd5e3530 | |||
4c0cbca4cb | |||
308b15b908 | |||
38906084d1 | |||
0997d9abf4 | |||
6ad914154a | |||
cba7e460eb | |||
6794173321 | |||
92f4288d3e | |||
8109262cbb | |||
037bf45884 | |||
833a5a37a2 | |||
00f316ba5d | |||
dfd8181754 | |||
90f2199eb7 | |||
63828bc159 | |||
35b4d90e0d | |||
4fe9c52737 | |||
c54358831f | |||
a9d016ae79 | |||
fcdaf14b01 | |||
83126d5f55 | |||
e13281dc97 | |||
3cc65b1e71 | |||
0ccb975d43 | |||
54f2ac0d8c | |||
3ee5a7f54d | |||
3b0624ea40 | |||
c992d32afd | |||
c5739abe32 | |||
3ac473d945 | |||
c79b35b089 | |||
8dce98c538 | |||
543c07fd69 | |||
05a788453f | |||
c31b64771b | |||
92f43a591a | |||
3a829bdfe8 | |||
237b22df59 | |||
c18be94986 | |||
d342356f29 | |||
07817b69c2 | |||
e73970d61b | |||
0eb0870d6c | |||
55748a4af4 | |||
51d6b8828d | |||
358fcb6b34 | |||
9a7cd3f270 | |||
92315a4189 | |||
a6afd2e904 | |||
156cc1aa4a | |||
0d36e9d8a6 | |||
13439769a1 | |||
7bb8cb0682 | |||
a4ea9a09ad | |||
bf69810f8f | |||
bb9510e59b | |||
4b0dfa49f4 | |||
6652965e48 | |||
94125a0215 | |||
d97402f757 | |||
5bde7b9f17 | |||
c8f0c83cd3 | |||
b0ff2cb4bb | |||
648691656a | |||
7eae85a394 | |||
123ffe9f9c | |||
7697278bb2 | |||
c48a439eea | |||
9d26ba3186 | |||
08970ad8c1 | |||
4c88c12cfe | |||
79f11ad686 | |||
7fa76b9d35 | |||
65faedb06b | |||
1ceddd9607 | |||
42cdb1db11 | |||
1c727c1ee4 | |||
2316c98a65 | |||
a4aa38ee43 | |||
4a11d028c0 | |||
08d005a611 | |||
c286f2a744 | |||
89b414e8fe | |||
e2b4899b36 | |||
aa82cd2064 | |||
bc568343a2 | |||
abf0796794 | |||
02febfb01b | |||
91c98d4bfb | |||
cfee6a43fd | |||
f14f1db0e0 | |||
3feb2d8980 | |||
9fc3093c2c | |||
7d910f2566 | |||
0a0eda3e34 | |||
cecef5b8da | |||
c9ed95ed21 | |||
3dfd6f5a69 | |||
8fc1400bab | |||
3e4b07cec3 | |||
fbb1846694 | |||
b435212c87 | |||
5dd46e82d7 | |||
3727affc15 | |||
ff5305ee66 | |||
3953022258 | |||
1108ad5705 | |||
b473aeb475 | |||
289c03e724 | |||
fe2be90002 | |||
f073342954 | |||
38b40efac3 | |||
e60bda7806 | |||
92e60c939d | |||
6e4830e325 | |||
c6b98f3654 | |||
12d54140e5 | |||
1de85daad9 | |||
210dcca0ee | |||
872baacfe4 | |||
050519e998 | |||
1af44ce5f7 | |||
8d1a36425d | |||
4e74b545ad | |||
183d6b53bd | |||
14562f7285 | |||
17bcd680b0 | |||
954019547d | |||
782635ec8e | |||
e609f4a57e | |||
907fa35547 | |||
00d0c34363 | |||
6811d31a6d | |||
a464c910f8 | |||
975de1dbed | |||
d69881f321 | |||
efc1f38f8c | |||
dedc622140 | |||
b9e8da1fbb | |||
d2fea275d8 | |||
a5af949c15 | |||
9842cac504 | |||
35404b9a7f | |||
0afcb60e7d | |||
eab94b4f03 | |||
cb595177a9 | |||
cb4d52c9fb | |||
d0a3b4663e | |||
f53b99a423 | |||
6da0693488 | |||
ea817ff1c5 | |||
62f0c6edc0 | |||
e71ad0e515 | |||
553604423e | |||
be723256d3 | |||
819d7182bb | |||
0a2ffdbdf1 | |||
4ac7331f3d | |||
7fc57bdf9b | |||
d370f6d7c8 | |||
46ef442139 | |||
5f3dc73440 | |||
6137a88a6f | |||
82af848c33 | |||
6b61c95843 | |||
261b4be287 | |||
205fc0d9d6 | |||
7699560458 | |||
a193b2659d | |||
bb85d41f05 | |||
9bfe904745 | |||
284dc8602f | |||
29087d4a87 | |||
18649ebddb | |||
d5935a13ac | |||
670d4dc34e | |||
5435a1739e | |||
853518fbb2 | |||
a673bf092d | |||
0776a301ea | |||
3a269be2ef | |||
02f03e6b23 | |||
8305ce67dd | |||
95deeb1be7 | |||
0b93f34fa0 | |||
5338f93852 | |||
73f0132d5d | |||
902a9aa243 | |||
5915cebd6d | |||
d91ff87fb9 | |||
c462d15bcf | |||
3cdd373368 | |||
e14602d1dc | |||
2aa9c3ea22 | |||
bbd9738452 | |||
36c5566b07 | |||
ce63332a2f | |||
26b4b6e194 | |||
79ef055bfb | |||
3a761be6b4 | |||
a9e2c31c32 | |||
3ac53d20e9 | |||
3c18fd5335 | |||
f00f34b244 | |||
63e0b15f3d | |||
80306f20df | |||
2972177541 | |||
1ad8f47dc1 | |||
8527d3f162 | |||
99423bacb2 | |||
edc6c3dd4f | |||
a761a0dbd2 | |||
d431ab23c8 | |||
f0aa34774e | |||
742136abe8 | |||
36aba8554d | |||
da14ae432a | |||
9a01b4ace9 | |||
109c1fe482 | |||
dbd4525404 | |||
c714266a81 | |||
8b1701e537 | |||
41d1b77370 | |||
ac75fe12bf | |||
2f26f4b8bb | |||
6d82ac7c59 | |||
411afb0bf3 | |||
57354cbd69 | |||
03d51281a2 | |||
415511f3e0 | |||
e0e778909d | |||
b9efc9f4bd | |||
872b14373b | |||
d28700e2bf | |||
18beef14cf | |||
e73923dca3 | |||
94afd3e66d | |||
5f540a5b45 | |||
a41617e8aa | |||
ff1745b5dc | |||
8e3e9876b8 | |||
9a4d8f87f6 | |||
aef76241a3 | |||
0768bd5c88 | |||
65333e6031 | |||
849e7c613c | |||
60169d53d7 | |||
4234c27af9 | |||
f9c0256afd | |||
b9eb85e0a6 | |||
6d8850b3d6 | |||
d88edd578f | |||
5373d9aa21 | |||
ad4d5e5c02 | |||
eb9775e307 | |||
aa9d66b991 | |||
4ff12605e9 | |||
7c561ae622 | |||
cec08a20e5 | |||
fb8ba32fb4 | |||
8e9ac8198d | |||
7a05207ae4 | |||
6b39cf3b70 | |||
994759e11a | |||
f31c1b69cb | |||
bdb9d2fbb8 | |||
9fb50dde32 | |||
a145aae0aa | |||
3623072f08 | |||
2717ad475a | |||
a6f8fe9317 | |||
f9eb80b4ec | |||
9510d71cd3 | |||
e7a47ae32a | |||
7890b929a7 | |||
0376de08f4 | |||
90c472fef9 | |||
8e873672a9 | |||
bba52e77d1 | |||
64d73ae8e6 | |||
d9982076f9 | |||
ab489df83d | |||
5e07e96bdb | |||
c495aa4914 | |||
ff267ba9bc | |||
69f923383c | |||
c69852c849 | |||
6d7f2670df | |||
71de8fdad3 | |||
998d9f2c59 | |||
4f3da353e4 | |||
4154cb2b85 | |||
3c6eb4bccf | |||
7b4398404b | |||
9b882978ed | |||
49178dc633 | |||
490ce4b51d | |||
5b63856d96 | |||
538c4d1a64 | |||
1cadbb8eed | |||
3f4f7457c7 | |||
ebf21fe9d8 | |||
a343da594f | |||
938289e8eb | |||
e23763e6db | |||
c06b8486ea | |||
67fe776d91 | |||
10cc270273 | |||
46d96429e0 | |||
9f9c418085 | |||
c412006f0e | |||
5d3c376267 | |||
a3f8f138a6 | |||
4b273e8746 | |||
f11cd47df3 | |||
f984758d37 | |||
97766404d6 | |||
38b93c527b | |||
62bae67080 | |||
2a4cdec020 | |||
6bd7257cf2 | |||
500eb113b6 | |||
1bec8c29b8 | |||
0ecb23199c | |||
33925fcf57 | |||
bf9ce4f690 | |||
d2a4163dff | |||
a0d7aef92e | |||
29f32cf8eb | |||
bb1c988a49 | |||
f063abe068 | |||
db87d8f644 | |||
efa858a337 | |||
fd90f3b9fc | |||
aa51764068 | |||
0a19ded167 | |||
2e3763e8b4 | |||
0c4e0890b1 | |||
e532d97ec1 | |||
fbde8d7d18 | |||
f96bea742e | |||
86bfdd011e | |||
d5c2c1938c | |||
fd18bcb97f | |||
ab4cab05cf | |||
7ce8a13ddf | |||
9bd4dbb65f | |||
ee875b359b | |||
3eb2e1655f | |||
9b207dd5dc | |||
3f1540b54e | |||
2b30925163 | |||
4690754f5f | |||
a9526cdd92 | |||
ab4d42fb20 | |||
0014e8ef06 | |||
311d8ddf7b | |||
6cb3c222a9 | |||
f84ec08847 | |||
9d0188cbf1 | |||
73462a3045 | |||
3eebf965e5 | |||
bba58d25e1 | |||
fedb45b019 | |||
9b83f08654 | |||
91fcf428dd | |||
7e1a279fd9 | |||
8de1fa835b | |||
af45c554fd | |||
11bc7051fd | |||
489a594027 | |||
3f83c161e4 | |||
e0a36b794f | |||
d2b516bdc2 | |||
37166caea2 | |||
9fa131c297 | |||
71ae3c4a8c | |||
f87526e615 | |||
51f53e2ae9 | |||
ef35f0a044 | |||
5db3f51ddb | |||
49f7ce3554 | |||
a3111dc2d8 | |||
be95542110 | |||
6723a566c2 | |||
90027cc4d5 | |||
810b226f21 | |||
42c5adf08d | |||
5edfb78721 | |||
491a38a79f | |||
051f77087e | |||
1a603742d0 | |||
edb65f1787 | |||
9af8355c07 | |||
dd44078297 | |||
a1f96a5b5a | |||
5770023593 | |||
2789268c23 | |||
eb4355890e | |||
4e41156db3 | |||
1a0b8b35f8 | |||
5f9cdcb4b4 | |||
2e4c3f850a | |||
127916a8d9 | |||
248a584e1a | |||
b8a3ad0c43 | |||
1f161b7e23 | |||
23315ede92 | |||
20ad3abb60 | |||
ac377fceba | |||
abbe56acfa | |||
f74cabd145 | |||
0e2237226f | |||
62d5aba796 | |||
f12e6c941d | |||
7caa8ce3bc | |||
20f969d563 | |||
a8f24e5c39 | |||
8ae9544b48 | |||
3758334824 | |||
6d8e5b892e | |||
c18c140ec9 | |||
1dc7dfc896 | |||
1c03163a33 | |||
9aa270c7ad | |||
3f80076fb1 | |||
dfbf448bb7 | |||
95fc20dca0 | |||
381084b2ab | |||
41ac2c6d70 | |||
08ea3d049e | |||
f24889230c | |||
b5f9549a8b | |||
e3e38d4c8a | |||
416bef7903 | |||
823acebf78 | |||
3e91125872 | |||
9a628c7b5d | |||
fb46a14172 | |||
ca4e75a1a0 | |||
2871e4f5b1 | |||
b7bfb20a2e | |||
a1aa16715d | |||
55add4734d | |||
2849e1f846 | |||
5b9876a20c | |||
adf299081d | |||
d50b690523 | |||
c6b0ae63ea | |||
3c93807fe6 | |||
7f1f98c2e5 | |||
6525314af8 | |||
da8d6fb4f4 | |||
fa6a9cab7e | |||
bdfc4ad8a7 | |||
6ab7209e4d | |||
4485d1c685 | |||
8b63f78d76 | |||
2e87e0b4c1 | |||
507134407b | |||
7663cd4e23 | |||
ec2954200e | |||
eb32c5455f | |||
fc367b3c3e | |||
57dcd569f3 | |||
3673520ef6 | |||
8aab46804b | |||
fe17050580 | |||
0edc953a23 | |||
fa67509ac2 | |||
ec40a8c969 | |||
6b1b3bec85 | |||
6bd6ececb7 | |||
c7db695e67 | |||
4cefdfedce | |||
6ce241163e | |||
79350899c5 | |||
f265724a3c | |||
2e50d2a36e | |||
643c062858 | |||
0e0db67aef | |||
6dc5b126d6 | |||
d2acabddd9 | |||
ec71b53c1e | |||
fc3d4187d1 | |||
a25f309990 | |||
5449592422 | |||
19b415871d | |||
6463f3439f | |||
f2320f9571 | |||
fc91694bdd | |||
dbb41108ef | |||
08c864bad7 | |||
9c5c65a243 | |||
b6199b1f27 | |||
38da54119a | |||
65b09ad4f0 | |||
603b8fae45 | |||
50e2e6a823 | |||
bb237e3bbb | |||
1bd2c0d220 | |||
bcb811a7e8 | |||
ec4d7e29ec | |||
a6df63f6d9 | |||
ea7213a5ae | |||
590a13334d | |||
631448335d | |||
12376368c7 | |||
f17564d743 | |||
a6fcc7dca6 | |||
70bce9e7dd | |||
17f3614288 | |||
238d1d87c6 | |||
82f639b91f | |||
c8bc553caa | |||
fa5d44af65 | |||
cbdbe5033f | |||
61ac250e2b | |||
04f72dfcb8 | |||
10ca5d94ea | |||
c5b8c69ae5 | |||
d3d7f7cc61 | |||
b6bb714264 | |||
a87310ac15 | |||
032e1b3d19 | |||
d9f15c1d21 | |||
99d09f71ad | |||
9c952b6bc8 | |||
fbae3d27c2 | |||
2f7d1f9f01 | |||
114101699d | |||
f5c0dcb5ea | |||
241220ce1f | |||
98d97e574c | |||
96e610970a | |||
2027802f82 | |||
54f93db632 | |||
3af7ca9ab0 | |||
93ef3edab3 | |||
c85852262e | |||
d0c3271628 | |||
ad9a48d5fa | |||
219d1383e5 | |||
8871280fab | |||
fb3e953e28 | |||
10712fd6ab | |||
9d478dbfe2 | |||
3013d67c16 | |||
bee8c2d159 | |||
945e5d5a74 | |||
93df8c56a8 | |||
cd1a964067 | |||
e4b829f0cf | |||
7206d84a6b | |||
b3233d3eb7 | |||
3c4c0ed46a | |||
24f1262005 | |||
86667a6d8a | |||
42e0d0f769 | |||
e976055253 | |||
84d6c8ec16 | |||
9fdfd091ac | |||
e66766f41c | |||
6177e69855 | |||
5c71cabb5f | |||
6ebe5532c5 | |||
8030c44f44 | |||
a85b5af761 | |||
d780c74abf | |||
5d7efa7f8f | |||
7e467443ed | |||
8439c337f7 | |||
151ad01038 | |||
73267442bb | |||
43fd794c96 | |||
36060fe332 | |||
3483debcc1 | |||
4324f6abbd | |||
43f8d8d8aa | |||
fb1ff77ec4 | |||
e355a7f6dd | |||
33e35368fc | |||
0e49a11e5e | |||
d47cf7e932 | |||
101057520b | |||
30b2e53002 | |||
5ab31a0ef5 | |||
b4ae331086 | |||
3f447df13c | |||
3517873156 | |||
118870bc41 | |||
d001ab5bef | |||
7496a88dcd | |||
6567c5e6c7 | |||
361427488f | |||
7272343e6d | |||
f0b3151d71 | |||
035359cb35 | |||
57b640622b | |||
de4c389c76 | |||
199456487c | |||
00ca5dc70a | |||
a04802b238 | |||
cb275aee37 | |||
fbf73c7c8e | |||
0040f8e924 | |||
6cca242f77 | |||
2929b8f617 | |||
8422c6de17 | |||
7c567b04bb | |||
1ac99e92a6 | |||
5ab975cc5c | |||
2cf63ea92a | |||
9e8d8ce878 | |||
b766bce07d | |||
01452efd8d | |||
0a0af221f0 | |||
af08759af6 | |||
e52f0faaa7 | |||
8fa676d034 | |||
b6594599c4 | |||
8be8cc9ef7 | |||
9762d5be40 | |||
b17b54d218 | |||
187e2a26db | |||
2f5fdbb7e2 | |||
8b1411f533 | |||
bdee5e0687 | |||
ce4e244a3b | |||
ff81715783 | |||
3196dcb57e | |||
50bf6df7fe | |||
02914495ce | |||
70a14f6350 | |||
cac5fb725a | |||
dbc17ae515 | |||
1de02c2fbb | |||
6d55c15761 | |||
377a228f88 | |||
2974f8b200 | |||
7388a408b8 | |||
f43dcb1183 | |||
492ed3954a | |||
7890e83204 | |||
00d1a2c380 | |||
78dfd6b3e6 | |||
3abce34484 | |||
4204ab262c | |||
c7a4d34192 | |||
7416fec93e | |||
3c40f64fb7 | |||
b57c71b1c9 | |||
fea54952d3 | |||
3dc5ef54ab | |||
9092b97fb8 | |||
cebd8136da | |||
64b3568d51 | |||
5e4e54153c | |||
d071324694 | |||
2c8cd89533 | |||
11b5c2c3ba | |||
9d7c4abb97 | |||
8e3234d188 | |||
b253722b98 | |||
fce576e3a4 | |||
7ed7b18ccd | |||
053bf7aeac | |||
6ccd083451 | |||
e39c4a7925 | |||
abdb83b9fd | |||
0bcc84cbd6 | |||
b2f6fb8c91 | |||
36042ed145 | |||
6ad1932fe5 | |||
4a6237b50e | |||
a7a19dab11 | |||
8d0aa0437c | |||
0a79b8b315 | |||
1dacfa6744 | |||
723a007c39 | |||
eaa1b04a4a | |||
b1710fde60 | |||
cd0a40c18d | |||
17636019e0 | |||
8078c39d6e | |||
be94b2f90a | |||
eff04be247 | |||
3986839801 | |||
9e436483de | |||
05a069be04 | |||
a1a71e2f1d | |||
203da0f37e | |||
6cd04525aa | |||
3c3c6aeac6 | |||
e71311f576 | |||
e4d0e0b0bf | |||
28e5e42ab1 | |||
b860c3b0e3 | |||
f7f97e2098 | |||
e28e2dadb9 | |||
c28be6adb0 | |||
c57af9cf3e | |||
679b0fff98 | |||
946fc36a26 | |||
13a5f784dc | |||
0ca8696e88 | |||
3622c0ecb4 | |||
116d569fa8 | |||
ee5ebb4b83 | |||
0a0c344bfb | |||
82fc97f619 | |||
20696353b8 | |||
ae5b6bd2b9 | |||
1e11d4492b | |||
6e39164b20 | |||
0a9ebb6bf6 | |||
db009ce683 | |||
55c80d3743 | |||
fbb23dfb66 | |||
e5779d425a | |||
99d9704a50 | |||
3f8ddbe880 | |||
30e43e47cd | |||
15dc4d6369 | |||
dceb5ffd8d | |||
eec470f2ce | |||
68db9c1cc0 | |||
cdfc402599 | |||
72d3f1e909 | |||
255fa11e89 | |||
119e4c0d32 | |||
a9c474105a | |||
6de64cbedd | |||
546c537e3b | |||
36c5f9af13 | |||
c2682c7f4b | |||
3073470c38 | |||
549f749682 | |||
d4dfb76e80 | |||
e80191b2e0 | |||
c62c77f14c | |||
d6e5c5a857 | |||
50a0660ab6 | |||
ecdb3c3326 | |||
2cd1d697fe | |||
3f9b7813bc | |||
f34f28b668 | |||
53572a3be6 | |||
90b6199e10 | |||
0aa299aa37 | |||
d387c310c8 | |||
8bd1fb08f7 | |||
ac6aff9175 | |||
adf0382d28 | |||
51554f7be0 | |||
c1c1c3f999 | |||
8b04fdab77 | |||
f8b665a245 | |||
d68a9a5342 | |||
5d2ff589f8 | |||
e85a0783fc | |||
d6c278288d | |||
4ad86a13a0 | |||
4f7ec91255 | |||
979b42aa30 | |||
fc49de080c | |||
d2b9668d4e | |||
0632870be1 | |||
8e39fd2a70 | |||
abbc62dd35 | |||
77de059dc9 | |||
1931a1a4a4 | |||
9c5987b682 | |||
4e4fb4c565 | |||
0582d0f641 | |||
ef2af14529 | |||
525da17678 | |||
aab41d7358 | |||
5db3c81aa9 | |||
c763635845 | |||
11d72b81f6 | |||
53543453b3 | |||
d4be68191c | |||
7ef471ad0d | |||
73dd735ba6 | |||
2f6d3adb17 | |||
2edfd4e830 | |||
ff7856c535 | |||
650a151b18 | |||
275dd20412 | |||
44f6391cb4 | |||
588e5d6e63 | |||
716999eec6 | |||
42e0a45f3f | |||
31397869b2 | |||
e842bf13b2 | |||
aea34da81e | |||
0814f53fed | |||
b5c6c1af0d | |||
de30e7c1c6 | |||
2d95fe921d | |||
84542326f4 | |||
53b1b89c47 | |||
28315be7b9 | |||
8605095668 | |||
737959f616 | |||
7817f49072 | |||
a060431aaf | |||
a3f561d788 | |||
0ea878af8a | |||
99de40c980 | |||
810a97c639 | |||
f02f16d9c5 | |||
62b7a83a31 | |||
4a80df082c | |||
60f6b3ef02 | |||
a0b1ef3216 | |||
1b66d1f746 | |||
643a2baabf | |||
cd62e87266 | |||
17cba1a432 | |||
f077cc8467 | |||
f3039601bf | |||
4c04014e4d | |||
ae8bceacba | |||
9b91b6ea87 | |||
b24a372262 | |||
63b43de4b8 | |||
d1a61f29e4 | |||
f6373221de | |||
ec0974f72c | |||
b5f2f01c8d | |||
21d808c1ce | |||
1e963bc0dc | |||
0d80750507 | |||
1c9cf7a810 | |||
c6d01fbcf4 | |||
9e1ded941f | |||
af433266c8 | |||
05d09bf950 | |||
6890f83810 | |||
51568c30a6 | |||
cc832633a5 | |||
e019ec6596 | |||
eadea9016b | |||
6422d946c9 | |||
5cc3dc00e3 | |||
5a2a9f908a | |||
c1f2e9f171 | |||
f6d34ec7fd | |||
620ba279d8 | |||
3fcfa33364 | |||
546da0f173 | |||
001711d5a3 | |||
8e1a964679 | |||
b25a130db1 | |||
8a9e6497e8 | |||
47e3797b7e | |||
5cbc90e06a | |||
b6e18e4a8f | |||
7e29665fd0 | |||
e04bf31faa | |||
d25cf79b07 | |||
faa8e6bbb2 | |||
d3d4deb884 | |||
f6b8e0c479 | |||
2a726f54a2 | |||
1197d4021d | |||
03f8120b7d | |||
acd7a709de | |||
5651ea515b | |||
9794b3a49d | |||
b3e1c3969d | |||
90eeb68d36 | |||
d1ff3314a7 | |||
f24bed17a2 | |||
a993a30203 | |||
ea0809ff87 | |||
9668487b6b | |||
91cc78d2ad | |||
562acc9702 | |||
dfab88ed95 | |||
36866dd24e | |||
c728834273 | |||
f5020d0f63 | |||
7da9cafcc2 | |||
6f09eea248 | |||
468bd5bcc9 | |||
3169093c50 | |||
d60d766354 | |||
0ffb5e627e | |||
b4a13f9504 | |||
88fb9667a3 | |||
ffa8b7e73a | |||
528958b3de | |||
3ffe2f7d40 | |||
3066d5f303 | |||
bf42b73713 | |||
ed93f4a6c1 | |||
b3d649a4d9 | |||
3739e50d46 | |||
9bf484cf1e | |||
6c2faff1f0 | |||
07fca0922b | |||
282de21708 | |||
ba9d119892 | |||
4453f0ced9 | |||
77168bfd6a | |||
25e9a179d2 | |||
73ec0f5a83 | |||
993fa74252 | |||
38fc4984fe | |||
695d8cce00 | |||
07e99901e1 | |||
20f53e9a58 | |||
ced72aff4f | |||
fdaaca49c2 | |||
3485f023b0 | |||
384dd100e9 | |||
1ba8a58219 | |||
69fb7bdf95 | |||
c8010561fc | |||
1f127335bc | |||
138a210a73 | |||
ca6bcde82d | |||
6bda437f5d | |||
5d6d0202a9 | |||
3e6b65e174 | |||
137dcab734 | |||
b22b8fba02 | |||
3ccdf4a244 | |||
5fbd271b1c | |||
db8ea0f5e8 | |||
a47a3ead1f | |||
05b2092ffc | |||
f4ab770be9 | |||
6249a59203 | |||
d4111d053d | |||
618e9a4f52 | |||
b8ebe3570b | |||
f2c8d4ad02 | |||
be524472ec | |||
1b82a1a24d | |||
cf0b331c3b | |||
2a92a3dc80 | |||
012840abba | |||
a5975a099e | |||
38da4b9ee5 | |||
242e60fcaa | |||
a23be05cbf | |||
ed39b02924 | |||
fe931b5361 | |||
90d9cd0587 | |||
9cedb18921 | |||
e89ba7b87b | |||
902657c22a | |||
eec2abf164 | |||
6879cc8ca8 | |||
fd6bbbd3b5 | |||
0ff0b014a9 | |||
a89f0ddd1d | |||
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 | |||
51a4c93676 | |||
d8f449388c | |||
9cd69d1e33 | |||
456908c851 | |||
215324a03e | |||
02e342849f | |||
df6080b1da |
@ -1,15 +1,38 @@
|
||||
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
||||
# Last docker plugin version can be found here:
|
||||
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
||||
|
||||
# Build debug version of the RiotX application, from the develop branch and the features branches
|
||||
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
|
||||
|
||||
steps:
|
||||
- label: "Assemble GPlay Debug version"
|
||||
- label: "Compile and run Unit tests"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
# gradle build can be memory hungry
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean test --stacktrace"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
- label: "Compile Android tests"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build can be memory hungry
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean assembleAndroidTest --stacktrace"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
- label: "Assemble GPlay Debug version"
|
||||
agents:
|
||||
# We use a xlarge sized instance instead of the normal small ones because
|
||||
# gradle build can be memory hungry
|
||||
queue: "xlarge"
|
||||
commands:
|
||||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
||||
artifact_paths:
|
||||
@ -18,12 +41,13 @@ steps:
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
- 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"
|
||||
# We use a xlarge sized instance instead of the normal small ones because
|
||||
# gradle build can be memory hungry
|
||||
queue: "xlarge"
|
||||
commands:
|
||||
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||
artifact_paths:
|
||||
@ -32,12 +56,13 @@ steps:
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
- label: "Build Google Play unsigned APK"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
# We use a xlarge sized instance instead of the normal small ones because
|
||||
# gradle build can be memory hungry
|
||||
queue: "xlarge"
|
||||
commands:
|
||||
- "./gradlew clean assembleGplayRelease --stacktrace"
|
||||
artifact_paths:
|
||||
@ -46,8 +71,18 @@ steps:
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
# Code quality
|
||||
|
||||
- label: "Code quality"
|
||||
command: "./tools/check/check_code_quality.sh"
|
||||
command:
|
||||
- "./tools/check/check_code_quality.sh"
|
||||
|
||||
- label: "ktlint"
|
||||
command:
|
||||
- "curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.34.2/ktlint && chmod a+x ktlint"
|
||||
- "./ktlint --android --experimental -v"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "openjdk"
|
||||
|
32
.editorconfig
Normal file
32
.editorconfig
Normal file
@ -0,0 +1,32 @@
|
||||
# For ktlint configuration. Ref: https://ktlint.github.io/
|
||||
|
||||
[*.{kt,kts}]
|
||||
# possible values: number (e.g. 2), "unset" (makes ktlint ignore indentation completely)
|
||||
indent_size=unset
|
||||
# true (recommended) / false
|
||||
insert_final_newline=true
|
||||
# possible values: number (e.g. 120) (package name, imports & comments are ignored), "off"
|
||||
# it's automatically set to 100 on `ktlint --android ...` (per Android Kotlin Style Guide)
|
||||
max_line_length=off
|
||||
|
||||
# Comma-separated list of rules to disable (Since 0.34.0)
|
||||
# Note that rules in any ruleset other than the standard ruleset will need to be prefixed
|
||||
# by the ruleset identifier.
|
||||
disabled_rules=no-wildcard-imports,no-multi-spaces,colon-spacing,chain-wrapping,import-ordering,experimental:annotation
|
||||
|
||||
# The following (so far identified) rules are kept:
|
||||
# no-blank-line-before-rbrace
|
||||
# final-newline
|
||||
# no-consecutive-blank-lines
|
||||
# comment-spacing
|
||||
# filename
|
||||
# comma-spacing
|
||||
# paren-spacing
|
||||
# op-spacing
|
||||
# string-template
|
||||
# no-unused-imports
|
||||
# curly-spacing
|
||||
# no-semi
|
||||
# no-empty-class-body
|
||||
# experimental:multiline-if-else
|
||||
# experimental:no-empty-first-line-in-method-block
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -7,4 +7,4 @@
|
||||
- [ ] Pull request is based on the develop branch
|
||||
- [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/riotX-android/blob/develop/CHANGES.md)
|
||||
- [ ] Pull request includes screenshots or videos if containing UI changes
|
||||
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off)
|
||||
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off)
|
||||
|
15
.gitignore
vendored
15
.gitignore
vendored
@ -1,14 +1,19 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.idea/*
|
||||
/.idea/*
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
# idea files: exclude everything except dictionnaries
|
||||
.idea/caches
|
||||
.idea/libraries
|
||||
.idea/*.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
||||
/tmp
|
||||
|
||||
ktlint
|
||||
.idea/copyright/New_vector.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
|
||||
.idea/copyright/New_Vector_Ltd.xml
|
||||
|
158
.idea/codeStyles/Project.xml
generated
Normal file
158
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,158 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" />
|
||||
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_ELVIS" value="true" />
|
||||
<option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="0" />
|
||||
<option name="IF_RPAREN_ON_NEW_LINE" value="false" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="0" />
|
||||
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="0" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="0" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="0" />
|
||||
<option name="ASSIGNMENT_WRAP" value="0" />
|
||||
<option name="CLASS_ANNOTATION_WRAP" value="0" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="1" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
6
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
6
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
28
.idea/dictionaries/bmarty.xml
generated
Normal file
28
.idea/dictionaries/bmarty.xml
generated
Normal file
@ -0,0 +1,28 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="bmarty">
|
||||
<words>
|
||||
<w>backstack</w>
|
||||
<w>bytearray</w>
|
||||
<w>checkables</w>
|
||||
<w>ciphertext</w>
|
||||
<w>coroutine</w>
|
||||
<w>decryptor</w>
|
||||
<w>emoji</w>
|
||||
<w>emojis</w>
|
||||
<w>fdroid</w>
|
||||
<w>gplay</w>
|
||||
<w>hmac</w>
|
||||
<w>ktlint</w>
|
||||
<w>linkified</w>
|
||||
<w>linkify</w>
|
||||
<w>megolm</w>
|
||||
<w>msisdn</w>
|
||||
<w>pbkdf</w>
|
||||
<w>pkcs</w>
|
||||
<w>signin</w>
|
||||
<w>signout</w>
|
||||
<w>signup</w>
|
||||
<w>threepid</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
35
AUTHORS.md
35
AUTHORS.md
@ -0,0 +1,35 @@
|
||||
A full developer contributors list can be found [here](https://github.com/vector-im/riotX-android/graphs/contributors).
|
||||
|
||||
# Core team:
|
||||
|
||||
Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves.
|
||||
|
||||
## Benoit: Android team leader
|
||||
|
||||
[@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org)
|
||||
- Android team leader and project leader, Android developer, GitHub community manager.
|
||||
- Specialist of the account creation, and many other fun features.
|
||||
- Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager.
|
||||
- Release manager on the Play Store
|
||||
|
||||
## François: Software architect
|
||||
|
||||
[@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org)
|
||||
- Software architect, Android developer
|
||||
- First developer on the project.
|
||||
- Work mainly on the global architecture of the project.
|
||||
- Specialist of the timeline, and lots of other features.
|
||||
|
||||
## Valere: Product manager, Android developer
|
||||
|
||||
[@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org)
|
||||
- Product manager, Android developer
|
||||
- Specialist on the crypto implementation.
|
||||
|
||||
# Other contributors
|
||||
|
||||
First of all, we thank all contributors who use RiotX and report problems on this GitHub project or via the integrated rageshake function.
|
||||
|
||||
We do not forget all translators, for their work of translating RiotX into many languages. They are also the authors of RiotX.
|
||||
|
||||
Feel free to add your name below, when you contribute to the project!
|
||||
|
277
CHANGES.md
277
CHANGES.md
@ -1,25 +1,268 @@
|
||||
Changes in RiotX 0.4.0 (2019-XX-XX)
|
||||
Changes in RiotX 0.14.2 (2020-02-02)
|
||||
===================================================
|
||||
|
||||
Fix RiotX not starting issue
|
||||
|
||||
Changes in RiotX 0.14.1 (2020-02-02)
|
||||
===================================================
|
||||
|
||||
Bugfix 🐛:
|
||||
- Cross-signing: fix UX issue when closing the bottom sheet verification (#813)
|
||||
- Room and room member profile: fix issues on dark and black themes
|
||||
|
||||
Changes in RiotX 0.14.0 (2020-02-01)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- First implementation of Cross-signing
|
||||
- Enable encryption in unencrypted rooms, from the room settings (#212)
|
||||
- Negotiate E2E by default for DMs (#907)
|
||||
|
||||
Improvements 🙌:
|
||||
- Sharing things to RiotX: sort list by recent room first (#771)
|
||||
- Hide the algorithm when turning on e2e (#897)
|
||||
- Sort room members by display names
|
||||
|
||||
Other changes:
|
||||
- Add support for /rainbow and /rainbowme commands (#879)
|
||||
|
||||
Build 🧱:
|
||||
- Ensure builds are reproducible (#842)
|
||||
- F-Droid: fix the "-dev" issue in version name (#815)
|
||||
|
||||
Changes in RiotX 0.13.0 (2020-01-17)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Send and render typing events (#564)
|
||||
- Create Room Profile screen (#54)
|
||||
- Create Room Member Profile screen (#59)
|
||||
|
||||
Improvements 🙌:
|
||||
- Render events m.room.encryption and m.room.guest_access in the timeline
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix broken background sync in F-Droid version
|
||||
- Fix issue with downloaded file on encrypted rooms. The file was not properly decrypted
|
||||
|
||||
Build 🧱:
|
||||
- Change the way versionCode is computed (#827)
|
||||
|
||||
Changes in RiotX 0.12.0 (2020-01-09)
|
||||
===================================================
|
||||
|
||||
Improvements 🙌:
|
||||
- The initial sync is now handled by a foreground service
|
||||
- Render aliases and canonical alias change in the timeline
|
||||
- Introduce developer mode in the settings (#745, #796)
|
||||
- Improve devices list screen
|
||||
- Add settings for rageshake sensibility
|
||||
- Fix autocompletion issues and add support for rooms, groups, and emoji (#780)
|
||||
- Show skip to bottom FAB while scrolling down (#752)
|
||||
- Enable encryption on a room, SDK part (#212)
|
||||
|
||||
Other changes:
|
||||
- Change the way RiotX identifies a session to allow the SDK to support several sessions with the same user (#800)
|
||||
- Exclude play-services-oss-licenses library from F-Droid build (#814)
|
||||
- Email domain can be limited on some homeservers, i18n of the displayed error (#754)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix crash when opening room creation screen from the room filtering screen
|
||||
- Fix avatar image disappearing (#777)
|
||||
- Fix read marker banner when permalink
|
||||
- Fix joining upgraded rooms (#697)
|
||||
- Fix matrix.org room directory not being browsable (#807)
|
||||
- Hide non working settings (#751)
|
||||
|
||||
Changes in RiotX 0.11.0 (2019-12-19)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Implement soft logout (#281)
|
||||
|
||||
Improvements 🙌:
|
||||
- Handle navigation to room via room alias (#201)
|
||||
- Open matrix.to link in RiotX (#57)
|
||||
- Limit sticker size in the timeline
|
||||
|
||||
Other changes:
|
||||
- Use same default room colors than Riot-Web
|
||||
|
||||
Bugfix 🐛:
|
||||
- Scroll breadcrumbs to top when opened
|
||||
- Render default room name when it starts with an emoji (#477)
|
||||
- Do not display " (IRC)" in display names https://github.com/vector-im/riot-android/issues/444
|
||||
- Fix rendering issue with HTML formatted body
|
||||
- Disable click on Stickers (#703)
|
||||
|
||||
Build 🧱:
|
||||
- Include diff-match-patch sources as dependency
|
||||
|
||||
Changes in RiotX 0.10.0 (2019-12-10)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Breadcrumbs: switch from one room to another quickly (#571)
|
||||
|
||||
Improvements 🙌:
|
||||
- Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744)
|
||||
|
||||
Other changes:
|
||||
- Add reason for all membership events (https://github.com/matrix-org/matrix-doc/pull/2367)
|
||||
|
||||
Bugfix 🐛:
|
||||
- When automardown is ON, pills are sent as MD in body (#739)
|
||||
- "ban" event are not rendered correctly (#716)
|
||||
- Fix crash when rotating screen in Room timeline
|
||||
|
||||
Changes in RiotX 0.9.1 (2019-12-05)
|
||||
===================================================
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix an issue with DB transaction (#740)
|
||||
|
||||
Changes in RiotX 0.9.0 (2019-12-05)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
|
||||
- Iteration of the login flow (#613)
|
||||
- [SDK] MSC2241 / verification in DMs (#707)
|
||||
|
||||
Improvements 🙌:
|
||||
- Send mention Pills from composer
|
||||
- Links in message preview in the bottom sheet are now active.
|
||||
- Rework the read marker to make it more usable
|
||||
|
||||
Other changes:
|
||||
- Fix a small grammatical error when an empty room list is shown.
|
||||
|
||||
Bugfix 🐛:
|
||||
- Do not show long click help if only invitation are displayed
|
||||
- Fix emoji filtering not working
|
||||
- Fix issue of closing Realm in another thread (#725)
|
||||
- Attempt to properly cancel the crypto module when user signs out (#724)
|
||||
|
||||
Changes in RiotX 0.8.0 (2019-11-19)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Handle long click on room in the room list (#395)
|
||||
- Ignore/UnIgnore users, and display list of ignored users (#542, #617)
|
||||
|
||||
Improvements 🙌:
|
||||
- Search reaction by name or keyword in emoji picker
|
||||
- Handle code tags (#567)
|
||||
- Support spoiler messages
|
||||
- Support m.sticker and m.room.join_rules events in timeline
|
||||
|
||||
Other changes:
|
||||
- Markdown set to off by default (#412)
|
||||
- Accessibility improvements to the attachment file type chooser
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix issues with some member events rendering (#498)
|
||||
- Passphrase does not match (Export room keys) (#644)
|
||||
- Ask for permission to write external storage when uri comes from the keyboard (#658)
|
||||
- Fix issue with english US/GB translation (#671)
|
||||
|
||||
Changes in RiotX 0.7.0 (2019-10-24)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Share elements from other app to RiotX (#58)
|
||||
- Read marker (#84)
|
||||
- Add ability to report content (#515)
|
||||
|
||||
Improvements:
|
||||
- Persist active tab between sessions (#503)
|
||||
- Do not upload file too big for the homeserver (#587)
|
||||
- Attachments: start using system pickers (#52)
|
||||
- Mark all messages as read (#396)
|
||||
|
||||
|
||||
Other changes:
|
||||
- Accessibility improvements to read receipts in the room timeline and reactions emoji chooser
|
||||
|
||||
Bugfix:
|
||||
- Fix issue on upload error in loop (#587)
|
||||
- Fix opening a permalink: the targeted event is displayed twice (#556)
|
||||
- Fix opening a permalink paginates all the history up to the last event (#282)
|
||||
- after login, the icon in the top left is a green 'A' for (all communities) rather than my avatar (#267)
|
||||
- Picture uploads are unreliable, pictures are shown in wrong aspect ratio on desktop client (#517)
|
||||
- Invitation notifications are not dismissed automatically if room is joined from another client (#347)
|
||||
- Opening links from RiotX reuses browser tab (#599)
|
||||
|
||||
Changes in RiotX 0.6.1 (2019-09-24)
|
||||
===================================================
|
||||
|
||||
Bugfix:
|
||||
- Fix crash: MergedHeaderItem was missing dimensionConverter
|
||||
|
||||
Changes in RiotX 0.6.0 (2019-09-24)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Save draft of a message when exiting a room with non empty composer (#329)
|
||||
|
||||
Improvements:
|
||||
- Add unread indent on room list (#485)
|
||||
- Message Editing: Update notifications (#128)
|
||||
- Remove any notification of a redacted event (#563)
|
||||
|
||||
Other changes:
|
||||
- Fix a few accessibility issues
|
||||
|
||||
Bugfix:
|
||||
- Fix characters erased from the Search field when the result are coming (#545)
|
||||
- "No connection" banner was displayed by mistake
|
||||
- Leaving community (from another client) has no effect on RiotX (#497)
|
||||
- Push rules was not retrieved after a clear cache
|
||||
- m.notice messages trigger push notifications (#238)
|
||||
- Embiggen messages with multiple emojis also for edited messages (#458)
|
||||
|
||||
Build:
|
||||
- Fix (again) issue with bad versionCode generated by Buildkite (#553)
|
||||
|
||||
Changes in RiotX 0.5.0 (2019-09-17)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Implementation of login to homeserver with SSO (#557)
|
||||
- Handle M_CONSENT_NOT_GIVEN error (#64)
|
||||
- Auto configure homeserver and identity server URLs of LoginActivity with a magic link
|
||||
|
||||
Improvements:
|
||||
- Reduce default release build log level, and lab option to enable more logs.
|
||||
- Display a no network indicator when there is no network (#559)
|
||||
|
||||
Bugfix:
|
||||
- Fix crash due to missing informationData (#535)
|
||||
- Progress in initial sync dialog is decreasing for a step and should not (#532)
|
||||
- Fix rendering issue of accepted third party invitation event
|
||||
- All current notifications were dismissed by mistake when the app is launched from the launcher
|
||||
|
||||
Build:
|
||||
- Fix issue with version name (#533)
|
||||
- Fix issue with bad versionCode generated by Buildkite (#553)
|
||||
|
||||
Changes in RiotX 0.4.0 (2019-08-30)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Display read receipts in timeline (#81)
|
||||
|
||||
Improvements:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
- 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)
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
-
|
||||
- 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)
|
||||
===================================================
|
||||
@ -90,24 +333,24 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
|
||||
=======================================================
|
||||
|
||||
|
||||
Changes in RiotX 0.0.0 (2019-XX-XX)
|
||||
Changes in RiotX 0.X.0 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
Features ✨:
|
||||
-
|
||||
|
||||
Improvements:
|
||||
Improvements 🙌:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix:
|
||||
Bugfix 🐛:
|
||||
-
|
||||
|
||||
Translations:
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
Build:
|
||||
Build 🧱:
|
||||
-
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Contributing code to Matrix
|
||||
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md
|
||||
|
||||
Android support can be found in this [](https://matrix.to/#/#riot-android:matrix.org) room.
|
||||
|
||||
@ -11,6 +11,7 @@ Dedicated room for RiotX: [![RiotX Android Matrix room #riot-android:matrix.org]
|
||||
## Android Studio settings
|
||||
|
||||
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).
|
||||
Please ensure that your using the project formatting rules (which are in the project at .idea/codeStyles/), and format the file before committing them.
|
||||
|
||||
## Compilation
|
||||
|
||||
@ -40,19 +41,45 @@ Please add a line to the top of the file `CHANGES.md` describing your change.
|
||||
|
||||
Make sure the following commands execute without any error:
|
||||
|
||||
> ./tools/check/check_code_quality.sh
|
||||
#### Internal tool
|
||||
|
||||
> ./gradlew lintGplayRelease
|
||||
<pre>
|
||||
./tools/check/check_code_quality.sh
|
||||
</pre>
|
||||
|
||||
#### ktlint
|
||||
|
||||
<pre>
|
||||
curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.34.2/ktlint && chmod a+x ktlint
|
||||
./ktlint --android --experimental -v
|
||||
</pre>
|
||||
|
||||
Note that you can run
|
||||
|
||||
<pre>
|
||||
./ktlint --android --experimental -v -F
|
||||
</pre>
|
||||
|
||||
For ktlint to fix some detected errors for you (you still have to check and commit the fix of course)
|
||||
|
||||
#### lint
|
||||
|
||||
<pre>
|
||||
./gradlew lintGplayRelease
|
||||
./gradlew lintFdroidRelease
|
||||
</pre>
|
||||
|
||||
### Unit tests
|
||||
|
||||
Make sure the following commands execute without any error:
|
||||
|
||||
> ./gradlew testGplayReleaseUnitTest
|
||||
<pre>
|
||||
./gradlew testGplayReleaseUnitTest
|
||||
</pre>
|
||||
|
||||
### Tests
|
||||
|
||||
RiotX is currently supported on Android Jelly Bean (API 16+): please test your change on an Android device (or Android emulator) running with API 16. Many issues can happen (including crashes) on older devices.
|
||||
RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices.
|
||||
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
||||
|
||||
### Internationalisation
|
||||
@ -60,6 +87,10 @@ Also, if possible, please test your change on a real device. Testing on Android
|
||||
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||
Do not hesitate to use plurals when appropriate.
|
||||
|
||||
### Accessibility
|
||||
|
||||
Please consider accessibility as an important point. As a minimum requirement, in layout XML files please use attributes such as `android:contentDescription` and `android:importantForAccessibility`, and test with a screen reader if it's working well. You can add new string resources, dedicated to accessibility, in this case, please prefix theirs id with `a11y_`.
|
||||
|
||||
### Layout
|
||||
|
||||
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.
|
||||
|
@ -12,12 +12,13 @@ RiotX is an Android Matrix Client currently in beta but in active development.
|
||||
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.riotx)
|
||||
|
||||
Nightly build: [](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.
|
||||
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 SDK is stable enough.
|
||||
|
||||
|
||||
# Roadmap
|
||||
|
23
build.gradle
23
build.gradle
@ -1,9 +1,7 @@
|
||||
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.kotlin_version = '1.3.50'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
@ -12,11 +10,11 @@ buildscript {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
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 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.google.gms:google-services:4.3.2'
|
||||
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2'
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
@ -47,12 +45,6 @@ allprojects {
|
||||
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 {
|
||||
@ -61,6 +53,11 @@ allprojects {
|
||||
]
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
// Warnings are potential errors, so stop ignoring them
|
||||
kotlinOptions.allWarningsAsErrors = true
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
extensions.findByName("kapt")?.arguments {
|
||||
arg("dagger.gradle.incremental", "enabled")
|
||||
|
1
diff-match-patch/.gitignore
vendored
Normal file
1
diff-match-patch/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
8
diff-match-patch/build.gradle
Normal file
8
diff-match-patch/build.gradle
Normal file
@ -0,0 +1,8 @@
|
||||
apply plugin: 'java-library'
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
sourceCompatibility = "8"
|
||||
targetCompatibility = "8"
|
File diff suppressed because it is too large
Load Diff
260
docs/signin.md
Normal file
260
docs/signin.md
Normal file
@ -0,0 +1,260 @@
|
||||
# Sign in to a homeserver
|
||||
|
||||
This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver.
|
||||
|
||||
## Sign up flows
|
||||
|
||||
### Get the flow
|
||||
|
||||
Client request the sign-in flows, once the homeserver is chosen by the user and its url is known (in the example it's `https://matrix.org`)
|
||||
|
||||
> curl -X GET 'https://matrix.org/_matrix/client/r0/login'
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.password"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Login with username
|
||||
|
||||
The user is able to connect using `m.login.password`
|
||||
|
||||
> curl -X POST --data $'{"identifier":{"type":"m.id.user","user":"alice"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
|
||||
|
||||
```json
|
||||
{
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": "alice"
|
||||
},
|
||||
"password": "weak_password",
|
||||
"type": "m.login.password",
|
||||
"initial_device_display_name": "Portable"
|
||||
}
|
||||
```
|
||||
|
||||
#### Incorrect password
|
||||
|
||||
403
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid password"
|
||||
}
|
||||
```
|
||||
|
||||
#### Correct password:
|
||||
|
||||
We get credential (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@benoit0816:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "GTVREDALBF",
|
||||
"well_known": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https:\/\/matrix.org\/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Login with email
|
||||
|
||||
If the user has associated an email with its account, he can signin using the email.
|
||||
|
||||
> curl -X POST --data $'{"identifier":{"type":"m.id.thirdparty","medium":"email","address":"alice@yopmail.com"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
|
||||
|
||||
```json
|
||||
{
|
||||
"identifier": {
|
||||
"type": "m.id.thirdparty",
|
||||
"medium": "email",
|
||||
"address": "alice@yopmail.com"
|
||||
},
|
||||
"password": "weak_password",
|
||||
"type": "m.login.password",
|
||||
"initial_device_display_name": "Portable"
|
||||
}
|
||||
```
|
||||
|
||||
#### Unknown email
|
||||
|
||||
403
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
#### Known email, wrong password
|
||||
|
||||
403
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid password"
|
||||
}
|
||||
```
|
||||
|
||||
##### Known email, correct password
|
||||
|
||||
We get the credentials (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@alice:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNjtDY0MwRlNPSFFoOC5wOgowMDJmc2lnbmF0dXJlIGiTRm1mYLLxQywxOh3qzQVT8HoEorSokEP2u-bAwtnYCg",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "WBSREDASND",
|
||||
"well_known": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https:\/\/matrix.org\/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Login with Msisdn
|
||||
|
||||
Not supported yet in RiotX
|
||||
|
||||
### Login with SSO
|
||||
|
||||
> curl -X GET 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.sso"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In this case, the user can click on "Sign in with SSO" and the web screen will be displayed on the page `https://homeserver.with.sso/_matrix/static/client/login/` and the credentials will be passed back to the native code through the JS bridge
|
||||
|
||||
## Reset password
|
||||
|
||||
Ref: `https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-password-email-requesttoken`
|
||||
|
||||
When the user has forgotten his password, he can reset it by providing an email and a new password.
|
||||
|
||||
Here is the flow:
|
||||
|
||||
### Send email
|
||||
|
||||
User is asked to enter the email linked to his account and a new password.
|
||||
We display a warning regarding e2e.
|
||||
|
||||
At the first step, we do not send the password, only the email and a client secret, generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","send_attempt":0,"email":"user@domain.com"}' 'https://matrix.org/_matrix/client/r0/account/password/email/requestToken'
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
|
||||
"send_attempt": 0,
|
||||
"email": "user@domain.com"
|
||||
}
|
||||
```
|
||||
|
||||
#### When the email is not known
|
||||
|
||||
We get a 400
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_THREEPID_NOT_FOUND",
|
||||
"error": "Email not found"
|
||||
}
|
||||
```
|
||||
|
||||
#### When the email is known
|
||||
|
||||
We get a 200 with a `sid`
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "tQNbrREDACTEDldA"
|
||||
}
|
||||
```
|
||||
|
||||
Then the user is asked to click on the link in the email he just received, and to confirm when it's done.
|
||||
|
||||
During this step, the new password is sent to the homeserver.
|
||||
|
||||
If the user confirms before the link is clicked, we get an error:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"type": "m.login.email.identity",
|
||||
"threepid_creds": {
|
||||
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
|
||||
"sid": "tQNbrREDACTEDldA"
|
||||
}
|
||||
},
|
||||
"new_password": "weak_password"
|
||||
}
|
||||
```
|
||||
|
||||
401
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_UNAUTHORIZED",
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
### User clicks on the link
|
||||
|
||||
The link has the form:
|
||||
|
||||
https://matrix.org/_matrix/client/unstable/password_reset/email/submit_token?token=fzZLBlcqhTKeaFQFSRbsQnQCkzbwtGAD&client_secret=6c57f284-85e2-421b-8270-fb1795a120a7&sid=tQNbrREDACTEDldA
|
||||
|
||||
It contains the client secret, a token and the sid
|
||||
|
||||
When the user click the link, if validate his ownership and the new password can now be ent by the application (on user demand):
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"type": "m.login.email.identity",
|
||||
"threepid_creds": {
|
||||
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
|
||||
"sid": "tQNbrREDACTEDldA"
|
||||
}
|
||||
},
|
||||
"new_password": "weak_password"
|
||||
}
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
The password has been changed, and all the existing token are invalidated. User can now login with the new password.
|
579
docs/signup.md
Normal file
579
docs/signup.md
Normal file
@ -0,0 +1,579 @@
|
||||
# Sign up to a homeserver
|
||||
|
||||
This document describes the flow of registration to a homeserver. Examples come from the `matrix.org` homeserver.
|
||||
|
||||
*Ref*: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
|
||||
|
||||
## Sign up flows
|
||||
|
||||
### First step
|
||||
|
||||
Client request the sign-up flows, once the homeserver is chosen by the user and its url is known (in the example it's `https://matrix.org`)
|
||||
|
||||
> curl -X POST --data $'{}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
We get the flows with a 401, which also means that the registration is possible on this homeserver.
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "vwehdKMtkRedactedAMwgCACZ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the registration is not possible, we get a 403
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Registration is disabled"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1: entering user name and password
|
||||
|
||||
The app is displaying a form to enter username and password.
|
||||
|
||||
> curl -X POST --data $'{"initial_device_display_name":"Mobile device","username":"alice","password": "weak_password"}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"initial_device_display_name": "Mobile device",
|
||||
"username": "alice",
|
||||
"password": "weak_password"
|
||||
}
|
||||
```
|
||||
|
||||
401. Note that the `session` value has changed (because we did not provide the previous value in the request body), but it's ok, we will use the new value for the next steps.
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### If username already exists
|
||||
|
||||
We get a 400:
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_USER_IN_USE",
|
||||
"error": "User ID already taken."
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: entering email
|
||||
|
||||
User is proposed to enter an email. We skip this step.
|
||||
|
||||
> curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.dummy"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.dummy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
401
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [
|
||||
"m.login.dummy"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2 bis: we enter an email
|
||||
|
||||
We request a token to the homeserver. The `client_secret` is generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","email":"alice@yopmail.com","send_attempt":0}' 'https://matrix.org/_matrix/client/r0/register/email/requestToken'
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
|
||||
"email": "alice@yopmail.com",
|
||||
"send_attempt": 0
|
||||
}
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "qlBCREDACTEDEtgxD"
|
||||
}
|
||||
```
|
||||
|
||||
And
|
||||
|
||||
> curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"threepid_creds": {
|
||||
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
|
||||
"sid": "qlBCREDACTEDEtgxD"
|
||||
},
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.email.identity"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We get 401 since the email is not validated yet:
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_UNAUTHORIZED",
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
The app is now polling on
|
||||
|
||||
> curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"threepid_creds": {
|
||||
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
|
||||
"sid": "qlBCREDACTEDEtgxD"
|
||||
},
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.email.identity"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We click on the link received by email `https://matrix.org/_matrix/client/unstable/registration/email/submit_token?token=vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ&client_secret=53e679ea-oRED-ACTED-92b8-3012c49c6cfa&sid=qlBCREDACTEDEtgxD` which contains:
|
||||
- A `token` vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ
|
||||
- The `client_secret`: 53e679ea-oRED-ACTED-92b8-3012c49c6cfa
|
||||
- A `sid`: qlBCREDACTEDEtgxD
|
||||
|
||||
Once the link is clicked, the registration request (polling) returns a 401 with the following content:
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Accepting T&C
|
||||
|
||||
User is proposed to accept T&C and he accepts them
|
||||
|
||||
> curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.terms"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.terms"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
401
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [
|
||||
"m.login.dummy",
|
||||
"m.login.terms"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Captcha
|
||||
|
||||
User is proposed to prove he is not a robot and he does it:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"response":"03AOLTBLSiGS9GhFDpAMblJ2nlXOmHXqAYJ5OvHCPUjiVLBef3k9snOYI_BDC32-t4D2jv-tpvkaiEI_uloobFd9RUTPpJ7con2hMddbKjSCYqXqcUQFhzhbcX6kw8uBnh2sbwBe80_ihrHGXEoACXQkL0ki1Q0uEtOeW20YBRjbNABsZPpLNZhGIWC0QVXnQ4FouAtZrl3gOAiyM-oG3cgP6M9pcANIAC_7T2P2amAHbtsTlSR9CsazNyS-rtDR9b5MywdtnWN9Aw8fTJb8cXQk_j7nvugMxzofPjSOrPKcr8h5OqPlpUCyxxnFtag6cuaPSUwh43D2L0E-ZX7djzaY2Yh_U2n6HegFNPOQ22CJmfrKwDlodmAfMPvAXyq77n3HpoREDACTEDo3830RHF4BfkGXUaZjctgg-A1mvC17hmQmQpkG7IhDqyw0onU-0vF_-ehCjq_CcQEDpS_O3uiHJaG5xGf-0rhLm57v_wA3deugbsZuO4uTuxZZycN_mKxZ97jlDVBetl9hc_5REPbhcT1w3uzTCSx7Q","session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.recaptcha"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"response": "03AOLTBLSiGS9GhFDpAMblJ2nlXOmHXqAYJ5OvHCPUjiVLBef3k9snOYI_BDC32-t4D2jv-tpvkaiEI_uloobFd9RUTPpJ7con2hMddbKjSCYqXqcUQFhzhbcX6kw8uBnh2sbwBe80_ihrHGXEoACXQkL0ki1Q0uEtOeW20YBRjbNABsZPpLNZhGIWC0QVXnQ4FouAtZrl3gOAiyM-oG3cgP6M9pcANIAC_7T2P2amAHbtsTlSR9CsazNyS-rtDR9b5MywdtnWN9Aw8fTJb8cXQk_j7nvugMxzofPjSOrPKcr8h5OqPlpUCyxxnFtag6cuaPSUwh43D2L0E-ZX7djzaY2Yh_U2n6HegFNPOQ22CJmfrKwDlodmAfMPvAXyq77n3HpoREDACTEDo3830RHF4BfkGXUaZjctgg-A1mvC17hmQmQpkG7IhDqyw0onU-0vF_-ehCjq_CcQEDpS_O3uiHJaG5xGf-0rhLm57v_wA3deugbsZuO4uTuxZZycN_mKxZ97jlDVBetl9hc_5REPbhcT1w3uzTCSx7Q",
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.recaptcha"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@alice:matrix.org",
|
||||
"home_server": "matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmcKMoREDACTEDo50aWZpZXIga2V5CjAwMTBjaWQgZ2VuID0gMQowMDI5Y2lkIHVzZXJfaWQgPSBAYmVub2l0eHh4eDptYXRoREDACTEDoCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNHVSVm00aVFDaWlKdoREDACTEDoJmc2lnbmF0dXJlIOmHnTLRfxiPjhrWhS-dThUX-qAzZktfRThzH1YyAsxaCg",
|
||||
"device_id": "FLBAREDAJZ"
|
||||
}
|
||||
```
|
||||
|
||||
The account is created!
|
||||
|
||||
### Step 5: MSISDN
|
||||
|
||||
Some homeservers may require the user to enter MSISDN.
|
||||
|
||||
On matrix.org, it's not required, and not even optional, but it's still possible for the app to add a MSISDN during the registration.
|
||||
|
||||
The user enter a phone number and select a country, the `client_secret` is generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","send_attempt":1,"country":"FR","phone_number":"+33611223344"}' 'https://matrix.org/_matrix/client/r0/register/msisdn/requestToken'
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
|
||||
"send_attempt": 1,
|
||||
"country": "FR",
|
||||
"phone_number": "+33611223344"
|
||||
}
|
||||
```
|
||||
|
||||
If the msisdn is already associated to another account, you will received an error:
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_THREEPID_IN_USE",
|
||||
"error": "Phone number is already in use"
|
||||
}
|
||||
```
|
||||
|
||||
If it is not the case, the homeserver send the SMS and returns some data, especially a `sid` and a `submit_url`:
|
||||
|
||||
```json
|
||||
{
|
||||
"msisdn": "33611223344",
|
||||
"intl_fmt": "+336 11 22 33 44",
|
||||
"success": true,
|
||||
"sid": "1678881798",
|
||||
"submit_url": "https:\/\/matrix.org\/_matrix\/client\/unstable\/add_threepid\/msisdn\/submit_token"
|
||||
}
|
||||
```
|
||||
|
||||
When you execute the register request, with the received `sid`, you get an error since the MSISDN is not validated yet:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
|
||||
```json
|
||||
"auth": {
|
||||
"type": "m.login.msisdn",
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"threepid_creds": {
|
||||
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
|
||||
"sid": "1678881798"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There is an issue on Synapse, which return a 401, it sends too much data along with the classical MatrixError fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAABGdGmruw6DdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [],
|
||||
"error": "",
|
||||
"errcode": "M_UNAUTHORIZED"
|
||||
}
|
||||
```
|
||||
|
||||
The user receive the SMS, he can enter the SMS code in the app, which is sent using the "submit_url" received ie the response of the `requestToken` request:
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798","token":"123456"}' 'https://matrix.org/_matrix/client/unstable/add_threepid/msisdn/submit_token'
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
|
||||
"sid": "1678881798",
|
||||
"token": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
If the code is not correct, we get a 200 with:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
And if the code is correct we get a 200 with:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
We can now execute the registration request, to the homeserver
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"type": "m.login.msisdn",
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"threepid_creds": {
|
||||
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
|
||||
"sid": "1678881798"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now the homeserver consider that the `m.login.msisdn` step is completed (401):
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAABGdGmruw6DdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [
|
||||
"m.login.msisdn"
|
||||
]
|
||||
}
|
||||
```
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Tue Mar 19 09:53:05 CET 2019
|
||||
#Fri Sep 27 10:10:35 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
|
@ -5,16 +5,15 @@ apply plugin: 'kotlin-kapt'
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
// Multidex is useful for tests
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@ -29,17 +28,22 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":matrix-sdk-android")
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
// Paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||
|
||||
// Logging
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
@ -1,42 +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.rx;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("im.vector.matrix.rx.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.MainThreadDisposable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
private class LiveDataObservable<T>(
|
||||
@ -60,4 +59,4 @@ private class LiveDataObservable<T>(
|
||||
|
||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.util.Optional
|
||||
import io.reactivex.Observable
|
||||
|
||||
fun <T : Any> Observable<Optional<T>>.unwrap(): Observable<T> {
|
||||
return filter { it.hasValue() }.map { it.get() }
|
||||
}
|
||||
|
||||
fun <T : Any, U : Any> Observable<Optional<T>>.mapOptional(fn: (T) -> U?): Observable<Optional<U>> {
|
||||
return map {
|
||||
it.map(fn)
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.Completable
|
||||
import io.reactivex.Single
|
||||
|
||||
fun <T> singleBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Single<T> = Single.create {
|
||||
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
|
||||
override fun onSuccess(data: T) {
|
||||
it.onSuccess(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
it.tryOnError(failure)
|
||||
}
|
||||
}
|
||||
val cancelable = builder(callback)
|
||||
it.setCancellable {
|
||||
cancelable.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> completableBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Completable = Completable.create {
|
||||
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
|
||||
override fun onSuccess(data: T) {
|
||||
it.onComplete()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
it.tryOnError(failure)
|
||||
}
|
||||
}
|
||||
val cancelable = builder(callback)
|
||||
it.setCancellable {
|
||||
cancelable.cancel()
|
||||
}
|
||||
}
|
@ -16,46 +16,160 @@
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
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.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.BiFunction
|
||||
import timber.log.Timber
|
||||
|
||||
class RxRoom(private val room: Room) {
|
||||
class RxRoom(private val room: Room, private val session: Session) {
|
||||
|
||||
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||
return room.liveRoomSummary().asObservable()
|
||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||
val summaryObservable = room.getRoomSummaryLive()
|
||||
.asObservable()
|
||||
.startWith(room.roomSummary().toOptional())
|
||||
.doOnNext { Timber.v("RX: summary emitted for: ${it.getOrNull()?.roomId}") }
|
||||
|
||||
val memberIdsChangeObservable = summaryObservable
|
||||
.map {
|
||||
it.getOrNull()?.let { roomSummary ->
|
||||
if (roomSummary.isEncrypted) {
|
||||
// Return the list of other users
|
||||
roomSummary.otherMemberIds + listOf(session.myUserId)
|
||||
} else {
|
||||
// Return an empty list, the room is not encrypted
|
||||
emptyList()
|
||||
}
|
||||
}.orEmpty()
|
||||
}.distinctUntilChanged()
|
||||
.doOnNext { Timber.v("RX: memberIds emitted. Size: ${it.size}") }
|
||||
|
||||
// Observe the device info of the users in the room
|
||||
val cryptoDeviceInfoObservable = memberIdsChangeObservable
|
||||
.switchMap { membersIds ->
|
||||
session.getLiveCryptoDeviceInfo(membersIds)
|
||||
.asObservable()
|
||||
.map {
|
||||
// If any key change, emit the userIds list
|
||||
membersIds
|
||||
}
|
||||
.startWith(membersIds)
|
||||
.doOnNext { Timber.v("RX: CryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||
}
|
||||
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted 2. Size: ${it.size}") }
|
||||
|
||||
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
|
||||
.map { userIds ->
|
||||
if (userIds.isEmpty()) {
|
||||
Optional<RoomEncryptionTrustLevel>(null)
|
||||
} else {
|
||||
session.getCrossSigningService().getTrustLevelForUsers(userIds).toOptional()
|
||||
}
|
||||
}
|
||||
.doOnNext { Timber.v("RX: roomEncryptionTrustLevel emitted: ${it.getOrNull()?.name}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
|
||||
summaryObservable,
|
||||
roomEncryptionTrustLevelObservable,
|
||||
BiFunction { summary, level ->
|
||||
summary.getOrNull()?.copy(
|
||||
roomEncryptionTrustLevel = level.getOrNull()
|
||||
).toOptional()
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.v("RX: final room summary emitted for ${it.getOrNull()?.roomId}") }
|
||||
}
|
||||
|
||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||
return room.getRoomMemberIdsLive().asObservable()
|
||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
|
||||
.startWith(room.getRoomMembers(queryParams))
|
||||
.doOnNext { Timber.v("RX: room members emitted. Size: ${it.size}") }
|
||||
|
||||
// TODO Do it only for room members of the room (switchMap)
|
||||
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||
.startWith(emptyList<CryptoDeviceInfo>())
|
||||
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<List<RoomMemberSummary>, List<CryptoDeviceInfo>, List<RoomMemberSummary>>(
|
||||
roomMembersObservable,
|
||||
cryptoDeviceInfoObservable,
|
||||
BiFunction { summaries, _ ->
|
||||
summaries.map {
|
||||
if (room.isEncrypted()) {
|
||||
it.copy(
|
||||
// Get the trust level of a virtual room with only this user
|
||||
userEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(listOf(it.userId))
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.v("RX: final room members emitted. Size: ${it.size}") }
|
||||
}
|
||||
|
||||
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||
return room.getEventSummaryLive(eventId).asObservable()
|
||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
||||
.startWith(room.getEventAnnotationsSummary(eventId).toOptional())
|
||||
}
|
||||
|
||||
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
||||
return room.liveTimeLineEvent(eventId).asObservable()
|
||||
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
||||
return room.getTimeLineEventLive(eventId).asObservable()
|
||||
.startWith(room.getTimeLineEvent(eventId).toOptional())
|
||||
}
|
||||
|
||||
fun loadRoomMembersIfNeeded(): Single<Unit> = Single.create {
|
||||
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
|
||||
fun liveStateEvent(eventType: String): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType).asObservable()
|
||||
.startWith(room.getStateEvent(eventType).toOptional())
|
||||
}
|
||||
|
||||
fun joinRoom(viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||
fun liveReadMarker(): Observable<Optional<String>> {
|
||||
return room.getReadMarkerLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveReadReceipt(): Observable<Optional<String>> {
|
||||
return room.getMyReadReceiptLive().asObservable()
|
||||
}
|
||||
|
||||
fun loadRoomMembersIfNeeded(): Single<Unit> = singleBuilder {
|
||||
room.loadRoomMembersIfNeeded(it)
|
||||
}
|
||||
|
||||
fun joinRoom(reason: String? = null,
|
||||
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
|
||||
room.join(reason, viaServers, it)
|
||||
}
|
||||
|
||||
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||
}
|
||||
|
||||
fun liveDrafts(): Observable<List<UserDraft>> {
|
||||
return room.getDraftsLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||
return room.getLiveRoomNotificationState().asObservable()
|
||||
}
|
||||
}
|
||||
|
||||
fun Room.rx(): RxRoom {
|
||||
return RxRoom(this)
|
||||
}
|
||||
fun Room.rx(session: Session): RxRoom {
|
||||
return RxRoom(this, session)
|
||||
}
|
||||
|
@ -18,57 +18,125 @@ package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.BiFunction
|
||||
import timber.log.Timber
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
||||
return session.liveRoomSummaries().asObservable()
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
|
||||
.startWith(session.getRoomSummaries(queryParams))
|
||||
.doOnNext { Timber.v("RX: summaries emitted: size: ${it.size}") }
|
||||
|
||||
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||
.startWith(emptyList<CryptoDeviceInfo>())
|
||||
.doOnNext { Timber.v("RX: crypto device info emitted: size: ${it.size}") }
|
||||
|
||||
return Observable
|
||||
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
|
||||
summariesObservable,
|
||||
cryptoDeviceInfoObservable,
|
||||
BiFunction { summaries, _ ->
|
||||
summaries.map {
|
||||
if (it.isEncrypted) {
|
||||
it.copy(
|
||||
roomEncryptionTrustLevel = session.getCrossSigningService()
|
||||
.getTrustLevelForUsers(it.otherMemberIds + session.myUserId)
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.doOnNext { Timber.d("RX: final summaries emitted: size: ${it.size}") }
|
||||
}
|
||||
|
||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
||||
return session.liveGroupSummaries().asObservable()
|
||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||
return session.getGroupSummariesLive(queryParams).asObservable()
|
||||
.startWith(session.getGroupSummaries(queryParams))
|
||||
}
|
||||
|
||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||
return session.getBreadcrumbsLive().asObservable()
|
||||
.startWith(session.getBreadcrumbs())
|
||||
}
|
||||
|
||||
fun liveSyncState(): Observable<SyncState> {
|
||||
return session.syncState().asObservable()
|
||||
return session.getSyncStateLive().asObservable()
|
||||
}
|
||||
|
||||
fun livePushers(): Observable<List<Pusher>> {
|
||||
return session.livePushers().asObservable()
|
||||
return session.getPushersLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||
return session.getUserLive(userId).asObservable()
|
||||
.startWith(session.getUser(userId).toOptional())
|
||||
}
|
||||
|
||||
fun liveUsers(): Observable<List<User>> {
|
||||
return session.liveUsers().asObservable()
|
||||
return session.getUsersLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveIgnoredUsers(): Observable<List<User>> {
|
||||
return session.getIgnoredUsersLive().asObservable()
|
||||
}
|
||||
|
||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||
return session.livePagedUsers(filter).asObservable()
|
||||
return session.getPagedUsersLive(filter).asObservable()
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create {
|
||||
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||
session.createRoom(roomParams, it)
|
||||
}
|
||||
|
||||
fun searchUsersDirectory(search: String,
|
||||
limit: Int,
|
||||
excludedUserIds: Set<String>): Single<List<User>> = Single.create {
|
||||
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
|
||||
excludedUserIds: Set<String>): Single<List<User>> = singleBuilder {
|
||||
session.searchUsersDirectory(search, limit, excludedUserIds, it)
|
||||
}
|
||||
|
||||
fun joinRoom(roomId: String, viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||
session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||
fun joinRoom(roomId: String,
|
||||
reason: String? = null,
|
||||
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
|
||||
session.joinRoom(roomId, reason, viaServers, it)
|
||||
}
|
||||
|
||||
fun getRoomIdByAlias(roomAlias: String,
|
||||
searchOnServer: Boolean): Single<Optional<String>> = singleBuilder {
|
||||
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
|
||||
}
|
||||
|
||||
fun getProfileInfo(userId: String): Single<JsonDict> = singleBuilder {
|
||||
session.getProfile(userId, it)
|
||||
}
|
||||
|
||||
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
|
||||
return session.getLiveCryptoDeviceInfo(userId).asObservable()
|
||||
}
|
||||
|
||||
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
|
||||
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable()
|
||||
.startWith(session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional())
|
||||
}
|
||||
}
|
||||
|
||||
fun Session.rx(): RxSession {
|
||||
return RxSession(this)
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "io.realm:realm-gradle-plugin:5.12.0"
|
||||
classpath "io.realm:realm-gradle-plugin:6.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,10 +67,14 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
static def gitRevision() {
|
||||
def cmd = "git rev-parse --short HEAD"
|
||||
def cmd = "git rev-parse --short=8 HEAD"
|
||||
return cmd.execute().text.trim()
|
||||
}
|
||||
|
||||
@ -86,41 +90,42 @@ static def gitRevisionDate() {
|
||||
|
||||
dependencies {
|
||||
|
||||
def arrow_version = "0.8.0"
|
||||
def support_version = '1.1.0-beta01'
|
||||
def arrow_version = "0.8.2"
|
||||
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'
|
||||
def lifecycle_version = '2.1.0'
|
||||
def coroutines_version = "1.3.2"
|
||||
def markwon_version = '3.1.0'
|
||||
def daggerVersion = '2.24'
|
||||
|
||||
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:1.1.0-rc01"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0-beta01"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||
|
||||
// Network
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.6.2'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
|
||||
implementation 'com.novoda:merlin:1.2.0'
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||
|
||||
implementation "ru.noties.markwon:core:$markwon_version"
|
||||
|
||||
// Image
|
||||
implementation 'androidx.exifinterface:exifinterface:1.1.0'
|
||||
|
||||
// Database
|
||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
@ -132,21 +137,25 @@ dependencies {
|
||||
// DI
|
||||
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'
|
||||
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0'
|
||||
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
|
||||
|
||||
// Logging
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
|
||||
|
||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
|
||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0'
|
||||
// Bus
|
||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||
|
||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.robolectric:robolectric:4.0.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.3'
|
||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
|
||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
@ -156,7 +165,8 @@ dependencies {
|
||||
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"
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
|
||||
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
|
@ -17,15 +17,16 @@
|
||||
package im.vector.matrix.android
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import java.io.File
|
||||
|
||||
interface InstrumentedTest {
|
||||
|
||||
fun context(): Context {
|
||||
return InstrumentationRegistry.getTargetContext()
|
||||
return ApplicationProvider.getApplicationContext()
|
||||
}
|
||||
|
||||
fun cacheDir(): File {
|
||||
return context().cacheDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,4 @@ class OkReplayRuleChainNoActivity(
|
||||
return RuleChain.outerRule(PermissionRule(configuration))
|
||||
.around(RecorderRule(configuration))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,5 +18,8 @@ package im.vector.matrix.android
|
||||
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main)
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main,
|
||||
Executors.newSingleThreadExecutor().asCoroutineDispatcher())
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.account
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class AccountCreationTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
@Test
|
||||
fun createAccountTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
|
||||
commonTestHelper.signout(session)
|
||||
|
||||
session.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createAccountAndLoginAgainTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
|
||||
// Log again to the same account
|
||||
val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true))
|
||||
|
||||
session.close()
|
||||
session2.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun simpleE2eTest() {
|
||||
val res = cryptoTestHelper.doE2ETestWithAliceInARoom()
|
||||
|
||||
res.close()
|
||||
}
|
||||
}
|
@ -1,64 +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.auth
|
||||
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.OkReplayRuleChainNoActivity
|
||||
import im.vector.matrix.android.api.auth.Authenticator
|
||||
import okreplay.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class AuthenticatorTest : InstrumentedTest {
|
||||
|
||||
lateinit var authenticator: Authenticator
|
||||
lateinit var okReplayInterceptor: OkReplayInterceptor
|
||||
|
||||
private val okReplayConfig = OkReplayConfig.Builder()
|
||||
.tapeRoot(AndroidTapeRoot(
|
||||
context(), javaClass))
|
||||
.defaultMode(TapeMode.READ_WRITE) // or TapeMode.READ_ONLY
|
||||
.sslEnabled(true)
|
||||
.interceptor(okReplayInterceptor)
|
||||
.build()
|
||||
|
||||
@get:Rule
|
||||
val testRule = OkReplayRuleChainNoActivity(okReplayConfig).get()
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
@OkReplay(tape = "auth", mode = TapeMode.READ_WRITE)
|
||||
fun auth() {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val grantExternalStoragePermissionRule: GrantPermissionRule =
|
||||
GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright 2016 OpenMarket 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.common
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.Observer
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import java.util.ArrayList
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* This class exposes methods to be used in common cases
|
||||
* Registration, login, Sync, Sending messages...
|
||||
*/
|
||||
class CommonTestHelper(context: Context) {
|
||||
|
||||
val matrix: Matrix
|
||||
|
||||
init {
|
||||
Matrix.initialize(context, MatrixConfiguration("TestFlavor"))
|
||||
|
||||
matrix = Matrix.getInstance(context)
|
||||
}
|
||||
|
||||
fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
|
||||
return createAccount(userNamePrefix, TestConstants.PASSWORD, testParams)
|
||||
}
|
||||
|
||||
fun logIntoAccount(userId: String, testParams: SessionTestParams): Session {
|
||||
return logIntoAccount(userId, TestConstants.PASSWORD, testParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Home server configuration, with Http connection allowed for test
|
||||
*/
|
||||
fun createHomeServerConfig(): HomeServerConnectionConfig {
|
||||
return HomeServerConnectionConfig.Builder()
|
||||
.withHomeServerUri(Uri.parse(TestConstants.TESTS_HOME_SERVER_URL))
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods init the event stream and check for initial sync
|
||||
*
|
||||
* @param session the session to sync
|
||||
*/
|
||||
fun syncSession(session: Session) {
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
session.open()
|
||||
session.startSync(true)
|
||||
|
||||
val syncLiveData = runBlocking(Dispatchers.Main) {
|
||||
session.getSyncStateLive()
|
||||
}
|
||||
val syncObserver = object : Observer<SyncState> {
|
||||
override fun onChanged(t: SyncState?) {
|
||||
if (session.hasAlreadySynced()) {
|
||||
lock.countDown()
|
||||
syncLiveData.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
|
||||
|
||||
await(lock)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends text messages in a room
|
||||
*
|
||||
* @param room the room where to send the messages
|
||||
* @param message the message to send
|
||||
* @param nbOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(nbOfMessages)
|
||||
val onEventSentListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// TODO Count only new messages?
|
||||
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {
|
||||
sentEvents.addAll(snapshot.filter { it.root.type == EventType.MESSAGE })
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
timeline.addListener(onEventSentListener)
|
||||
for (i in 0 until nbOfMessages) {
|
||||
room.sendTextMessage(message + " #" + (i + 1))
|
||||
}
|
||||
await(latch)
|
||||
timeline.removeListener(onEventSentListener)
|
||||
|
||||
// Check that all events has been created
|
||||
assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong())
|
||||
|
||||
return sentEvents
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
/**
|
||||
* Creates a unique account
|
||||
*
|
||||
* @param userNamePrefix the user name prefix
|
||||
* @param password the password
|
||||
* @param testParams test params about the session
|
||||
* @return the session associated with the newly created account
|
||||
*/
|
||||
private fun createAccount(userNamePrefix: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams): Session {
|
||||
val session = createAccountAndSync(
|
||||
userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(),
|
||||
password,
|
||||
testParams
|
||||
)
|
||||
assertNotNull(session)
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs into an existing account
|
||||
*
|
||||
* @param userId the userId to log in
|
||||
* @param password the password to log in
|
||||
* @param testParams test params about the session
|
||||
* @return the session associated with the existing account
|
||||
*/
|
||||
private fun logIntoAccount(userId: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams): Session {
|
||||
val session = logAccountAndSync(userId, password, testParams)
|
||||
assertNotNull(session)
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an account and a dedicated session
|
||||
*
|
||||
* @param userName the account username
|
||||
* @param password the password
|
||||
* @param sessionTestParams parameters for the test
|
||||
*/
|
||||
private fun createAccountAndSync(userName: String,
|
||||
password: String,
|
||||
sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
doSync<RegistrationResult> {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null, it)
|
||||
}
|
||||
|
||||
// Preform dummy step
|
||||
val registrationResult = doSync<RegistrationResult> {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.dummy(it)
|
||||
}
|
||||
|
||||
assertTrue(registrationResult is RegistrationResult.Success)
|
||||
val session = (registrationResult as RegistrationResult.Success).session
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session)
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an account login
|
||||
*
|
||||
* @param userName the account username
|
||||
* @param password the password
|
||||
* @param sessionTestParams session test params
|
||||
*/
|
||||
private fun logAccountAndSync(userName: String,
|
||||
password: String,
|
||||
sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
val session = doSync<Session> {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice", it)
|
||||
}
|
||||
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session)
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Await for a latch and ensure the result is true
|
||||
*
|
||||
* @param latch
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
fun await(latch: CountDownLatch) {
|
||||
assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
|
||||
// Transform a method with a MatrixCallback to a synchronous method
|
||||
inline fun <reified T> doSync(block: (MatrixCallback<T>) -> Unit): T {
|
||||
val lock = CountDownLatch(1)
|
||||
var result: T? = null
|
||||
|
||||
val callback = object : TestMatrixCallback<T>(lock) {
|
||||
override fun onSuccess(data: T) {
|
||||
result = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
|
||||
block.invoke(callback)
|
||||
|
||||
await(lock)
|
||||
|
||||
assertNotNull(result)
|
||||
return result!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all provided sessions
|
||||
*/
|
||||
fun Iterable<Session>.close() = forEach { it.close() }
|
||||
|
||||
fun signout(session: Session) {
|
||||
val lock = CountDownLatch(1)
|
||||
session.signOut(true, TestMatrixCallback(lock))
|
||||
await(lock)
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.common
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
|
||||
data class CryptoTestData(val firstSession: Session,
|
||||
val roomId: String,
|
||||
val secondSession: Session? = null,
|
||||
val thirdSession: Session? = null) {
|
||||
|
||||
fun close() {
|
||||
firstSession.close()
|
||||
secondSession?.close()
|
||||
secondSession?.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,352 @@
|
||||
/*
|
||||
* 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.common
|
||||
|
||||
import android.os.SystemClock
|
||||
import androidx.lifecycle.Observer
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import java.util.Arrays
|
||||
import java.util.HashMap
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
|
||||
val messagesFromAlice: List<String> = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!")
|
||||
val messagesFromBob: List<String> = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
|
||||
|
||||
val defaultSessionParams = SessionTestParams(true)
|
||||
|
||||
/**
|
||||
* @return alice session
|
||||
*/
|
||||
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
var roomId: String? = null
|
||||
val lock1 = CountDownLatch(1)
|
||||
|
||||
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), object : TestMatrixCallback<String>(lock1) {
|
||||
override fun onSuccess(data: String) {
|
||||
roomId = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
assertNotNull(roomId)
|
||||
|
||||
val room = aliceSession.getRoom(roomId!!)!!
|
||||
|
||||
val lock2 = CountDownLatch(1)
|
||||
room.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM, object : TestMatrixCallback<Unit>(lock2) {})
|
||||
mTestHelper.await(lock2)
|
||||
|
||||
return CryptoTestData(aliceSession, roomId!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return alice and bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
||||
val statuses = HashMap<String, String>()
|
||||
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
|
||||
val lock1 = CountDownLatch(2)
|
||||
|
||||
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
}
|
||||
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (t?.isNotEmpty() == true) {
|
||||
statuses["onNewRoom"] = "onNewRoom"
|
||||
lock1.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
|
||||
aliceRoom.invite(bobSession.myUserId, callback = object : TestMatrixCallback<Unit>(lock1) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
statuses["invite"] = "invite"
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
|
||||
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
|
||||
|
||||
val lock2 = CountDownLatch(2)
|
||||
|
||||
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (bobSession.getRoom(aliceRoomId)
|
||||
?.getRoomMember(aliceSession.myUserId)
|
||||
?.membership == Membership.JOIN) {
|
||||
statuses["AliceJoin"] = "AliceJoin"
|
||||
lock2.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
||||
}
|
||||
|
||||
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
|
||||
|
||||
mTestHelper.await(lock2)
|
||||
|
||||
// Ensure bob can send messages to the room
|
||||
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
// assertNotNull(roomFromBobPOV.powerLevels)
|
||||
// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
|
||||
|
||||
assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin"))
|
||||
|
||||
// bobSession.dataHandler.removeListener(bobEventListener)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, bobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alice, Bob and Sam session
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData {
|
||||
val statuses = HashMap<String, String>()
|
||||
|
||||
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
val room = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
||||
|
||||
val lock1 = CountDownLatch(2)
|
||||
|
||||
// val samEventListener = object : MXEventListener() {
|
||||
// override fun onNewRoom(roomId: String) {
|
||||
// if (TextUtils.equals(roomId, aliceRoomId)) {
|
||||
// if (!statuses.containsKey("onNewRoom")) {
|
||||
// statuses["onNewRoom"] = "onNewRoom"
|
||||
// lock1.countDown()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// samSession.dataHandler.addListener(samEventListener)
|
||||
|
||||
room.invite(samSession.myUserId, null, object : TestMatrixCallback<Unit>(lock1) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
statuses["invite"] = "invite"
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
|
||||
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
|
||||
|
||||
// samSession.dataHandler.removeListener(samEventListener)
|
||||
|
||||
val lock2 = CountDownLatch(1)
|
||||
|
||||
samSession.joinRoom(aliceRoomId, null, object : TestMatrixCallback<Unit>(lock2) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
statuses["joinRoom"] = "joinRoom"
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock2)
|
||||
assertTrue(statuses.containsKey("joinRoom"))
|
||||
|
||||
// wait the initial sync
|
||||
SystemClock.sleep(1000)
|
||||
|
||||
// samSession.dataHandler.removeListener(samEventListener)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alice and Bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoomWithEncryptedMessages(): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
bobSession.setWarnOnUnknownDevices(false)
|
||||
|
||||
aliceSession.setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
var lock = CountDownLatch(1)
|
||||
|
||||
val bobEventsListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val size = snapshot.filter { it.root.senderId != bobSession.myUserId && it.root.getClearType() == EventType.MESSAGE }
|
||||
.size
|
||||
|
||||
if (size == 3) {
|
||||
lock.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(10))
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
|
||||
val results = HashMap<String, Any>()
|
||||
|
||||
// bobSession.dataHandler.addListener(object : MXEventListener() {
|
||||
// override fun onToDeviceEvent(event: Event) {
|
||||
// results["onToDeviceEvent"] = event
|
||||
// lock.countDown()
|
||||
// }
|
||||
// })
|
||||
|
||||
// Alice sends a message
|
||||
roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
|
||||
assertTrue(results.containsKey("onToDeviceEvent"))
|
||||
// assertEquals(1, messagesReceivedByBobCount)
|
||||
|
||||
// Bob send a message
|
||||
lock = CountDownLatch(1)
|
||||
roomFromBobPOV.sendTextMessage(messagesFromBob[0])
|
||||
// android does not echo the messages sent from itself
|
||||
// messagesReceivedByBobCount++
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(2, messagesReceivedByBobCount)
|
||||
|
||||
// Bob send a message
|
||||
lock = CountDownLatch(1)
|
||||
roomFromBobPOV.sendTextMessage(messagesFromBob[1])
|
||||
// android does not echo the messages sent from itself
|
||||
// messagesReceivedByBobCount++
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(3, messagesReceivedByBobCount)
|
||||
|
||||
// Bob send a message
|
||||
lock = CountDownLatch(1)
|
||||
roomFromBobPOV.sendTextMessage(messagesFromBob[2])
|
||||
// android does not echo the messages sent from itself
|
||||
// messagesReceivedByBobCount++
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(4, messagesReceivedByBobCount)
|
||||
|
||||
// Alice sends a message
|
||||
lock = CountDownLatch(2)
|
||||
roomFromAlicePOV.sendTextMessage(messagesFromAlice[1])
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(5, messagesReceivedByBobCount)
|
||||
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
return cryptoTestData
|
||||
}
|
||||
|
||||
fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) {
|
||||
assertEquals(EventType.ENCRYPTED, event.type)
|
||||
assertNotNull(event.content)
|
||||
|
||||
val eventWireContent = event.content.toContent()
|
||||
assertNotNull(eventWireContent)
|
||||
|
||||
assertNull(eventWireContent.get("body"))
|
||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent.get("algorithm"))
|
||||
|
||||
assertNotNull(eventWireContent.get("ciphertext"))
|
||||
assertNotNull(eventWireContent.get("session_id"))
|
||||
assertNotNull(eventWireContent.get("sender_key"))
|
||||
|
||||
assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id"))
|
||||
|
||||
assertNotNull(event.eventId)
|
||||
assertEquals(roomId, event.roomId)
|
||||
assertEquals(EventType.MESSAGE, event.getClearType())
|
||||
// TODO assertTrue(event.getAge() < 10000)
|
||||
|
||||
val eventContent = event.toContent()
|
||||
assertNotNull(eventContent)
|
||||
assertEquals(clearMessage, eventContent.get("body"))
|
||||
assertEquals(senderSession.myUserId, event.senderId)
|
||||
}
|
||||
|
||||
fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData {
|
||||
return MegolmBackupAuthData(
|
||||
publicKey = "abcdefg",
|
||||
signatures = HashMap<String, Map<String, String>>().apply {
|
||||
this["something"] = HashMap<String, String>().apply {
|
||||
this["ed25519:something"] = "hijklmnop"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo {
|
||||
return MegolmBackupCreationInfo().apply {
|
||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
authData = createFakeMegolmBackupAuthData()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.common
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Protocol
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
/**
|
||||
* Allows to intercept network requests for test purpose by
|
||||
* - re-writing the response
|
||||
* - changing the response code (200/404/etc..).
|
||||
* - Test delays..
|
||||
*
|
||||
* Basic usage:
|
||||
* <code>
|
||||
* val mockInterceptor = MockOkHttpInterceptor()
|
||||
* mockInterceptor.addRule(MockOkHttpInterceptor.SimpleRule(".well-known/matrix/client", 200, "{}"))
|
||||
*
|
||||
* RestHttpClientFactoryProvider.defaultProvider = RestClientHttpClientFactory(mockInterceptor)
|
||||
* AutoDiscovery().findClientConfig("matrix.org", <callback>)
|
||||
* </code>
|
||||
*/
|
||||
class MockOkHttpInterceptor : Interceptor {
|
||||
|
||||
private var rules: ArrayList<Rule> = ArrayList()
|
||||
|
||||
fun addRule(rule: Rule) {
|
||||
rules.add(rule)
|
||||
}
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
rules.forEach { rule ->
|
||||
if (originalRequest.url.toString().contains(rule.match)) {
|
||||
rule.process(originalRequest)?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chain.proceed(originalRequest)
|
||||
}
|
||||
|
||||
abstract class Rule(val match: String) {
|
||||
abstract fun process(originalRequest: Request): Response?
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple rule that reply with the given body for any request that matches the match param
|
||||
*/
|
||||
class SimpleRule(match: String,
|
||||
private val code: Int = HttpsURLConnection.HTTP_OK,
|
||||
private val body: String = "{}") : Rule(match) {
|
||||
|
||||
override fun process(originalRequest: Request): Response? {
|
||||
return Response.Builder()
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.request(originalRequest)
|
||||
.message("mocked answer")
|
||||
.body(body.toResponseBody(null))
|
||||
.code(code)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.common
|
||||
|
||||
data class SessionTestParams @JvmOverloads constructor(val withInitialSync: Boolean = false)
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.common
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Compare two lists and their content
|
||||
*/
|
||||
fun assertListEquals(list1: List<Any>?, list2: List<Any>?) {
|
||||
if (list1 == null) {
|
||||
assertNull(list2)
|
||||
} else {
|
||||
assertNotNull(list2)
|
||||
|
||||
assertEquals("List sizes must match", list1.size, list2!!.size)
|
||||
|
||||
for (i in list1.indices) {
|
||||
assertEquals("Elements at index $i are not equal", list1[i], list2[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two maps and their content
|
||||
*/
|
||||
fun assertDictEquals(dict1: Map<String, Any>?, dict2: Map<String, Any>?) {
|
||||
if (dict1 == null) {
|
||||
assertNull(dict2)
|
||||
} else {
|
||||
assertNotNull(dict2)
|
||||
|
||||
assertEquals("Map sizes must match", dict1.size, dict2!!.size)
|
||||
|
||||
for (i in dict1.keys) {
|
||||
assertEquals("Values for key $i are not equal", dict1[i], dict2[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two byte arrays content.
|
||||
* Note that if the arrays have not the same size, it also fails.
|
||||
*/
|
||||
fun assertByteArrayNotEqual(a1: ByteArray, a2: ByteArray) {
|
||||
if (a1.size != a2.size) {
|
||||
fail("Arrays have not the same size.")
|
||||
}
|
||||
|
||||
for (index in a1.indices) {
|
||||
if (a1[index] != a2[index]) {
|
||||
// Difference found!
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fail("Arrays are equals.")
|
||||
}
|
@ -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.common
|
||||
|
||||
import android.os.Debug
|
||||
|
||||
object TestConstants {
|
||||
|
||||
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
|
||||
|
||||
// Time out to use when waiting for server response. 60s
|
||||
private const val AWAIT_TIME_OUT_MILLIS = 60000
|
||||
|
||||
// Time out to use when waiting for server response, when the debugger is connected. 10 minutes
|
||||
private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60000
|
||||
|
||||
const val USER_ALICE = "Alice"
|
||||
const val USER_BOB = "Bob"
|
||||
const val USER_SAM = "Sam"
|
||||
|
||||
const val PASSWORD = "password"
|
||||
|
||||
val timeOutMillis: Long
|
||||
get() = if (Debug.isDebuggerConnected()) {
|
||||
// Wait more
|
||||
AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS.toLong()
|
||||
} else {
|
||||
AWAIT_TIME_OUT_MILLIS.toLong()
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.common
|
||||
|
||||
import androidx.annotation.CallSuper
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import org.junit.Assert.fail
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* Simple implementation of MatrixCallback, which count down the CountDownLatch on each API callback
|
||||
* @param onlySuccessful true to fail if an error occurs. This is the default behavior
|
||||
* @param <T>
|
||||
*/
|
||||
open class TestMatrixCallback<T>(private val countDownLatch: CountDownLatch,
|
||||
private val onlySuccessful: Boolean = true) : MatrixCallback<T> {
|
||||
|
||||
@CallSuper
|
||||
override fun onSuccess(data: T) {
|
||||
countDownLatch.countDown()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "TestApiCallback")
|
||||
|
||||
if (onlySuccessful) {
|
||||
fail("onFailure " + failure.localizedMessage)
|
||||
}
|
||||
|
||||
countDownLatch.countDown()
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.crypto
|
||||
|
||||
import android.os.MemoryFile
|
||||
import android.util.Base64
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey
|
||||
import org.junit.Assert.*
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Unit tests AttachmentEncryptionTest.
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class AttachmentEncryptionTest {
|
||||
|
||||
private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String {
|
||||
val `in` = Base64.decode(input, Base64.DEFAULT)
|
||||
|
||||
val inputStream: InputStream
|
||||
|
||||
inputStream = if (`in`.isEmpty()) {
|
||||
ByteArrayInputStream(`in`)
|
||||
} else {
|
||||
val memoryFile = MemoryFile("file" + System.currentTimeMillis(), `in`.size)
|
||||
memoryFile.outputStream.write(`in`)
|
||||
memoryFile.inputStream
|
||||
}
|
||||
|
||||
val decryptedStream = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo)
|
||||
|
||||
assertNotNull(decryptedStream)
|
||||
|
||||
val buffer = ByteArray(100)
|
||||
|
||||
val len = decryptedStream!!.read(buffer)
|
||||
|
||||
decryptedStream.close()
|
||||
|
||||
return Base64.encodeToString(buffer, 0, len, Base64.DEFAULT).replace("\n".toRegex(), "").replace("=".toRegex(), "")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDecrypt1() {
|
||||
val encryptedFileInfo = EncryptedFileInfo(
|
||||
v = "v2",
|
||||
hashes = mapOf("sha256" to "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU"),
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
k = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
key_ops = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
iv = "AAAAAAAAAAAAAAAAAAAAAA",
|
||||
url = "dummyUrl"
|
||||
)
|
||||
|
||||
assertEquals("", checkDecryption("", encryptedFileInfo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDecrypt2() {
|
||||
val encryptedFileInfo = EncryptedFileInfo(
|
||||
v = "v2",
|
||||
hashes = mapOf("sha256" to "YzF08lARDdOCzJpzuSwsjTNlQc4pHxpdHcXiD/wpK6k"),
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
k = "__________________________________________8",
|
||||
key_ops = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
iv = "//////////8AAAAAAAAAAA",
|
||||
url = "dummyUrl"
|
||||
)
|
||||
|
||||
assertEquals("SGVsbG8sIFdvcmxk", checkDecryption("5xJZTt5cQicm+9f4", encryptedFileInfo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDecrypt3() {
|
||||
val encryptedFileInfo = EncryptedFileInfo(
|
||||
v = "v2",
|
||||
hashes = mapOf("sha256" to "IOq7/dHHB+mfHfxlRY5XMeCWEwTPmlf4cJcgrkf6fVU"),
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
k = "__________________________________________8",
|
||||
key_ops = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
iv = "//////////8AAAAAAAAAAA",
|
||||
url = "dummyUrl"
|
||||
)
|
||||
|
||||
assertEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
|
||||
checkDecryption("zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q",
|
||||
encryptedFileInfo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDecrypt4() {
|
||||
val encryptedFileInfo = EncryptedFileInfo(
|
||||
v = "v2",
|
||||
hashes = mapOf("sha256" to "LYG/orOViuFwovJpv2YMLSsmVKwLt7pY3f8SYM7KU5E"),
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
k = "__________________________________________8",
|
||||
key_ops = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
iv = "/////////////////////w",
|
||||
url = "dummyUrl"
|
||||
)
|
||||
|
||||
assertNotEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
|
||||
checkDecryption("tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA",
|
||||
encryptedFileInfo))
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ 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 java.util.*
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class CryptoStoreHelper {
|
||||
|
||||
@ -35,10 +35,10 @@ internal class CryptoStoreHelper {
|
||||
}
|
||||
|
||||
fun createCredential() = Credentials(
|
||||
userId = "userId_" + Random().nextInt(),
|
||||
userId = "userId_" + Random.nextInt(),
|
||||
homeServer = "http://matrix.org",
|
||||
accessToken = "access_token",
|
||||
refreshToken = null,
|
||||
deviceId = "deviceId_sample"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,20 +16,31 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import io.realm.Realm
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmManager
|
||||
import org.matrix.olm.OlmSession
|
||||
|
||||
private const val DUMMY_DEVICE_KEY = "DeviceKey"
|
||||
|
||||
class CryptoStoreTest {
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CryptoStoreTest : InstrumentedTest {
|
||||
|
||||
private val cryptoStoreHelper = CryptoStoreHelper()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Realm.init(context())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_metadata_realm_ok() {
|
||||
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||
@ -114,4 +125,4 @@ class CryptoStoreTest {
|
||||
olmAccount1.releaseAccount()
|
||||
olmAccount2.releaseAccount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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 androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.*
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
/**
|
||||
* Unit tests ExportEncryptionTest.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class ExportEncryptionTest {
|
||||
|
||||
@Test
|
||||
fun checkExportError1() {
|
||||
val password = "password"
|
||||
val input = "-----"
|
||||
var failed = false
|
||||
|
||||
try {
|
||||
MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
failed = true
|
||||
}
|
||||
|
||||
assertTrue(failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportError2() {
|
||||
val password = "password"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\n" + "-----"
|
||||
var failed = false
|
||||
|
||||
try {
|
||||
MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
failed = true
|
||||
}
|
||||
|
||||
assertTrue(failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportError3() {
|
||||
val password = "password"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\n" +
|
||||
" AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" +
|
||||
" cissyYBxjsfsAn\n" +
|
||||
" -----END MEGOLM SESSION DATA-----"
|
||||
var failed = false
|
||||
|
||||
try {
|
||||
MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
failed = true
|
||||
}
|
||||
|
||||
assertTrue(failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportDecrypt1() {
|
||||
val password = "password"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + "cissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----"
|
||||
val expectedString = "plain"
|
||||
|
||||
var decodedString: String? = null
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportDecrypt1() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportDecrypt2() {
|
||||
val password = "betterpassword"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" + "KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----"
|
||||
val expectedString = "Hello, World"
|
||||
|
||||
var decodedString: String? = null
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportDecrypt2() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportDecrypt3() {
|
||||
val password = "SWORDFISH"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" + "MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----"
|
||||
val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
|
||||
|
||||
var decodedString: String? = null
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportDecrypt3() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportEncrypt1() {
|
||||
val password = "password"
|
||||
val expectedString = "plain"
|
||||
var decodedString: String? = null
|
||||
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption
|
||||
.decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportEncrypt1() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportEncrypt2() {
|
||||
val password = "betterpassword"
|
||||
val expectedString = "Hello, World"
|
||||
var decodedString: String? = null
|
||||
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption
|
||||
.decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportEncrypt2() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportEncrypt3() {
|
||||
val password = "SWORDFISH"
|
||||
val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
|
||||
var decodedString: String? = null
|
||||
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption
|
||||
.decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportEncrypt3() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportEncrypt4() {
|
||||
val password = "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword"
|
||||
val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
|
||||
var decodedString: String? = null
|
||||
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption
|
||||
.decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportEncrypt4() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package im.vector.matrix.android.internal.crypto.crosssigning
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.common.TestMatrixCallback
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class XSigningTest : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
@Test
|
||||
fun test_InitializeAndStoreKeys() {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
val aliceLatch = CountDownLatch(1)
|
||||
aliceSession.getCrossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), TestMatrixCallback(aliceLatch))
|
||||
|
||||
mTestHelper.await(aliceLatch)
|
||||
|
||||
val myCrossSigningKeys = aliceSession.getCrossSigningService().getMyCrossSigningKeys()
|
||||
val masterPubKey = myCrossSigningKeys?.masterKey()
|
||||
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
|
||||
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
|
||||
assertNotNull("SelfSigned key should be stored", selfSigningKey?.unpaddedBase64PublicKey)
|
||||
val userKey = myCrossSigningKeys?.userKey()
|
||||
assertNotNull("User key should be stored", userKey?.unpaddedBase64PublicKey)
|
||||
|
||||
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
|
||||
|
||||
assertTrue("Signing Keys should be trusted", aliceSession.getCrossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_CrossSigningCheckBobSeesTheKeys() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceAuthParams = UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
val bobAuthParams = UserPasswordAuth(
|
||||
user = bobSession!!.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
val latch = CountDownLatch(2)
|
||||
|
||||
aliceSession.getCrossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
|
||||
bobSession.getCrossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
|
||||
|
||||
mTestHelper.await(latch)
|
||||
|
||||
// Check that alice can see bob keys
|
||||
val downloadLatch = CountDownLatch(1)
|
||||
aliceSession.downloadKeys(listOf(bobSession.myUserId), true, TestMatrixCallback(downloadLatch))
|
||||
mTestHelper.await(downloadLatch)
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.getCrossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
||||
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
||||
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
|
||||
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey)
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
|
||||
|
||||
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signout(bobSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_CrossSigningTestAliceTrustBobNewDevice() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceAuthParams = UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
val bobAuthParams = UserPasswordAuth(
|
||||
user = bobSession!!.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
val latch = CountDownLatch(2)
|
||||
|
||||
aliceSession.getCrossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
|
||||
bobSession.getCrossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
|
||||
|
||||
mTestHelper.await(latch)
|
||||
|
||||
// Check that alice can see bob keys
|
||||
val downloadLatch = CountDownLatch(1)
|
||||
val bobUserId = bobSession.myUserId
|
||||
aliceSession.downloadKeys(listOf(bobUserId), true, TestMatrixCallback(downloadLatch))
|
||||
mTestHelper.await(downloadLatch)
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.getCrossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
||||
|
||||
val trustLatch = CountDownLatch(1)
|
||||
aliceSession.getCrossSigningService().trustUser(bobUserId, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
trustLatch.countDown()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
fail("Failed to trust bob")
|
||||
}
|
||||
})
|
||||
mTestHelper.await(trustLatch)
|
||||
|
||||
// Now bobs logs in on a new device and verifies it
|
||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||
|
||||
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
||||
val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId
|
||||
|
||||
// Check that bob first session sees the new login
|
||||
val bobKeysLatch = CountDownLatch(1)
|
||||
bobSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
fail("Failed to get device")
|
||||
}
|
||||
|
||||
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
|
||||
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) {
|
||||
fail("Bob should see the new device")
|
||||
}
|
||||
bobKeysLatch.countDown()
|
||||
}
|
||||
})
|
||||
mTestHelper.await(bobKeysLatch)
|
||||
|
||||
val bobSecondDevicePOVFirstDevice = bobSession.getDeviceInfo(bobUserId, bobSecondDeviceId)
|
||||
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||
|
||||
// Manually mark it as trusted from first session
|
||||
val bobSignLatch = CountDownLatch(1)
|
||||
bobSession.getCrossSigningService().signDevice(bobSecondDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
bobSignLatch.countDown()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
fail("Failed to trust bob ${failure.localizedMessage}")
|
||||
}
|
||||
})
|
||||
mTestHelper.await(bobSignLatch)
|
||||
|
||||
// Now alice should cross trust bob's second device
|
||||
val aliceKeysLatch = CountDownLatch(1)
|
||||
aliceSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
fail("Failed to get device")
|
||||
}
|
||||
|
||||
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
|
||||
// check that the device is seen
|
||||
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||
fail("Alice should see the new device")
|
||||
}
|
||||
aliceKeysLatch.countDown()
|
||||
}
|
||||
})
|
||||
mTestHelper.await(aliceKeysLatch)
|
||||
|
||||
val result = aliceSession.getCrossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
||||
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signout(bobSession)
|
||||
mTestHelper.signout(bobSession2)
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.crypto.keysbackup
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.common.assertByteArrayNotEqual
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.olm.OlmManager
|
||||
import org.matrix.olm.OlmPkDecryption
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class KeysBackupPasswordTest : InstrumentedTest {
|
||||
|
||||
@Before
|
||||
fun ensureLibLoaded() {
|
||||
OlmManager()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check generatePrivateKeyWithPassword progress listener behavior
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_progress_ok() {
|
||||
val progressValues = ArrayList<Int>(101)
|
||||
var lastTotal = 0
|
||||
|
||||
generatePrivateKeyWithPassword(PASSWORD, object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
if (!progressValues.contains(progress)) {
|
||||
progressValues.add(progress)
|
||||
}
|
||||
|
||||
lastTotal = total
|
||||
}
|
||||
})
|
||||
|
||||
assertEquals(100, lastTotal)
|
||||
|
||||
// Ensure all values are here
|
||||
assertEquals(101, progressValues.size)
|
||||
|
||||
for (i in 0..100) {
|
||||
assertTrue(progressValues[i] == i)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad password
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badPassword_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad password
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(BAD_PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad password
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badIteration_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad iteration
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
500_001)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad salt
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badSalt_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad iteration
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
BAD_SALT,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check [retrievePrivateKeyWithPassword] with data coming from another platform (RiotWeb).
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_crossPlatform_ok() {
|
||||
val password = "This is a passphrase!"
|
||||
val salt = "TO0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
|
||||
val iteration = 500_000
|
||||
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
|
||||
// Data from RiotWeb
|
||||
val privateKeyBytes = byteArrayOf(
|
||||
116.toByte(), 224.toByte(), 229.toByte(), 224.toByte(), 9.toByte(), 3.toByte(), 178.toByte(), 162.toByte(),
|
||||
120.toByte(), 23.toByte(), 108.toByte(), 218.toByte(), 22.toByte(), 61.toByte(), 241.toByte(), 200.toByte(),
|
||||
235.toByte(), 173.toByte(), 236.toByte(), 100.toByte(), 115.toByte(), 247.toByte(), 33.toByte(), 132.toByte(),
|
||||
195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte())
|
||||
|
||||
assertArrayEquals(privateKeyBytes, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PASSWORD = "password"
|
||||
private const val BAD_PASSWORD = "passw0rd"
|
||||
|
||||
private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* This class observe the state change of a KeysBackup object and provide a method to check the several state change
|
||||
* It checks all state transitions and detected forbidden transition
|
||||
*/
|
||||
internal class StateObserver(private val keysBackup: KeysBackupService,
|
||||
private val latch: CountDownLatch? = null,
|
||||
private val expectedStateChange: Int = -1) : KeysBackupStateListener {
|
||||
|
||||
private val allowedStateTransitions = listOf(
|
||||
KeysBackupState.BackingUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.BackingUp to KeysBackupState.WrongBackUpVersion,
|
||||
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Disabled,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.NotTrusted,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Unknown,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.WrongBackUpVersion,
|
||||
|
||||
KeysBackupState.Disabled to KeysBackupState.Enabling,
|
||||
|
||||
KeysBackupState.Enabling to KeysBackupState.Disabled,
|
||||
KeysBackupState.Enabling to KeysBackupState.ReadyToBackUp,
|
||||
|
||||
KeysBackupState.NotTrusted to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
// This transition happens when we trust the device
|
||||
KeysBackupState.NotTrusted to KeysBackupState.ReadyToBackUp,
|
||||
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.WillBackUp,
|
||||
|
||||
KeysBackupState.Unknown to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
|
||||
KeysBackupState.WillBackUp to KeysBackupState.BackingUp,
|
||||
|
||||
KeysBackupState.WrongBackUpVersion to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
|
||||
// FIXME These transitions are observed during test, and I'm not sure they should occur. Don't have time to investigate now
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.BackingUp,
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.WillBackUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.WillBackUp to KeysBackupState.Unknown
|
||||
)
|
||||
|
||||
private val stateList = ArrayList<KeysBackupState>()
|
||||
private var lastTransitionError: String? = null
|
||||
|
||||
init {
|
||||
keysBackup.addListener(this)
|
||||
}
|
||||
|
||||
// TODO Make expectedStates mandatory to enforce test
|
||||
fun stopAndCheckStates(expectedStates: List<KeysBackupState>?) {
|
||||
keysBackup.removeListener(this)
|
||||
|
||||
expectedStates?.let {
|
||||
assertEquals(it.size, stateList.size)
|
||||
|
||||
for (i in it.indices) {
|
||||
assertEquals("The state $i is not correct. states: " + stateList.joinToString(separator = " "), it[i], stateList[i])
|
||||
}
|
||||
}
|
||||
|
||||
assertNull("states: " + stateList.joinToString(separator = " "), lastTransitionError)
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
stateList.add(newState)
|
||||
|
||||
// Check that state transition is valid
|
||||
if (stateList.size >= 2
|
||||
&& !allowedStateTransitions.contains(stateList[stateList.size - 2] to newState)) {
|
||||
// Forbidden transition detected
|
||||
lastTransitionError = "Forbidden transition detected from " + stateList[stateList.size - 2] + " to " + newState
|
||||
}
|
||||
|
||||
if (expectedStateChange == stateList.size) {
|
||||
latch?.countDown()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,561 @@
|
||||
/*
|
||||
* 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.crypto.verification
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class SASTest : InstrumentedTest {
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
@Test
|
||||
fun test_aliceStartThenAliceCancel() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
val bobVerificationService = bobSession!!.getVerificationService()
|
||||
|
||||
val bobTxCreatedLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
bobTxCreatedLatch.countDown()
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
|
||||
bobSession.myUserId,
|
||||
bobSession.getMyDevice().deviceId,
|
||||
null)
|
||||
assertNotNull("Alice should have a started transaction", txID)
|
||||
|
||||
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||
assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
||||
|
||||
mTestHelper.await(bobTxCreatedLatch)
|
||||
bobVerificationService.removeListener(bobListener)
|
||||
|
||||
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
||||
|
||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
|
||||
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
||||
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
|
||||
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
||||
|
||||
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
|
||||
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
|
||||
|
||||
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
|
||||
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
|
||||
|
||||
// Let's cancel from alice side
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
|
||||
val bobListener2 = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if (tx.transactionId == txID) {
|
||||
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
||||
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
||||
cancelLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener2)
|
||||
|
||||
aliceSasTx.cancel(CancelCode.User)
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
||||
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
|
||||
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
|
||||
|
||||
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
|
||||
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
|
||||
|
||||
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
|
||||
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
|
||||
|
||||
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
|
||||
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
|
||||
|
||||
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
||||
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_key_agreement_protocols_must_include_curve25519() {
|
||||
fail("Not passing for the moment")
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val protocols = listOf("meh_dont_know")
|
||||
val tid = "00000000"
|
||||
|
||||
// Bob should receive a cancel
|
||||
var cancelReason: CancelCode? = null
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
|
||||
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
|
||||
cancelLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSession.getVerificationService().addListener(bobListener)
|
||||
|
||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||
// TODO canceledToDeviceEvent = event
|
||||
// TODO cancelLatch.countDown()
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO })
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||
|
||||
val aliceListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSession.getVerificationService().addListener(aliceListener)
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
||||
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_key_agreement_macs_Must_include_hmac_sha256() {
|
||||
fail("Not passing for the moment")
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val mac = listOf("shaBit")
|
||||
val tid = "00000000"
|
||||
|
||||
// Bob should receive a cancel
|
||||
var canceledToDeviceEvent: Event? = null
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||
// TODO canceledToDeviceEvent = event
|
||||
// TODO cancelLatch.countDown()
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO })
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
||||
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_key_agreement_short_code_include_decimal() {
|
||||
fail("Not passing for the moment")
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val codes = listOf("bin", "foo", "bar")
|
||||
val tid = "00000000"
|
||||
|
||||
// Bob should receive a cancel
|
||||
var canceledToDeviceEvent: Event? = null
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||
// TODO canceledToDeviceEvent = event
|
||||
// TODO cancelLatch.countDown()
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO })
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
|
||||
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
private fun fakeBobStart(bobSession: Session,
|
||||
aliceUserID: String?,
|
||||
aliceDevice: String?,
|
||||
tid: String,
|
||||
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
||||
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
|
||||
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
|
||||
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) {
|
||||
val startMessage = KeyVerificationStart(
|
||||
fromDevice = bobSession.getMyDevice().deviceId,
|
||||
method = VerificationMethod.SAS.toValue(),
|
||||
transactionID = tid,
|
||||
keyAgreementProtocols = protocols,
|
||||
hashes = hashes,
|
||||
messageAuthenticationCodes = mac,
|
||||
shortAuthenticationStrings = codes
|
||||
)
|
||||
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
|
||||
|
||||
// TODO val sendLatch = CountDownLatch(1)
|
||||
// TODO bobSession.cryptoRestClient.sendToDevice(
|
||||
// TODO EventType.KEY_VERIFICATION_START,
|
||||
// TODO contentMap,
|
||||
// TODO tid,
|
||||
// TODO TestMatrixCallback<Void>(sendLatch)
|
||||
// TODO )
|
||||
}
|
||||
|
||||
// any two devices may only have at most one key verification in flight at a time.
|
||||
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
|
||||
@Test
|
||||
fun test_aliceStartTwoRequests() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
|
||||
val aliceCreatedLatch = CountDownLatch(2)
|
||||
val aliceCancelledLatch = CountDownLatch(2)
|
||||
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
||||
val aliceListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {
|
||||
createdTx.add(tx as SASDefaultVerificationTransaction)
|
||||
aliceCreatedLatch.countDown()
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
|
||||
aliceCancelledLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobUserId = bobSession!!.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
|
||||
mTestHelper.await(aliceCreatedLatch)
|
||||
mTestHelper.await(aliceCancelledLatch)
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that when alice starts a 'correct' request, bob agrees.
|
||||
*/
|
||||
@Test
|
||||
fun test_aliceAndBobAgreement() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
val bobVerificationService = bobSession!!.getVerificationService()
|
||||
|
||||
var accepted: KeyVerificationAccept? = null
|
||||
var startReq: KeyVerificationStart? = null
|
||||
|
||||
val aliceAcceptedLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.VerificationListener {
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||
val at = tx as SASDefaultVerificationTransaction
|
||||
accepted = at.accepted as? KeyVerificationAccept
|
||||
startReq = at.startReq as? KeyVerificationStart
|
||||
aliceAcceptedLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
val at = tx as IncomingSasVerificationTransaction
|
||||
at.performAccept()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
mTestHelper.await(aliceAcceptedLatch)
|
||||
|
||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||
|
||||
// check that agreement is valid
|
||||
assertTrue("Agreed Protocol should be Valid", accepted!!.isValid())
|
||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols!!.contains(accepted!!.keyAgreementProtocol))
|
||||
assertTrue("Hash should be known by alice", startReq!!.hashes!!.contains(accepted!!.hash))
|
||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode))
|
||||
|
||||
accepted!!.shortAuthenticationStrings?.forEach {
|
||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
|
||||
}
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_aliceAndBobSASCode() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
val bobVerificationService = bobSession!!.getVerificationService()
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
tx.performAccept()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
|
||||
bobSASLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
mTestHelper.await(aliceSASLatch)
|
||||
mTestHelper.await(bobSASLatch)
|
||||
|
||||
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
||||
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
||||
|
||||
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_happyPath() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
val bobVerificationService = bobSession!!.getVerificationService()
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
tx.performAccept()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
bobSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
mTestHelper.await(aliceSASLatch)
|
||||
mTestHelper.await(bobSASLatch)
|
||||
|
||||
// Assert that devices are verified
|
||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
|
||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
|
||||
|
||||
// latch wait a bit again
|
||||
Thread.sleep(1000)
|
||||
|
||||
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||
cryptoTestData.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.amshove.kluent.shouldNotBeEqualTo
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class SharedSecretTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun testSharedSecretLengthCase() {
|
||||
repeat(100) {
|
||||
generateSharedSecret().length shouldBe 43
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSharedDiffCase() {
|
||||
val sharedSecret1 = generateSharedSecret()
|
||||
val sharedSecret2 = generateSharedSecret()
|
||||
|
||||
sharedSecret1 shouldNotBeEqualTo sharedSecret2
|
||||
}
|
||||
}
|
@ -62,8 +62,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
JsonCanonicalizer.canonicalize("{\"a\":\"\\\"\"}"))
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ==========================================================================================
|
||||
* Test from https://matrix.org/docs/spec/appendices.html#examples
|
||||
* ========================================================================================== */
|
||||
@ -74,7 +72,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
JsonCanonicalizer.canonicalize("""{}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg002Test() {
|
||||
assertEquals("""{"one":1,"two":"Two"}""",
|
||||
@ -84,7 +81,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg003Test() {
|
||||
assertEquals("""{"a":"1","b":"2"}""",
|
||||
@ -94,14 +90,12 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@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}}""",
|
||||
@ -126,7 +120,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg006Test() {
|
||||
assertEquals("""{"a":"日本語"}""",
|
||||
@ -135,7 +128,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg007Test() {
|
||||
assertEquals("""{"日":1,"本":2}""",
|
||||
@ -145,7 +137,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg008Test() {
|
||||
assertEquals("""{"a":"日"}""",
|
||||
@ -159,4 +150,4 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
"a": null
|
||||
}"""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,12 @@ 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.*
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.database.helper.add
|
||||
import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
||||
import im.vector.matrix.android.internal.database.helper.merge
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
||||
@ -28,14 +32,12 @@ import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeR
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.kotlin.createObject
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.amshove.kluent.shouldEqual
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class ChunkEntityTest : InstrumentedTest {
|
||||
|
||||
@ -44,11 +46,14 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
@Before
|
||||
fun setup() {
|
||||
Realm.init(context())
|
||||
val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build()
|
||||
val testConfig = RealmConfiguration.Builder()
|
||||
.inMemory()
|
||||
.name("test-realm")
|
||||
.modules(SessionRealmModule())
|
||||
.build()
|
||||
monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
@ -143,30 +148,6 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk1: ChunkEntity = realm.createObject()
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.isUnlinked().shouldBeFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_shouldEventsBeUnlinked_whenMergingUnlinkedWithUnlinked() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk1: ChunkEntity = realm.createObject()
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.isUnlinked().shouldBeTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
@ -174,8 +155,8 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
val prevToken = "prev_token"
|
||||
chunk1.prevToken = prevToken
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
||||
chunk1.prevToken shouldEqual prevToken
|
||||
}
|
||||
@ -188,11 +169,19 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
val nextToken = "next_token"
|
||||
chunk1.nextToken = nextToken
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.nextToken shouldEqual nextToken
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private fun ChunkEntity.addAll(roomId: String,
|
||||
events: List<Event>,
|
||||
direction: PaginationDirection,
|
||||
stateIndexOffset: Int = 0) {
|
||||
events.forEach { event ->
|
||||
add(roomId, event, direction, stateIndexOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,4 @@ internal class FakeGetContextOfEventTask constructor(private val tokenChunkEvent
|
||||
)
|
||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,4 @@ internal class FakePaginationTask @Inject constructor(private val tokenChunkEven
|
||||
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -23,4 +23,4 @@ internal data class FakeTokenChunkEvent(override val start: String?,
|
||||
override val end: String?,
|
||||
override val events: List<Event> = emptyList(),
|
||||
override val stateEvents: List<Event> = emptyList()
|
||||
) : TokenChunkEvent
|
||||
) : TokenChunkEvent
|
||||
|
@ -16,21 +16,14 @@
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.internal.database.helper.addAll
|
||||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import io.realm.kotlin.createObject
|
||||
import kotlin.random.Random
|
||||
|
||||
object RoomDataHelper {
|
||||
@ -70,24 +63,7 @@ object RoomDataHelper {
|
||||
}
|
||||
|
||||
fun createFakeRoomMemberEvent(): Event {
|
||||
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||
val roomMember = RoomMemberSummary(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
||||
}
|
||||
|
||||
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
||||
roomEntity.membership = Membership.JOIN
|
||||
val eventList = createFakeListOfEvents(10)
|
||||
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
||||
nextToken = null
|
||||
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
||||
isLastForward = true
|
||||
}
|
||||
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS)
|
||||
roomEntity.addOrUpdate(chunkEntity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ internal class TimelineTest : InstrumentedTest {
|
||||
// val latch = CountDownLatch(2)
|
||||
// var timelineEvents: List<TimelineEvent> = emptyList()
|
||||
// timeline.listener = object : Timeline.Listener {
|
||||
// override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||
// override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// if (snapshot.isNotEmpty()) {
|
||||
// if (initialLoad == 0) {
|
||||
// initialLoad = snapshot.size
|
||||
@ -81,6 +81,4 @@ internal class TimelineTest : InstrumentedTest {
|
||||
// timelineEvents.size shouldEqual initialLoad + paginationCount
|
||||
// timeline.dispose()
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okio.Buffer
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import javax.inject.Inject
|
||||
@ -51,27 +52,33 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
||||
var compressed = false
|
||||
|
||||
var curlCmd = "curl"
|
||||
if (curlOptions != null) {
|
||||
curlCmd += " " + curlOptions!!
|
||||
curlOptions?.let {
|
||||
curlCmd += " $it"
|
||||
}
|
||||
curlCmd += " -X " + request.method()
|
||||
curlCmd += " -X " + request.method
|
||||
|
||||
val requestBody = request.body()
|
||||
val requestBody = request.body
|
||||
if (requestBody != null) {
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
var charset: Charset? = UTF8
|
||||
val contentType = requestBody.contentType()
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8)
|
||||
if (requestBody.contentLength() > 100_000) {
|
||||
Timber.w("Unable to log curl command data, size is too big (${requestBody.contentLength()})")
|
||||
// Ensure the curl command will failed
|
||||
curlCmd += "DATA IS TOO BIG"
|
||||
} else {
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
var charset: Charset? = UTF8
|
||||
val contentType = requestBody.contentType()
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8)
|
||||
}
|
||||
// try to keep to a single line and use a subshell to preserve any line breaks
|
||||
curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'"
|
||||
}
|
||||
// try to keep to a single line and use a subshell to preserve any line breaks
|
||||
curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'"
|
||||
}
|
||||
|
||||
val headers = request.headers()
|
||||
val headers = request.headers
|
||||
var i = 0
|
||||
val count = headers.size()
|
||||
val count = headers.size
|
||||
while (i < count) {
|
||||
val name = headers.name(i)
|
||||
val value = headers.value(i)
|
||||
@ -82,7 +89,7 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
||||
i++
|
||||
}
|
||||
|
||||
curlCmd += ((if (compressed) " --compressed " else " ") + "'" + request.url().toString()
|
||||
curlCmd += ((if (compressed) " --compressed " else " ") + "'" + request.url.toString()
|
||||
// Replace localhost for emulator by localhost for shell
|
||||
.replace("://10.0.2.2:8080/".toRegex(), "://127.0.0.1:8080/")
|
||||
+ "'")
|
||||
@ -90,7 +97,7 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
||||
// Add Json formatting
|
||||
curlCmd += " | python -m json.tool"
|
||||
|
||||
logger.log("--- cURL (" + request.url() + ")")
|
||||
logger.log("--- cURL (" + request.url + ")")
|
||||
logger.log(curlCmd)
|
||||
|
||||
return chain.proceed(request)
|
||||
|
@ -51,7 +51,6 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||
// Finally this is not a JSON string...
|
||||
Timber.e(e)
|
||||
}
|
||||
|
||||
} else if (message.startsWith("[")) {
|
||||
// JSON Array detected
|
||||
try {
|
||||
@ -61,7 +60,6 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||
// Finally not JSON...
|
||||
Timber.e(e)
|
||||
}
|
||||
|
||||
}
|
||||
// Else not a json string to log
|
||||
}
|
||||
@ -73,4 +71,4 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||
Timber.v(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,10 @@
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application>
|
||||
<application android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<provider android:name="androidx.work.impl.WorkManagerInitializer"
|
||||
<provider
|
||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||
android:authorities="${applicationId}.workmanager-init"
|
||||
android:exported="false"
|
||||
tools:node="remove" />
|
||||
|
@ -22,12 +22,15 @@ 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.auth.AuthenticationService
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
||||
import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -38,7 +41,6 @@ data class MatrixConfiguration(
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,7 +49,7 @@ data class MatrixConfiguration(
|
||||
*/
|
||||
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
@Inject internal lateinit var authenticator: Authenticator
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@Inject internal lateinit var olmManager: OlmManager
|
||||
@ -65,8 +67,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
|
||||
fun getUserAgent() = userAgentHolder.userAgent
|
||||
|
||||
fun authenticator(): Authenticator {
|
||||
return authenticator
|
||||
fun authenticationService(): AuthenticationService {
|
||||
return authenticationService
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -97,6 +99,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
fun getSdkVersion(): String {
|
||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
}
|
||||
}
|
||||
|
||||
fun decryptStream(inputStream: InputStream?, elementToDecrypt: ElementToDecrypt): InputStream? {
|
||||
return MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ interface MatrixCallback<in T> {
|
||||
* @param data the data successfully returned from the async function
|
||||
*/
|
||||
fun onSuccess(data: T) {
|
||||
//no-op
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,7 +35,6 @@ interface MatrixCallback<in T> {
|
||||
* @param failure the failure data returned from the async function
|
||||
*/
|
||||
fun onFailure(failure: Throwable) {
|
||||
//no-op
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.api
|
||||
|
||||
|
||||
/**
|
||||
* This class contains pattern to match the different Matrix ids
|
||||
*/
|
||||
@ -154,6 +153,5 @@ object MatrixPatterns {
|
||||
return if (index == -1) {
|
||||
null
|
||||
} else matrixId.substring(index + 1)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.auth
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||
*/
|
||||
interface AuthenticationService {
|
||||
|
||||
/**
|
||||
* Request the supported login flows for this homeserver.
|
||||
* This is the first method to call to be able to get a wizard to login or the create an account
|
||||
*/
|
||||
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||
*/
|
||||
fun getLoginWizard(): LoginWizard
|
||||
|
||||
/**
|
||||
* Return a RegistrationWizard, to create an matrix account on the homeserver. The login flow has to be retrieved first.
|
||||
*/
|
||||
fun getRegistrationWizard(): RegistrationWizard
|
||||
|
||||
/**
|
||||
* True when login and password has been sent with success to the homeserver
|
||||
*/
|
||||
val isRegistrationStarted: Boolean
|
||||
|
||||
/**
|
||||
* Cancel pending login or pending registration
|
||||
*/
|
||||
fun cancelPendingLoginOrRegistration()
|
||||
|
||||
/**
|
||||
* Reset all pending settings, including current HomeServerConnectionConfig
|
||||
*/
|
||||
fun reset()
|
||||
|
||||
/**
|
||||
* Check if there is an authenticated [Session].
|
||||
* @return true if there is at least one active session.
|
||||
*/
|
||||
fun hasAuthenticatedSessions(): Boolean
|
||||
|
||||
/**
|
||||
* Get the last authenticated [Session], if there is an active session.
|
||||
* @return the last active session if any, or null
|
||||
*/
|
||||
fun getLastAuthenticatedSession(): Session?
|
||||
|
||||
/**
|
||||
* Get an authenticated session. You should at least call authenticate one time before.
|
||||
* If you logout, this session will no longer be valid.
|
||||
*
|
||||
* @param sessionParams the sessionParams to open with.
|
||||
* @return the associated session if any, or null
|
||||
*/
|
||||
fun getSession(sessionParams: SessionParams): Session?
|
||||
|
||||
/**
|
||||
* Create a session after a SSO successful login
|
||||
*/
|
||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
}
|
@ -1,59 +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.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
|
||||
|
||||
/**
|
||||
* This interface defines methods to authenticate to a matrix server.
|
||||
*/
|
||||
interface Authenticator {
|
||||
|
||||
/**
|
||||
* @param homeServerConnectionConfig this param is used to configure the Homeserver
|
||||
* @param login the login field
|
||||
* @param password the password field
|
||||
* @param callback the matrix callback on which you'll receive the result of authentication.
|
||||
* @return return a [Cancelable]
|
||||
*/
|
||||
fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Check if there is an authenticated [Session].
|
||||
* @return true if there is at least one active session.
|
||||
*/
|
||||
fun hasAuthenticatedSessions(): Boolean
|
||||
|
||||
/**
|
||||
* Get the last authenticated [Session], if there is an active session.
|
||||
* @return the last active session if any, or null
|
||||
*/
|
||||
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?
|
||||
}
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.util.md5
|
||||
|
||||
/**
|
||||
* This data class hold credentials user data.
|
||||
@ -30,4 +31,11 @@ data class Credentials(
|
||||
@Json(name = "home_server") val homeServer: String,
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "refresh_token") val refreshToken: String?,
|
||||
@Json(name = "device_id") val deviceId: String?)
|
||||
@Json(name = "device_id") val deviceId: String?,
|
||||
// Optional data that may contain info to override home server and/or identity server
|
||||
@Json(name = "well_known") val wellKnown: WellKnown? = null
|
||||
)
|
||||
|
||||
internal fun Credentials.sessionId(): String {
|
||||
return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5()
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import okhttp3.TlsVersion
|
||||
|
||||
/**
|
||||
* This data class holds how to connect to a specific Homeserver.
|
||||
* It's used with [im.vector.matrix.android.api.auth.Authenticator] class.
|
||||
* It's used with [im.vector.matrix.android.api.auth.AuthenticationService] class.
|
||||
* You should use the [Builder] to create one.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
@ -253,13 +253,5 @@ data class HomeServerConnectionConfig(
|
||||
forceUsageTlsVersions
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.auth.data
|
||||
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||
|
||||
// Either a LoginFlowResponse, or an error if the homeserver is outdated
|
||||
sealed class LoginFlowResult {
|
||||
data class Success(
|
||||
val loginFlowResponse: LoginFlowResponse,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String
|
||||
) : LoginFlowResult()
|
||||
|
||||
object OutdatedHomeserver : LoginFlowResult()
|
||||
}
|
@ -22,5 +22,6 @@ package im.vector.matrix.android.api.auth.data
|
||||
*/
|
||||
data class SessionParams(
|
||||
val credentials: Credentials,
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
val isTokenValid: Boolean
|
||||
)
|
||||
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Model for https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions
|
||||
*
|
||||
* Ex:
|
||||
* <pre>
|
||||
* {
|
||||
* "unstable_features": {
|
||||
* "m.lazy_load_members": true
|
||||
* },
|
||||
* "versions": [
|
||||
* "r0.0.1",
|
||||
* "r0.1.0",
|
||||
* "r0.2.0",
|
||||
* "r0.3.0"
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Versions(
|
||||
@Json(name = "versions")
|
||||
val supportedVersions: List<String>? = null,
|
||||
|
||||
@Json(name = "unstable_features")
|
||||
val unstableFeatures: Map<String, Boolean>? = null
|
||||
)
|
||||
|
||||
// MatrixClientServerAPIVersion
|
||||
private const val r0_0_1 = "r0.0.1"
|
||||
private const val r0_1_0 = "r0.1.0"
|
||||
private const val r0_2_0 = "r0.2.0"
|
||||
private const val r0_3_0 = "r0.3.0"
|
||||
private const val r0_4_0 = "r0.4.0"
|
||||
private const val r0_5_0 = "r0.5.0"
|
||||
private const val r0_6_0 = "r0.6.0"
|
||||
|
||||
// MatrixVersionsFeature
|
||||
private const val FEATURE_LAZY_LOAD_MEMBERS = "m.lazy_load_members"
|
||||
private const val FEATURE_REQUIRE_IDENTITY_SERVER = "m.require_identity_server"
|
||||
private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token"
|
||||
private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
|
||||
|
||||
/**
|
||||
* Return true if the SDK supports this homeserver version
|
||||
*/
|
||||
fun Versions.isSupportedBySdk(): Boolean {
|
||||
return supportLazyLoadMembers()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the SDK supports this homeserver version for login and registration
|
||||
*/
|
||||
fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
|
||||
return !doesServerRequireIdentityServerParam()
|
||||
&& doesServerAcceptIdentityAccessToken()
|
||||
&& doesServerSeparatesAddAndBind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the server support the lazy loading of room members
|
||||
*
|
||||
* @return true if the server support the lazy loading of room members
|
||||
*/
|
||||
private fun Versions.supportLazyLoadMembers(): Boolean {
|
||||
return supportedVersions?.contains(r0_5_0) == true
|
||||
|| unstableFeatures?.get(FEATURE_LAZY_LOAD_MEMBERS) == true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if the `id_server` parameter is required when registering with an 3pid,
|
||||
* adding a 3pid or resetting password.
|
||||
*/
|
||||
private fun Versions.doesServerRequireIdentityServerParam(): Boolean {
|
||||
if (supportedVersions?.contains(r0_6_0) == true) return false
|
||||
return unstableFeatures?.get(FEATURE_REQUIRE_IDENTITY_SERVER) ?: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if the `id_access_token` parameter can be safely passed to the homeserver.
|
||||
* Some homeservers may trigger errors if they are not prepared for the new parameter.
|
||||
*/
|
||||
private fun Versions.doesServerAcceptIdentityAccessToken(): Boolean {
|
||||
return supportedVersions?.contains(r0_6_0) == true
|
||||
|| unstableFeatures?.get(FEATURE_ID_ACCESS_TOKEN) ?: false
|
||||
}
|
||||
|
||||
private fun Versions.doesServerSeparatesAddAndBind(): Boolean {
|
||||
return supportedVersions?.contains(r0_6_0) == true
|
||||
|| unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||
* <pre>
|
||||
* {
|
||||
* "m.homeserver": {
|
||||
* "base_url": "https://matrix.org"
|
||||
* },
|
||||
* "m.identity_server": {
|
||||
* "base_url": "https://vector.im"
|
||||
* }
|
||||
* "m.integrations": {
|
||||
* "managers": [
|
||||
* {
|
||||
* "api_url": "https://integrations.example.org",
|
||||
* "ui_url": "https://integrations.example.org/ui"
|
||||
* },
|
||||
* {
|
||||
* "api_url": "https://bots.example.org"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WellKnown(
|
||||
@Json(name = "m.homeserver")
|
||||
var homeServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.identity_server")
|
||||
var identityServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.integrations")
|
||||
var integrations: Map<String, @JvmSuppressWildcards Any>? = null
|
||||
) {
|
||||
/**
|
||||
* Returns the list of integration managers proposed
|
||||
*/
|
||||
fun getIntegrationManagers(): List<WellKnownManagerConfig> {
|
||||
val managers = ArrayList<WellKnownManagerConfig>()
|
||||
integrations?.get("managers")?.let {
|
||||
(it as? ArrayList<*>)?.let { configs ->
|
||||
configs.forEach { config ->
|
||||
(config as? Map<*, *>)?.let { map ->
|
||||
val apiUrl = map["api_url"] as? String
|
||||
val uiUrl = map["ui_url"] as? String ?: apiUrl
|
||||
if (apiUrl != null
|
||||
&& apiUrl.startsWith("https://")
|
||||
&& uiUrl!!.startsWith("https://")) {
|
||||
managers.add(WellKnownManagerConfig(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return managers
|
||||
}
|
||||
}
|
@ -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.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||
* <pre>
|
||||
* {
|
||||
* "base_url": "https://vector.im"
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WellKnownBaseConfig(
|
||||
@Json(name = "base_url")
|
||||
val baseURL: String? = null
|
||||
)
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.auth.data
|
||||
|
||||
data class WellKnownManagerConfig(
|
||||
val apiUrl : String,
|
||||
val uiUrl: String
|
||||
)
|
@ -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.auth.login
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface LoginWizard {
|
||||
|
||||
/**
|
||||
* @param login the login field
|
||||
* @param password the password field
|
||||
* @param deviceName the initial device name
|
||||
* @param callback the matrix callback on which you'll receive the result of authentication.
|
||||
* @return return a [Cancelable]
|
||||
*/
|
||||
fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Reset user password
|
||||
*/
|
||||
fun resetPassword(email: String,
|
||||
newPassword: String,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Confirm the new password, once the user has checked his email
|
||||
*/
|
||||
fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
@ -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.auth.registration
|
||||
|
||||
sealed class RegisterThreePid {
|
||||
data class Email(val email: String) : RegisterThreePid()
|
||||
data class Msisdn(val msisdn: String, val countryCode: String) : RegisterThreePid()
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.auth.registration
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
|
||||
// Either a session or an object containing data about registration stages
|
||||
sealed class RegistrationResult {
|
||||
data class Success(val session: Session) : RegistrationResult()
|
||||
data class FlowResponse(val flowResult: FlowResult) : RegistrationResult()
|
||||
}
|
||||
|
||||
data class FlowResult(
|
||||
val missingStages: List<Stage>,
|
||||
val completedStages: List<Stage>
|
||||
)
|
@ -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.auth.registration
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface RegistrationWizard {
|
||||
|
||||
fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
val currentThreePid: String?
|
||||
|
||||
// True when login and password has been sent with success to the homeserver
|
||||
val isRegistrationStarted: Boolean
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.auth.registration
|
||||
|
||||
sealed class Stage(open val mandatory: Boolean) {
|
||||
|
||||
// m.login.recaptcha
|
||||
data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
|
||||
|
||||
// m.login.oauth2
|
||||
// m.login.email.identity
|
||||
data class Email(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// m.login.msisdn
|
||||
data class Msisdn(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// m.login.token
|
||||
|
||||
// m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username
|
||||
// and a password, the dummy stage has to be done
|
||||
data class Dummy(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// Undocumented yet: m.login.terms
|
||||
data class Terms(override val mandatory: Boolean, val policies: TermPolicies) : Stage(mandatory)
|
||||
|
||||
// For unknown stages
|
||||
data class Other(override val mandatory: Boolean, val type: String, val params: Map<*, *>?) : Stage(mandatory)
|
||||
}
|
||||
|
||||
typealias TermPolicies = Map<*, *>
|
@ -17,7 +17,6 @@
|
||||
package im.vector.matrix.android.api.comparators
|
||||
|
||||
import im.vector.matrix.android.api.interfaces.DatedObject
|
||||
import java.util.*
|
||||
|
||||
object DatedObjectComparators {
|
||||
|
||||
@ -38,4 +37,4 @@ object DatedObjectComparators {
|
||||
(datedObject2.date - datedObject1.date).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.crypto
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
|
||||
import im.vector.matrix.android.internal.crypto.verification.getEmojiForCode
|
||||
|
||||
/**
|
||||
* Provide all the emojis used for SAS verification (for debug purpose)
|
||||
*/
|
||||
fun getAllVerificationEmojis(): List<EmojiRepresentation> {
|
||||
return (0..63).map { getEmojiForCode(it) }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.crypto
|
||||
|
||||
/**
|
||||
* RoomEncryptionTrustLevel represents the trust level in an encrypted room.
|
||||
*/
|
||||
enum class RoomEncryptionTrustLevel {
|
||||
// No one in the room has been verified -> Black shield
|
||||
Default,
|
||||
|
||||
// There are one or more device un-verified -> the app should display a red shield
|
||||
Warning,
|
||||
|
||||
// All devices in the room are verified -> the app should display a green shield
|
||||
Trusted
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.extensions
|
||||
|
||||
fun Boolean?.orTrue() = this ?: true
|
||||
|
||||
fun Boolean?.orFalse() = this ?: false
|
@ -17,19 +17,23 @@
|
||||
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.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import java.util.*
|
||||
|
||||
/* ==========================================================================================
|
||||
* MXDeviceInfo
|
||||
* ========================================================================================== */
|
||||
|
||||
fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||
fun CryptoDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||
?.chunked(4)
|
||||
?.joinToString(separator = " ")
|
||||
|
||||
/* ==========================================================================================
|
||||
* DeviceInfo
|
||||
* ========================================================================================== */
|
||||
|
||||
fun List<DeviceInfo>.sortByLastSeen() {
|
||||
Collections.sort(this, DatedObjectComparators.descComparator)
|
||||
}
|
||||
fun List<DeviceInfo>.sortByLastSeen(): List<DeviceInfo> {
|
||||
val list = toMutableList()
|
||||
list.sortWith(DatedObjectComparators.descComparator)
|
||||
return list
|
||||
}
|
||||
|
@ -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.failure
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
fun Throwable.is401() =
|
||||
this is Failure.ServerError
|
||||
&& httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
||||
&& error.code == MatrixError.M_UNAUTHORIZED
|
||||
|
||||
fun Throwable.isTokenError() =
|
||||
this is Failure.ServerError
|
||||
&& (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN)
|
||||
|
||||
fun Throwable.shouldBeRetried(): Boolean {
|
||||
return this is Failure.NetworkConnection
|
||||
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
|
||||
}
|
@ -31,15 +31,16 @@ import java.io.IOException
|
||||
*/
|
||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
|
||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody"))
|
||||
|
||||
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
||||
|
||||
data class CryptoError(val error: MXCryptoError) : Failure(error)
|
||||
|
||||
abstract class FeatureFailure : Failure()
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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.failure
|
||||
|
||||
// This class will be sent to the bus
|
||||
sealed class GlobalError {
|
||||
data class InvalidToken(val softLogout: Boolean) : GlobalError()
|
||||
data class ConsentNotGivenError(val consentUri: String) : GlobalError()
|
||||
}
|
@ -22,46 +22,114 @@ import com.squareup.moshi.JsonClass
|
||||
/**
|
||||
* This data class holds the error defined by the matrix specifications.
|
||||
* You shouldn't have to instantiate it.
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#api-standards
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MatrixError(
|
||||
/** unique string which can be used to handle an error message */
|
||||
@Json(name = "errcode") val code: String,
|
||||
/** human-readable error message */
|
||||
@Json(name = "error") val message: String,
|
||||
|
||||
// For M_CONSENT_NOT_GIVEN
|
||||
@Json(name = "consent_uri") val consentUri: String? = null,
|
||||
// RESOURCE_LIMIT_EXCEEDED data
|
||||
// For M_RESOURCE_LIMIT_EXCEEDED
|
||||
@Json(name = "limit_type") val limitType: String? = null,
|
||||
@Json(name = "admin_contact") val adminUri: String? = null) {
|
||||
|
||||
@Json(name = "admin_contact") val adminUri: String? = null,
|
||||
// For M_LIMIT_EXCEEDED
|
||||
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
||||
// For M_UNKNOWN_TOKEN
|
||||
@Json(name = "soft_logout") val isSoftLogout: Boolean = false
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val FORBIDDEN = "M_FORBIDDEN"
|
||||
const val UNKNOWN = "M_UNKNOWN"
|
||||
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||
const val MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||
const val BAD_JSON = "M_BAD_JSON"
|
||||
const val NOT_JSON = "M_NOT_JSON"
|
||||
const val NOT_FOUND = "M_NOT_FOUND"
|
||||
const val LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||
const val USER_IN_USE = "M_USER_IN_USE"
|
||||
const val ROOM_IN_USE = "M_ROOM_IN_USE"
|
||||
const val BAD_PAGINATION = "M_BAD_PAGINATION"
|
||||
const val UNAUTHORIZED = "M_UNAUTHORIZED"
|
||||
const val OLD_VERSION = "M_OLD_VERSION"
|
||||
const val UNRECOGNIZED = "M_UNRECOGNIZED"
|
||||
/** Forbidden access, e.g. joining a room without permission, failed login. */
|
||||
const val M_FORBIDDEN = "M_FORBIDDEN"
|
||||
/** An unknown error has occurred. */
|
||||
const val M_UNKNOWN = "M_UNKNOWN"
|
||||
/** The access token specified was not recognised. */
|
||||
const val M_UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||
/** No access token was specified for the request. */
|
||||
const val M_MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||
/** Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys. */
|
||||
const val M_BAD_JSON = "M_BAD_JSON"
|
||||
/** Request did not contain valid JSON. */
|
||||
const val M_NOT_JSON = "M_NOT_JSON"
|
||||
/** No resource was found for this request. */
|
||||
const val M_NOT_FOUND = "M_NOT_FOUND"
|
||||
/** Too many requests have been sent in a short period of time. Wait a while then try again. */
|
||||
const val M_LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||
|
||||
const val LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET"
|
||||
const val THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
|
||||
// Error code returned by the server when no account matches the given 3pid
|
||||
const val THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
|
||||
const val THREEPID_IN_USE = "M_THREEPID_IN_USE"
|
||||
const val SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||
const val TOO_LARGE = "M_TOO_LARGE"
|
||||
/* ==========================================================================================
|
||||
* Other error codes the client might encounter are
|
||||
* ========================================================================================== */
|
||||
|
||||
/** Encountered when trying to register a user ID which has been taken. */
|
||||
const val M_USER_IN_USE = "M_USER_IN_USE"
|
||||
/** Sent when the room alias given to the createRoom API is already in use. */
|
||||
const val M_ROOM_IN_USE = "M_ROOM_IN_USE"
|
||||
/** (Not documented yet) */
|
||||
const val M_BAD_PAGINATION = "M_BAD_PAGINATION"
|
||||
/** The request was not correctly authorized. Usually due to login failures. */
|
||||
const val M_UNAUTHORIZED = "M_UNAUTHORIZED"
|
||||
/** (Not documented yet) */
|
||||
const val M_OLD_VERSION = "M_OLD_VERSION"
|
||||
/** The server did not understand the request. */
|
||||
const val M_UNRECOGNIZED = "M_UNRECOGNIZED"
|
||||
/** (Not documented yet) */
|
||||
const val M_LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET"
|
||||
/** Authentication could not be performed on the third party identifier. */
|
||||
const val M_THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
|
||||
/** Sent when a threepid given to an API cannot be used because no record matching the threepid was found. */
|
||||
const val M_THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
|
||||
/** Sent when a threepid given to an API cannot be used because the same threepid is already in use. */
|
||||
const val M_THREEPID_IN_USE = "M_THREEPID_IN_USE"
|
||||
/** The client's request used a third party server, eg. identity server, that this server does not trust. */
|
||||
const val M_SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||
/** The request or entity was too large. */
|
||||
const val M_TOO_LARGE = "M_TOO_LARGE"
|
||||
/** (Not documented yet) */
|
||||
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"
|
||||
/** The request cannot be completed because the homeserver has reached a resource limit imposed on it. For example,
|
||||
* a homeserver held in a shared hosting environment may reach a resource limit if it starts using too much memory
|
||||
* or disk space. The error MUST have an admin_contact field to provide the user receiving the error a place to reach
|
||||
* out to. Typically, this error will appear on routes which attempt to modify state (eg: sending messages, account
|
||||
* data, etc) and not routes which only read state (eg: /sync, get account data, etc). */
|
||||
const val M_RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||
/** The user ID associated with the request has been deactivated. Typically for endpoints that prove authentication, such as /login. */
|
||||
const val M_USER_DEACTIVATED = "M_USER_DEACTIVATED"
|
||||
/** Encountered when trying to register a user ID which is not valid. */
|
||||
const val M_INVALID_USERNAME = "M_INVALID_USERNAME"
|
||||
/** Sent when the initial state given to the createRoom API is invalid. */
|
||||
const val M_INVALID_ROOM_STATE = "M_INVALID_ROOM_STATE"
|
||||
/** The server does not permit this third party identifier. This may happen if the server only permits,
|
||||
* for example, email addresses from a particular domain. */
|
||||
const val M_THREEPID_DENIED = "M_THREEPID_DENIED"
|
||||
/** The client's request to create a room used a room version that the server does not support. */
|
||||
const val M_UNSUPPORTED_ROOM_VERSION = "M_UNSUPPORTED_ROOM_VERSION"
|
||||
/** The client attempted to join a room that has a version the server does not support.
|
||||
* Inspect the room_version property of the error response for the room's version. */
|
||||
const val M_INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION"
|
||||
/** The state change requested cannot be performed, such as attempting to unban a user who is not banned. */
|
||||
const val M_BAD_STATE = "M_BAD_STATE"
|
||||
/** The room or resource does not permit guests to access it. */
|
||||
const val M_GUEST_ACCESS_FORBIDDEN = "M_GUEST_ACCESS_FORBIDDEN"
|
||||
/** A Captcha is required to complete the request. */
|
||||
const val M_CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
||||
/** The Captcha provided did not match what was expected. */
|
||||
const val M_CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
||||
/** A required parameter was missing from the request. */
|
||||
const val M_MISSING_PARAM = "M_MISSING_PARAM"
|
||||
/** A parameter that was specified has the wrong value. For example, the server expected an integer and instead received a string. */
|
||||
const val M_INVALID_PARAM = "M_INVALID_PARAM"
|
||||
/** The resource being requested is reserved by an application service, or the application service making the request has not created the resource. */
|
||||
const val M_EXCLUSIVE = "M_EXCLUSIVE"
|
||||
/** The user is unable to reject an invite to join the server notices room. See the Server Notices module for more information. */
|
||||
const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||
/** (Not documented yet) */
|
||||
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
|
||||
// Possible value for "limit_type"
|
||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,4 +25,4 @@ interface ProgressListener {
|
||||
* @param total
|
||||
*/
|
||||
fun onProgress(progress: Int, total: Int)
|
||||
}
|
||||
}
|
||||
|
@ -31,4 +31,4 @@ interface StepProgressListener {
|
||||
* @param step The current step, containing progress data if available. Else you should consider progress as indeterminate
|
||||
*/
|
||||
fun onStepProgress(step: Step)
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.matrix.android.api.permalinks
|
||||
|
||||
import android.text.Spannable
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
|
||||
/**
|
||||
* MatrixLinkify take a piece of text and turns all of the
|
||||
@ -30,9 +29,15 @@ object MatrixLinkify {
|
||||
*
|
||||
* @param spannable the text in which the matrix items has to be clickable.
|
||||
*/
|
||||
fun addLinks(spannable: Spannable?, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||
/**
|
||||
* I disable it because it mess up with pills, and even with pills, it does not work correctly:
|
||||
* The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to
|
||||
*/
|
||||
/*
|
||||
// sanity checks
|
||||
if (spannable.isNullOrEmpty()) {
|
||||
if (spannable.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
val text = spannable.toString()
|
||||
@ -50,6 +55,7 @@ object MatrixLinkify {
|
||||
}
|
||||
}
|
||||
return hasMatch
|
||||
*/
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,4 @@ class MatrixPermalinkSpan(private val url: String,
|
||||
override fun onClick(widget: View) {
|
||||
callback?.onUrlClicked(url)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -24,14 +24,11 @@ import android.net.Uri
|
||||
*/
|
||||
sealed class PermalinkData {
|
||||
|
||||
data class EventLink(val roomIdOrAlias: String, val eventId: String) : PermalinkData()
|
||||
|
||||
data class RoomLink(val roomIdOrAlias: String) : PermalinkData()
|
||||
data class RoomLink(val roomIdOrAlias: String, val isRoomAlias: Boolean, val eventId: String?) : PermalinkData()
|
||||
|
||||
data class UserLink(val userId: String) : PermalinkData()
|
||||
|
||||
data class GroupLink(val groupId: String) : PermalinkData()
|
||||
|
||||
data class FallbackLink(val uri: Uri) : PermalinkData()
|
||||
|
||||
}
|
||||
|
@ -16,11 +16,10 @@
|
||||
|
||||
package im.vector.matrix.android.api.permalinks
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
/**
|
||||
* Useful methods to create Matrix permalink.
|
||||
* Useful methods to create Matrix permalink (matrix.to links).
|
||||
*/
|
||||
object PermalinkFactory {
|
||||
|
||||
@ -48,10 +47,9 @@ object PermalinkFactory {
|
||||
* @return the permalink, or null in case of error
|
||||
*/
|
||||
fun createPermalink(id: String): String? {
|
||||
return if (TextUtils.isEmpty(id)) {
|
||||
return if (id.isEmpty()) {
|
||||
null
|
||||
} else MATRIX_TO_URL_BASE + escape(id)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,23 +70,31 @@ object PermalinkFactory {
|
||||
* @param url the universal link, Ex: "https://matrix.to/#/@benoit:matrix.org"
|
||||
* @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink
|
||||
*/
|
||||
fun getLinkedId(url: String?): String? {
|
||||
val isSupported = url != null && url.startsWith(MATRIX_TO_URL_BASE)
|
||||
fun getLinkedId(url: String): String? {
|
||||
val isSupported = url.startsWith(MATRIX_TO_URL_BASE)
|
||||
|
||||
return if (isSupported) {
|
||||
url!!.substring(MATRIX_TO_URL_BASE.length)
|
||||
url.substring(MATRIX_TO_URL_BASE.length)
|
||||
} else null
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escape '/' in id, because it is used as a separator
|
||||
*
|
||||
* @param id the id to escape
|
||||
* @return the escaped id
|
||||
*/
|
||||
private fun escape(id: String): String {
|
||||
return id.replace("/".toRegex(), "%2F")
|
||||
internal fun escape(id: String): String {
|
||||
return id.replace("/", "%2F")
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescape '/' in id
|
||||
*
|
||||
* @param id the id to escape
|
||||
* @return the escaped id
|
||||
*/
|
||||
internal fun unescape(id: String): String {
|
||||
return id.replace("%2F", "/")
|
||||
}
|
||||
}
|
||||
|
@ -56,21 +56,25 @@ object PermalinkParser {
|
||||
|
||||
val identifier = params.getOrNull(0)
|
||||
val extraParameter = params.getOrNull(1)
|
||||
if (identifier.isNullOrEmpty()) {
|
||||
return PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
return when {
|
||||
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||
MatrixPatterns.isRoomId(identifier) -> {
|
||||
if (!extraParameter.isNullOrEmpty() && MatrixPatterns.isEventId(extraParameter)) {
|
||||
PermalinkData.EventLink(roomIdOrAlias = identifier, eventId = extraParameter)
|
||||
} else {
|
||||
PermalinkData.RoomLink(roomIdOrAlias = identifier)
|
||||
}
|
||||
identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
|
||||
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||
MatrixPatterns.isRoomId(identifier) -> {
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = identifier,
|
||||
isRoomAlias = false,
|
||||
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
|
||||
)
|
||||
}
|
||||
else -> PermalinkData.FallbackLink(uri)
|
||||
MatrixPatterns.isRoomAlias(identifier) -> {
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = identifier,
|
||||
isRoomAlias = true,
|
||||
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
|
||||
)
|
||||
}
|
||||
else -> PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -18,78 +18,111 @@ package im.vector.matrix.android.api.pushrules
|
||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||
import timber.log.Timber
|
||||
|
||||
sealed class Action {
|
||||
object Notify : Action()
|
||||
object DoNotNotify : Action()
|
||||
data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action()
|
||||
data class Highlight(val highlight: Boolean) : Action()
|
||||
}
|
||||
|
||||
class Action(val type: Type) {
|
||||
private const val ACTION_NOTIFY = "notify"
|
||||
private const val ACTION_DONT_NOTIFY = "dont_notify"
|
||||
private const val ACTION_COALESCE = "coalesce"
|
||||
|
||||
enum class Type(val value: String) {
|
||||
NOTIFY("notify"),
|
||||
DONT_NOTIFY("dont_notify"),
|
||||
COALESCE("coalesce"),
|
||||
SET_TWEAK("set_tweak");
|
||||
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
|
||||
private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
|
||||
|
||||
companion object {
|
||||
private const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
|
||||
private const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
|
||||
|
||||
fun safeValueOf(value: String): Type? {
|
||||
try {
|
||||
return valueOf(value)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return null
|
||||
}
|
||||
private const val ACTION_OBJECT_VALUE_KEY = "value"
|
||||
private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#actions
|
||||
*
|
||||
* Convert
|
||||
* <pre>
|
||||
* "actions": [
|
||||
* "notify",
|
||||
* {
|
||||
* "set_tweak": "sound",
|
||||
* "value": "default"
|
||||
* },
|
||||
* {
|
||||
* "set_tweak": "highlight"
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* To
|
||||
* [
|
||||
* Action.Notify,
|
||||
* Action.Sound("default"),
|
||||
* Action.Highlight(true)
|
||||
* ]
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
@Suppress("IMPLICIT_CAST_TO_ANY")
|
||||
fun List<Action>.toJson(): List<Any> {
|
||||
return map { action ->
|
||||
when (action) {
|
||||
is Action.Notify -> ACTION_NOTIFY
|
||||
is Action.DoNotNotify -> ACTION_DONT_NOTIFY
|
||||
is Action.Sound -> {
|
||||
mapOf(
|
||||
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
|
||||
ACTION_OBJECT_VALUE_KEY to action.sound
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
is Action.Highlight -> {
|
||||
mapOf(
|
||||
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
|
||||
ACTION_OBJECT_VALUE_KEY to action.highlight
|
||||
)
|
||||
}
|
||||
return if (actions.isEmpty()) null else actions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PushRule.getActions(): List<Action> {
|
||||
val result = ArrayList<Action>()
|
||||
|
||||
actions.forEach { actionStrOrObj ->
|
||||
when (actionStrOrObj) {
|
||||
ACTION_NOTIFY -> Action.Notify
|
||||
ACTION_DONT_NOTIFY -> Action.DoNotNotify
|
||||
is Map<*, *> -> {
|
||||
when (actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]) {
|
||||
ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
|
||||
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
|
||||
Action.Sound(stringValue)
|
||||
}
|
||||
// When the value is not there, default sound (not specified by the spec)
|
||||
?: Action.Sound(ACTION_OBJECT_VALUE_VALUE_DEFAULT)
|
||||
}
|
||||
ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
|
||||
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
|
||||
Action.Highlight(boolValue)
|
||||
}
|
||||
// When the value is not there, default is true, says the spec
|
||||
?: Action.Highlight(true)
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unsupported set_tweak value ${actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unsupported action type $actionStrOrObj")
|
||||
null
|
||||
}
|
||||
}?.let {
|
||||
result.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -35,9 +35,7 @@ abstract class Condition(val kind: Kind) {
|
||||
else -> UNRECOGNIZE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean
|
||||
@ -45,4 +43,4 @@ abstract class Condition(val kind: Kind) {
|
||||
open fun technicalDescription(): String {
|
||||
return "Kind: $kind"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,4 +25,4 @@ interface ConditionResolver {
|
||||
fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean
|
||||
fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean
|
||||
fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,11 @@
|
||||
*/
|
||||
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) {
|
||||
|
||||
@ -34,11 +32,11 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, displayName: String): Boolean {
|
||||
var message = when (event.type) {
|
||||
val message = when (event.type) {
|
||||
EventType.MESSAGE -> {
|
||||
event.content.toModel<MessageContent>()
|
||||
}
|
||||
//TODO the spec says:
|
||||
// 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>()
|
||||
@ -49,7 +47,6 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
return caseInsensitiveFind(displayName, message.body)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Returns whether a string contains an occurrence of another, as a standalone word, regardless of case.
|
||||
@ -60,20 +57,18 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
*/
|
||||
fun caseInsensitiveFind(subString: String, longString: String): Boolean {
|
||||
// add sanity checks
|
||||
if (TextUtils.isEmpty(subString) || TextUtils.isEmpty(longString)) {
|
||||
if (subString.isEmpty() || longString.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
var res = false
|
||||
|
||||
try {
|
||||
val pattern = Pattern.compile("(\\W|^)" + Pattern.quote(subString) + "(\\W|$)", Pattern.CASE_INSENSITIVE)
|
||||
res = pattern.matcher(longString).find()
|
||||
val regex = Regex("(\\W|^)" + Regex.escape(subString) + "(\\W|$)", RegexOption.IGNORE_CASE)
|
||||
return regex.containsMatchIn(longString)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## caseInsensitiveFind() : failed")
|
||||
}
|
||||
|
||||
return res
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,28 +29,25 @@ class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind
|
||||
return "'$key' Matches '$pattern'"
|
||||
}
|
||||
|
||||
|
||||
fun isSatisfied(event: Event): Boolean {
|
||||
//TODO encrypted events?
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
@ -77,9 +74,9 @@ class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind
|
||||
return glob.contains("*") || glob.contains("?")
|
||||
}
|
||||
|
||||
//Very simple glob to regexp converter
|
||||
// Very simple glob to regexp converter
|
||||
private fun simpleGlobToRegExp(glob: String): String {
|
||||
var out = ""//"^"
|
||||
var out = "" // "^"
|
||||
for (i in 0 until glob.length) {
|
||||
val c = glob[i]
|
||||
when (c) {
|
||||
@ -90,8 +87,8 @@ class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind
|
||||
else -> out += c
|
||||
}
|
||||
}
|
||||
out += ""//'$'.toString()
|
||||
out += "" // '$'.toString()
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,14 +25,18 @@ interface PushRuleService {
|
||||
/**
|
||||
* Fetch the push rules from the server
|
||||
*/
|
||||
fun fetchPushRules(scope: String = "global")
|
||||
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
|
||||
|
||||
//TODO get push rule set
|
||||
fun getPushRules(scope: String = "global"): List<PushRule>
|
||||
// TODO get push rule set
|
||||
fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
|
||||
|
||||
//TODO update rule
|
||||
// TODO update rule
|
||||
|
||||
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun addPushRuleListener(listener: PushRuleListener)
|
||||
|
||||
@ -42,7 +46,9 @@ interface PushRuleService {
|
||||
|
||||
interface PushRuleListener {
|
||||
fun onMatchRule(event: Event, actions: List<Action>)
|
||||
fun onRoomJoined(roomId: String)
|
||||
fun onRoomLeft(roomId: String)
|
||||
fun onEventRedacted(redactedEventId: String)
|
||||
fun batchFinish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,18 +18,17 @@ 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
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
|
||||
class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) {
|
||||
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 $`is`"
|
||||
return "Room member count is $iz"
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
||||
@ -56,16 +55,12 @@ class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_co
|
||||
*/
|
||||
private fun parseIsField(): Pair<String?, Int>? {
|
||||
try {
|
||||
val match = regex.matcher(`is`)
|
||||
if (match.find()) {
|
||||
val prefix = match.group(1)
|
||||
val count = match.group(2).toInt()
|
||||
return prefix to count
|
||||
}
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user