Compare commits
1553 Commits
Author | SHA1 | Date | |
---|---|---|---|
197352b669 | |||
|
d8d1c1192f | ||
|
995d975127 | ||
|
cce5a7b39f | ||
|
4a43df8c97 | ||
|
ed4a190793 | ||
|
719102d02f | ||
|
c3926a9ef8 | ||
|
1ab391931d | ||
|
490115d20e | ||
|
911e392006 | ||
|
b925f436fd | ||
|
5cee46cda5 | ||
|
014643b9c4 | ||
|
c3cbb21133 | ||
|
7bcf173866 | ||
|
225cca4566 | ||
|
4ff3c36ed9 | ||
|
768eadde36 | ||
|
82c5924434 | ||
|
e11277c70f | ||
|
01b207d44d | ||
|
d3194172c5 | ||
|
f5da4791ad | ||
|
837c212931 | ||
|
ad601e47c8 | ||
|
ea6289c02e | ||
|
d6e51288c3 | ||
|
48147b7fd1 | ||
|
432598f896 | ||
|
7ac26952d7 | ||
|
c8bd5bc1f5 | ||
|
2fc216bfc4 | ||
|
74cde1d60a | ||
|
85dc0c284d | ||
|
002dbf2e17 | ||
|
da87eac48e | ||
|
9a57673130 | ||
|
8d6b2074cb | ||
|
7a2856ac86 | ||
|
f7258d16e1 | ||
|
25e993693f | ||
|
6c95897f09 | ||
|
2a198793b1 | ||
|
58f3787795 | ||
|
8a729061d5 | ||
|
8285a4fe1c | ||
|
217335703c | ||
|
0a20b87ebe | ||
|
febee9863f | ||
|
2dce71cd96 | ||
|
8afe7efc2c | ||
|
dfa17d1339 | ||
|
91119ab691 | ||
|
ea6b756c0f | ||
|
3d0b127a8f | ||
|
7623accfd5 | ||
|
35a4b848a5 | ||
|
45f92553c4 | ||
|
ecc0ef45c5 | ||
|
edb28ccb31 | ||
|
19c1484053 | ||
|
8de81e0bc8 | ||
|
4d1b6d4404 | ||
|
c5824a3b2a | ||
|
a1f9f5e774 | ||
|
8415f27cac | ||
|
abf84e065d | ||
|
da00a58902 | ||
|
7cbef529ae | ||
|
c400f74918 | ||
|
67bc032ccc | ||
|
ae0ba3bbc1 | ||
|
7df0f3fd28 | ||
|
ef980ff5cf | ||
|
2ada4d5cfe | ||
|
f2409ed95e | ||
|
5cac4397dd | ||
|
7cf360fabf | ||
|
2b9cdde558 | ||
|
7d4d5c437a | ||
|
311c99bb6d | ||
|
4a2e222b34 | ||
|
7c1e1132b0 | ||
|
b2aae44645 | ||
|
cbbf6f439b | ||
|
90afaab8ed | ||
|
59e39304dd | ||
|
c85b3bbacc | ||
|
6dad1c0dde | ||
|
4faf0f2ec9 | ||
|
2c5250a82c | ||
|
cbce73c301 | ||
|
24ab765e11 | ||
|
6ec8e57fbb | ||
|
14fd41cd7f | ||
|
19b4207c3a | ||
|
d24022d755 | ||
|
f8e031ac86 | ||
|
5fd4169720 | ||
|
3c90dbe723 | ||
|
2ed71df01a | ||
|
2e380ed792 | ||
|
5d9d725446 | ||
|
95405fde5f | ||
|
24768d051d | ||
|
f67baab983 | ||
|
a1fe8f1c87 | ||
|
00bb527333 | ||
|
10025123e9 | ||
|
f7f6e8dfd5 | ||
|
012f60be56 | ||
|
51badfa721 | ||
|
e722ef6477 | ||
|
612e0afa63 | ||
|
ed7882fe69 | ||
|
3889c0eb01 | ||
|
9dcd0bf16b | ||
|
2cf05528b4 | ||
|
e48517b0c8 | ||
|
7b271bcf67 | ||
|
50f61a4d91 | ||
|
6ccdd1227e | ||
|
e973117aed | ||
|
3ece613a5d | ||
|
fe29b51290 | ||
|
51d1223198 | ||
|
5a5eb5bdd3 | ||
|
7cd4ed78b2 | ||
|
9fd6539534 | ||
|
7e5e32e9eb | ||
|
41b3df0d43 | ||
|
2f577097d1 | ||
|
5056a28553 | ||
|
91db2023d3 | ||
|
185dac6953 | ||
|
a5d9932b08 | ||
|
b0a8da35db | ||
|
0f2e60d576 | ||
|
9a7d925b97 | ||
|
be371ac5df | ||
|
7b6d49f329 | ||
|
404cf808b0 | ||
|
f98888d796 | ||
|
839178b269 | ||
|
cc1402442a | ||
|
f47cf7ae67 | ||
|
ef78721f94 | ||
|
7c31a981bb | ||
|
cec9b9b35a | ||
|
5cadc81de4 | ||
|
84baa3ae68 | ||
|
15e5ccd1f4 | ||
|
1a72683b52 | ||
|
7ba81177c6 | ||
|
adff2baa4a | ||
|
b3d4d0608f | ||
|
a5e15d5a10 | ||
|
f6c1af2b06 | ||
|
48e3726071 | ||
|
a55d0a691c | ||
|
8e0fb6a23a | ||
|
4d1308e138 | ||
|
e6eb58f2d7 | ||
|
2cfa3ce288 | ||
|
848c339c8d | ||
|
c837e0616a | ||
|
c93b1a86bb | ||
|
9f5a089d5c | ||
|
c33f4b704c | ||
|
9cc37a7bdb | ||
|
ae85a9e87b | ||
|
067daa81f4 | ||
|
d86a4642e9 | ||
|
a4454bcff2 | ||
|
4a915c60e9 | ||
|
6c34763d32 | ||
|
1ac0c2f453 | ||
|
483304c697 | ||
|
c86134e523 | ||
|
d220d9db05 | ||
|
403eff3d19 | ||
|
bf07196707 | ||
|
0904ba42f8 | ||
|
0229a1605e | ||
|
8742194f4b | ||
|
4fd11a4c2b | ||
|
4bbf2dab99 | ||
|
d4c2b12f95 | ||
|
3467a67e75 | ||
|
03fff3179e | ||
|
5a27da848b | ||
|
41112d29bd | ||
|
ed8336ee54 | ||
|
11e26e71f6 | ||
|
e98f7e57b0 | ||
|
cc1aa9e431 | ||
|
56f202d4b8 | ||
|
d91c314ac2 | ||
|
65b3655a8b | ||
|
06d1e1752c | ||
|
649d1243d5 | ||
|
99565a6876 | ||
|
26badb7f4c | ||
|
3b3121b9c5 | ||
|
401329caaa | ||
|
f75eb6bc43 | ||
|
081c33b886 | ||
|
b34a1f7f5e | ||
|
75749d7c7b | ||
|
9682f4d454 | ||
|
4bfc54e51d | ||
|
cf96a5e840 | ||
|
92ac82f94d | ||
|
3171dcac8d | ||
|
4e898fa4f2 | ||
|
c7912ed7ef | ||
|
37465db817 | ||
|
024dbd1c32 | ||
|
cb96911da3 | ||
|
d8901441f6 | ||
|
892b1b02af | ||
|
b4318996ac | ||
|
3af30d7563 | ||
|
2302122d8e | ||
|
00e8ba00c4 | ||
|
010b341251 | ||
|
c97c5def2c | ||
|
76b9010c39 | ||
|
644a3a729d | ||
|
e58f72c20f | ||
|
c2c6904eda | ||
|
4171e2b90e | ||
|
0db5928031 | ||
|
e5f08a5eae | ||
|
4d2a1afaf9 | ||
|
6167ddb4a8 | ||
|
30ed6b3cec | ||
|
ce336690e2 | ||
|
91a69d7d85 | ||
|
935ac25cbd | ||
|
ba32217152 | ||
|
95e33b5a97 | ||
|
09d68ae603 | ||
|
0b11d0f7f2 | ||
|
9c588ae9eb | ||
|
0ee13bbecb | ||
|
1fd3bbfe25 | ||
|
5ea4322d3f | ||
|
8a3c996164 | ||
|
cdf2bd07df | ||
|
c3b2f33956 | ||
|
d56fb3cec2 | ||
|
19b7179bcd | ||
|
7747fc0ea1 | ||
|
6b72e18982 | ||
|
1f3be1597c | ||
|
9d043076e8 | ||
|
8ca16a6f63 | ||
|
064926a18b | ||
|
a3b51f7654 | ||
|
233ee8a51c | ||
|
721250c7d9 | ||
|
8502d519c1 | ||
|
5b17734287 | ||
|
254edc48f5 | ||
|
3c951ce2cd | ||
|
87f0c4a646 | ||
|
5fae9465d1 | ||
|
7306e0286f | ||
|
857c761c68 | ||
|
79b2f34685 | ||
|
9f3543a650 | ||
|
9ba5335ae7 | ||
|
633aad3193 | ||
|
1c56b74e41 | ||
|
5a00b11594 | ||
|
4a7dbaab1e | ||
|
6088913fb6 | ||
|
aa24a057fc | ||
|
cae993a95f | ||
|
bb7780eae6 | ||
|
b0ada55b28 | ||
|
f414998307 | ||
|
72537b76c5 | ||
|
b86dd584dc | ||
|
cb203f5fd3 | ||
|
d166309393 | ||
|
f2d2966b31 | ||
|
5c789b75cc | ||
|
58595fccfe | ||
|
768d79c621 | ||
|
afa34ce15a | ||
|
469cf72e56 | ||
|
51ef096e01 | ||
|
8cce653a1f | ||
|
c740386297 | ||
|
ea1e28267b | ||
|
ecd53580c0 | ||
|
23a91e5019 | ||
|
4cddf31ad2 | ||
|
1f4c5ff97c | ||
|
c7ec82679f | ||
|
8e0e81a603 | ||
|
0c0ff882a9 | ||
|
a28116753a | ||
|
d3dd80eec9 | ||
|
0d00ff0c15 | ||
|
abaabae853 | ||
|
79858278e0 | ||
|
c69ad8f52c | ||
|
cf9ab4d302 | ||
|
4f0e1b71c0 | ||
|
caa16a8517 | ||
|
6e62b62b80 | ||
|
c7e95d105c | ||
|
bb249e0881 | ||
|
b90a1fe1db | ||
|
2665c3a1e0 | ||
|
9116782cdc | ||
|
c8f97be68e | ||
|
6b81ff70e5 | ||
|
5e367cb115 | ||
|
edb3f57242 | ||
|
54ae8abcb7 | ||
|
82385e38f6 | ||
|
55e9067b27 | ||
|
c76b073b8e | ||
|
f60dd98d3c | ||
|
7317f5015a | ||
|
6569f20737 | ||
|
d9e2ab62b2 | ||
|
24fab162e2 | ||
|
71ad18beb9 | ||
|
c37117b940 | ||
|
6be3ae8ef0 | ||
|
e35ada4997 | ||
|
6313c3c92e | ||
|
853862ec9c | ||
|
e459616123 | ||
|
8ab166e817 | ||
|
d7198bd68c | ||
|
6fc67d9a60 | ||
|
5fd91fcd8e | ||
|
cd3ce76115 | ||
2a34ece571 | |||
|
8a1ebf2bbe | ||
|
0d199c8ceb | ||
|
7651700c2a | ||
|
eea1bc8090 | ||
|
53241f2ef1 | ||
|
e4524e2c7b | ||
|
c9e6d05fa0 | ||
|
be84443921 | ||
|
bbceee7f61 | ||
|
40ee1a0bfc | ||
|
a86b2fefd9 | ||
|
f2d9539d90 | ||
|
66457c9f2e | ||
|
9b6ae6d75f | ||
|
4c6ef3b24e | ||
|
b48bf39e08 | ||
|
7035f38e0b | ||
|
d53c813408 | ||
|
b72d7ec8d0 | ||
|
5dde977233 | ||
|
2f4eee1fa7 | ||
|
96a6460744 | ||
|
780d1daf7e | ||
|
0f870223c4 | ||
|
5faa05ca19 | ||
|
97ba0a0d49 | ||
|
cb9c4d4327 | ||
|
c324f0c8df | ||
|
59f82cbd34 | ||
|
143ad48be1 | ||
|
1dcf804618 | ||
|
ac2eee8e81 | ||
|
764026b87e | ||
|
7219e077f4 | ||
|
bfc2cffc2f | ||
|
f7c5a5c42e | ||
|
9bdd2bf1ae | ||
|
d028f4b398 | ||
|
b085426d22 | ||
|
a71e3d0653 | ||
|
8f39a594ff | ||
|
ebf8ae231a | ||
|
aa7bfe9fe7 | ||
|
b2e9b4aeb1 | ||
|
8e025cbb9e | ||
|
1876b444fa | ||
|
c03e3b5965 | ||
|
fd7216b6a0 | ||
|
585a538340 | ||
|
b050ff2576 | ||
|
bfacc180c5 | ||
|
2c1d3ef968 | ||
|
313baca84e | ||
|
f0c3b31a42 | ||
|
a1cb855739 | ||
|
ef4ed90811 | ||
|
39bb8ad05f | ||
|
b09b8136d2 | ||
|
a994d8f847 | ||
|
b19572ba8c | ||
|
d192c529e0 | ||
|
b116926bb1 | ||
|
39c8867ed7 | ||
|
1269123816 | ||
|
cd772360db | ||
|
4a299920dc | ||
|
e6ba8484fa | ||
|
470d244414 | ||
|
2bb7bc1455 | ||
|
5a670c88b0 | ||
|
fa70bd7536 | ||
|
b8b2051f4c | ||
|
8c34bb3c6f | ||
|
40a9f70478 | ||
|
fcd9ab17fe | ||
|
b8f67bfaa3 | ||
|
82c2e89d21 | ||
|
593dd259a9 | ||
|
c43f224e8b | ||
|
9972f5eabc | ||
|
28c64c2bd1 | ||
|
d03c431137 | ||
|
6c10f8a232 | ||
|
f77afd9596 | ||
|
b011d46ff2 | ||
|
e5fff42b10 | ||
|
fbbf1a37b4 | ||
|
dbda2afd6d | ||
|
87746ca2ba | ||
|
da914ba09c | ||
|
75ee14cfdf | ||
|
55b60f6b0f | ||
|
88321c1e8c | ||
|
8abfbf82fa | ||
|
8d127f70d0 | ||
|
8eb292d16a | ||
|
1739af2a41 | ||
|
b879fb3753 | ||
|
cbc9c1fb20 | ||
|
1e7b4030bb | ||
|
1a89915b31 | ||
|
a5b3c579c4 | ||
|
56991bbaeb | ||
|
6e289b8738 | ||
|
599f7dad2c | ||
|
d4b1119240 | ||
|
6b0242523b | ||
|
5d4aa04e5d | ||
|
58de10bcab | ||
|
e127ba9361 | ||
|
6e95ad4bdf | ||
|
168ad50ddd | ||
|
f0f2aab92d | ||
|
96a992353b | ||
|
c62f3f99be | ||
|
a7ec23ef30 | ||
|
1b9a91eb2f | ||
|
9d744add38 | ||
|
9e7a54849d | ||
|
33e6d8a1ce | ||
|
e5d7357e6e | ||
|
84a2fa0041 | ||
|
bbe01c9a6a | ||
|
fb6f0649c3 | ||
|
fdf19ae287 | ||
|
d983f0bc71 | ||
|
22ca8200fa | ||
|
988cce6320 | ||
|
f4a769080b | ||
|
f36dff485e | ||
|
6320a3ca7c | ||
|
43fd5e5fe6 | ||
|
84a4f4d66d | ||
|
a87f7903c6 | ||
|
1e59a9517a | ||
|
6a5d2e35b5 | ||
|
cbd45d3ee5 | ||
|
2ec7165381 | ||
|
20d3a41b52 | ||
|
839ef8e14b | ||
|
4720ac94d3 | ||
|
07fe434cc7 | ||
|
d2268c6a6f | ||
|
d76b0a3104 | ||
|
1a7e0fd153 | ||
|
6631705aea | ||
|
7b99346a4b | ||
|
1c31b96920 | ||
|
568d6c8392 | ||
|
64e8035f6d | ||
|
b71aa6d3a4 | ||
|
2614706d39 | ||
|
cb639f3fdd | ||
|
6362799d56 | ||
|
40c747660d | ||
|
8132480b82 | ||
|
3bf2876e09 | ||
|
1820b163a1 | ||
|
965f73f95a | ||
|
2b9b3be3f1 | ||
|
a86a36f570 | ||
|
01f92ef4ee | ||
|
d68b7cfcfc | ||
|
fef601b4ae | ||
|
0303c28ad9 | ||
|
1ed2445c1d | ||
|
a7ee8f8a74 | ||
|
9d9a9e63ad | ||
|
99a41265b8 | ||
|
7ec38bd202 | ||
|
211354ee26 | ||
|
7e2e42cb11 | ||
|
3f3b360eee | ||
|
ad9a8c2281 | ||
|
4d965e96ed | ||
|
5007aa1b07 | ||
|
d8bff08f1f | ||
|
ec63900ef3 | ||
|
48afeb571b | ||
|
e84af51272 | ||
|
d61b00604d | ||
|
05fc15be3d | ||
|
6da8b50d95 | ||
|
a753e28ad2 | ||
|
1d3167b520 | ||
|
035d0c7957 | ||
|
bec048407a | ||
|
fe62ef32ae | ||
|
f7c2cd4807 | ||
|
e8cc959a7f | ||
|
bd578c59bf | ||
|
bb4952c89e | ||
|
698ddadbee | ||
|
1ef8d0a746 | ||
|
bca8f11c9c | ||
|
297c0a792f | ||
|
1a57599da2 | ||
|
00b3d5ee35 | ||
|
b3c19f039c | ||
|
d341904c4d | ||
|
7978fd768e | ||
|
b390908610 | ||
|
64ad93dad6 | ||
|
9edbddd7e1 | ||
|
d369ec767f | ||
|
2c004857f6 | ||
|
544c5b4a21 | ||
|
e582b9fc10 | ||
|
e538272417 | ||
|
20ddba2aa9 | ||
|
07a71d312a | ||
|
a5181b22e0 | ||
|
ffebb4677a | ||
|
a44f35ed69 | ||
|
1e4b1a3346 | ||
|
8557120ef8 | ||
|
a4020e85f6 | ||
|
8c1bb058da | ||
|
10398cab51 | ||
|
f2696b66ba | ||
|
52d4be4249 | ||
|
0f62ff6736 | ||
|
44ce5df359 | ||
|
fd4e15ba97 | ||
|
8835f08cf7 | ||
|
c3423d6ffe | ||
|
dce8149aae | ||
|
7226fc0010 | ||
|
50780debf7 | ||
|
f8c21caec9 | ||
|
22d13a3dcd | ||
|
dc02e2b498 | ||
|
6371d2b7a9 | ||
|
2a73b8d76e | ||
|
f6cfa27741 | ||
|
501152bcfd | ||
|
9e54fd5c92 | ||
|
cd1c05a7c3 | ||
|
f7d51b8890 | ||
|
c5bdb04490 | ||
|
74087b873f | ||
|
ad8b9eb054 | ||
|
f3ef8d4978 | ||
|
9efef24a04 | ||
|
5a73a6b139 | ||
|
1f7f82da7b | ||
|
26e33de79a | ||
|
187825d6c6 | ||
|
6d5f23213b | ||
|
0af13fc746 | ||
|
5530b0b0e2 | ||
|
40e5090bdd | ||
|
9f060f477f | ||
|
8d8cb92e43 | ||
|
27af6a4b1e | ||
|
082c06a486 | ||
|
5ac0e9267d | ||
|
cea52b0722 | ||
|
f4a883848c | ||
|
b6e7def9db | ||
|
7c6d1d19d5 | ||
|
dcd6ef8f84 | ||
|
d6c2ff9782 | ||
|
b0fb9fd9ee | ||
|
e275fd8143 | ||
|
43f5dfe174 | ||
|
f0dbcce58f | ||
|
41db773b08 | ||
|
5cd8917122 | ||
|
bb48f67a30 | ||
|
1339b9c464 | ||
|
cee3c98a23 | ||
|
343d895a26 | ||
|
13ed27f91e | ||
|
401759cdc7 | ||
|
61f58b3dbd | ||
|
de7c0c5121 | ||
|
9aaa5b78f4 | ||
|
18ab826413 | ||
|
5790d4c4ab | ||
|
98ab9beec7 | ||
|
7bda624723 | ||
|
7eac903277 | ||
|
badc97e280 | ||
|
7c608c8862 | ||
|
6b904d4de1 | ||
|
858a327299 | ||
|
7bdd4166c0 | ||
|
00c04cd413 | ||
|
3e6747c880 | ||
|
9d0a333372 | ||
|
af55aeca58 | ||
|
521469a57d | ||
|
569b7bf6d0 | ||
|
3cdf5f9afc | ||
|
15c807730e | ||
|
7b445bc4c7 | ||
|
8ca5eb4429 | ||
|
ab63dba8aa | ||
|
4359afacb4 | ||
|
7b52e6984c | ||
|
869ee3d438 | ||
|
d3dfecae8a | ||
|
6cb2b0b5d1 | ||
|
1a0b538166 | ||
|
805717673c | ||
|
e6e46651c9 | ||
|
75fcab3170 | ||
|
59b2e281a3 | ||
|
6fd9888b3b | ||
|
c3b11e515e | ||
|
edf0ae9aa6 | ||
|
00cbf8458a | ||
|
ac9f13a9f2 | ||
|
a54a7dca30 | ||
|
416481bb65 | ||
|
ba6b4763d2 | ||
|
e1d2c32e63 | ||
|
257d1e42d8 | ||
|
7e81149869 | ||
|
1dc55f72e3 | ||
|
d2c475d501 | ||
|
ad09d7dc49 | ||
|
aca7054174 | ||
|
f7d8580969 | ||
|
f14ab4c391 | ||
|
7917c19d18 | ||
|
3685c8cd2a | ||
|
d32cbcc70d | ||
|
af329eff46 | ||
|
b747afb44c | ||
|
2c187d0e7c | ||
|
caafd03130 | ||
|
3d5940cb76 | ||
|
78e962ce67 | ||
|
ea0e6d0619 | ||
|
ad994a2f4c | ||
|
fd54dc5aff | ||
|
76cbb4f727 | ||
|
e33d8451a8 | ||
|
f931c08da7 | ||
|
b52f079292 | ||
|
9e0145a8f6 | ||
|
e98ab37c9d | ||
|
cbda5a5016 | ||
|
910b38ec13 | ||
|
b0cdc2745c | ||
|
2e4713897d | ||
|
542626758d | ||
|
a4d342683e | ||
|
0b9d38cf32 | ||
|
f1ecbf2ff8 | ||
|
6f72128c45 | ||
|
8927ba8065 | ||
|
a0038565c5 | ||
|
285d86b375 | ||
|
cf909afc60 | ||
|
2a139a4b47 | ||
|
0528a47b8a | ||
|
b5d3859b22 | ||
|
343bb7ff28 | ||
|
94aee445e7 | ||
|
4736d12e99 | ||
|
eb8b6165d7 | ||
|
81b0f60860 | ||
|
8b6f06f0f9 | ||
|
08725ba2bb | ||
|
9bfdbc708e | ||
|
856029a611 | ||
|
a51de9fcd9 | ||
|
121312d103 | ||
|
d02e24248f | ||
|
8b331895d1 | ||
|
ed2fa20414 | ||
|
9dc8e3db9d | ||
|
1b114beb0b | ||
|
3c48b14448 | ||
|
0e96e0a796 | ||
|
c06aceaae9 | ||
|
04976fe333 | ||
|
178229ac60 | ||
|
dbab43e423 | ||
|
cf7df84cab | ||
|
701140fe92 | ||
|
58a3ef46ce | ||
|
82908fb54b | ||
|
3409399ef1 | ||
|
198a9f2226 | ||
|
89a05265ea | ||
|
3d372cb339 | ||
|
6dcce76568 | ||
|
3a5735e717 | ||
|
e9c00c0427 | ||
|
c8188ee52c | ||
|
2843a0af26 | ||
|
e90e333f29 | ||
|
eb3ac1c326 | ||
|
3e50d4831f | ||
|
0bc5dbdf94 | ||
|
baa149924a | ||
|
1db85e582e | ||
|
2803d342e1 | ||
|
223d50c1a0 | ||
|
27690865a6 | ||
|
58d5d2a1be | ||
|
ff1b23b4d9 | ||
|
be4aa2afc9 | ||
|
01a4d2ea25 | ||
|
f9aca85edf | ||
|
57e51bc735 | ||
|
cdee91363c | ||
|
ac8aa63916 | ||
|
369e7172d6 | ||
|
09aba0a062 | ||
|
9efa242d96 | ||
|
30110431ba | ||
|
91c3732c63 | ||
|
f7933c26d7 | ||
|
1d79a677c8 | ||
|
b5caa8fa35 | ||
|
8882c6b6fd | ||
|
e63d6b4bf2 | ||
|
9a7f51520e | ||
|
4e6d16c49b | ||
|
e52f662569 | ||
|
72a2622c84 | ||
|
97fe14c4be | ||
|
78e3afc1af | ||
|
d2ca0c7fe8 | ||
|
4d5e0c291e | ||
|
982a20fef5 | ||
|
4ba5472d0c | ||
|
d28d968985 | ||
|
34454ef2ec | ||
|
4d1640d6ff | ||
|
e88f01923f | ||
|
1166619539 | ||
|
28dc888159 | ||
|
ea1e4c773d | ||
|
37e7175a86 | ||
|
85c82d9b3b | ||
|
829720409d | ||
|
f91d16cbe7 | ||
|
b92b3863b9 | ||
|
fc3aefd56e | ||
|
dcc13d7a3d | ||
|
48a7818e88 | ||
|
1eb776f39c | ||
|
f8b1e8098c | ||
|
60588af825 | ||
|
f99f21ab9b | ||
|
5f4471a45e | ||
|
cb5393c32f | ||
|
5f40a7042d | ||
|
e0575642b5 | ||
|
73679b97f1 | ||
|
49de43b364 | ||
|
f9600b950f | ||
|
95a51ea2e0 | ||
|
39ad426ca9 | ||
|
40f81f19df | ||
|
587fb3cca3 | ||
|
490a1ca3cf | ||
|
ea667a1a73 | ||
|
f4e3cd5098 | ||
|
c4680e3198 | ||
|
31dd7b5a21 | ||
|
74d376be68 | ||
|
5017e8564c | ||
|
a70f57358e | ||
|
4bf9a1e809 | ||
|
e2a803ee04 | ||
|
4b9b7257a9 | ||
|
cb7c47bc62 | ||
|
33a02faad9 | ||
|
a018935b23 | ||
|
112a4d389e | ||
|
7932244c51 | ||
|
b88128241e | ||
|
9f42ead747 | ||
|
92bad0fa1e | ||
|
d089ceac13 | ||
|
8e6f054e52 | ||
|
36ae840d76 | ||
|
7a97da6d21 | ||
|
794353ad0c | ||
|
71e9117176 | ||
|
6639d0f23b | ||
|
becc3eb867 | ||
|
7398424f3b | ||
|
e26d842549 | ||
|
17c62b5991 | ||
|
161fdf7340 | ||
|
e402348f9b | ||
|
583aba1b44 | ||
|
594aab56db | ||
|
25211f13b3 | ||
|
e43a01159c | ||
|
45cc33ca36 | ||
|
20ba1add1e | ||
|
91732b89ea | ||
|
add8e2cb74 | ||
|
15316e6a7f | ||
|
5c5d5cc4e3 | ||
|
24ea66c9fc | ||
|
ffba53777c | ||
|
ea6a008b39 | ||
|
1838023c88 | ||
|
b3337c4ad7 | ||
|
b7c8ce1511 | ||
|
6d0e5f4354 | ||
|
5b9ba79495 | ||
|
9321ccc775 | ||
|
8eb1640a26 | ||
|
be0fc59314 | ||
|
272cffe797 | ||
|
762820072a | ||
|
ea18ceae4a | ||
|
71787bd2e1 | ||
|
49cefd1c0c | ||
|
9afafe387a | ||
|
107ab85a22 | ||
|
d89d7ade84 | ||
|
c3ec3ea70a | ||
|
2c55954ddd | ||
|
aaf5233efe | ||
|
422fd1847f | ||
|
ce0888b077 | ||
|
fde27f447f | ||
|
b3f50d1ad0 | ||
|
bc326efd2c | ||
|
bc36f1950f | ||
|
ae7543bbfc | ||
|
06bef5de8d | ||
|
25f6651848 | ||
|
29bd1103c0 | ||
|
a241ab66de | ||
|
f70fcc7bb8 | ||
|
44833c1499 | ||
|
82c3cbaf2a | ||
|
21ebb35e44 | ||
|
d9ff61ea2e | ||
|
841e718d6a | ||
|
c4e82eb3f8 | ||
|
c06e2787c7 | ||
|
83adbb6052 | ||
|
5137837f6d | ||
|
c65c314801 | ||
|
79796b0079 | ||
|
b69ab65b12 | ||
|
abbdf232c6 | ||
|
d84cf4e6d1 | ||
|
e5b8302fd9 | ||
|
33218ec32a | ||
|
a8420c9ad0 | ||
|
277e3d59c8 | ||
|
e1cf7b8cb6 | ||
|
9ce2cfa3d2 | ||
|
8d595c1fc2 | ||
|
ef27055434 | ||
|
3f65b0e985 | ||
|
70497318dd | ||
|
0eb8d4226e | ||
|
627bf18f8c | ||
|
afa3883089 | ||
|
b478eca315 | ||
|
61726f4994 | ||
|
14952ba5e5 | ||
|
fc5304c6fe | ||
|
8d0693ed6a | ||
|
d7c5264ad0 | ||
|
331cbf3696 | ||
|
6f1a4494eb | ||
|
cf5ca27a06 | ||
|
c9e9dc2ef2 | ||
|
a25912c32c | ||
|
540f6f3d7a | ||
|
018f978a22 | ||
|
6a28b5a9fa | ||
|
e41a9483bd | ||
|
aced9d2697 | ||
|
b756d61c45 | ||
|
e6ff1539b4 | ||
|
72f541140f | ||
|
acad161344 | ||
|
b8c1bd2cba | ||
|
2014f388b1 | ||
|
cbdb413613 | ||
|
f4369b29ae | ||
|
7113e21a43 | ||
|
908aa19a36 | ||
|
09e20f6e01 | ||
|
1bc92482e9 | ||
|
cc209afc51 | ||
|
8e3948e495 | ||
|
c37b5af2ca | ||
|
e542dd3923 | ||
|
549be9bb3d | ||
|
27b245ac35 | ||
|
488780d2ce | ||
|
6f3b8f64d1 | ||
|
784df0c218 | ||
|
fb7525e0b9 | ||
|
76889b9c58 | ||
|
e2d3bef739 | ||
|
a7cd05bd4e | ||
|
0157039e87 | ||
|
544e1dee65 | ||
|
6e0ec9b924 | ||
|
12704fa640 | ||
|
8a81f85734 | ||
|
c27663c456 | ||
|
fb41a4ffaa | ||
|
16eb1bfbd0 | ||
|
b334582eff | ||
|
7047d68165 | ||
|
dee7fd3eab | ||
|
cf374ec4ef | ||
|
59f02f7766 | ||
|
cef2eb58a7 | ||
|
fad8b702aa | ||
|
cfa31beaf7 | ||
|
f444390617 | ||
|
06a561743a | ||
|
7674e01585 | ||
|
bf92ef6cd3 | ||
|
d23178acb9 | ||
|
98ecac0ffa | ||
|
936006173c | ||
|
d5608cb4f3 | ||
|
c7882b7225 | ||
|
6d9ca25915 | ||
|
252d015b71 | ||
|
1d2e2f71c2 | ||
|
51753a1d39 | ||
|
5021b9a5dd | ||
|
29616d02a8 | ||
|
ebcb13c8eb | ||
|
e6b526230a | ||
|
9c3e910dc4 | ||
|
59652ecaf2 | ||
|
6a677a172b | ||
|
94983ca3ed | ||
|
a363e0a5d8 | ||
|
cd1fbf60ec | ||
|
a9c1768107 | ||
|
1901abd05f | ||
|
195b745efc | ||
|
1a073ca454 | ||
|
bfe01c4322 | ||
|
e9494af098 | ||
|
eb63cdb9ad | ||
|
72aa10b536 | ||
|
39e717ed94 | ||
|
c53c6cb6b6 | ||
|
594e65bb2b | ||
|
4332b0df44 | ||
|
3e654bea0e | ||
|
2a4db01709 | ||
|
7223b5b274 | ||
|
7ff890e513 | ||
|
23a0beab43 | ||
|
77f4513862 | ||
|
677269606c | ||
|
5786e75374 | ||
|
91b17c6925 | ||
|
607b7d1593 | ||
|
83fab06508 | ||
|
4652541b61 | ||
|
65548ddccb | ||
|
b99d70bfe7 | ||
|
2713fd50c8 | ||
|
14b46c3ee7 | ||
|
a8ebc5fafc | ||
|
c22b384680 | ||
|
db0301310b | ||
|
7a84cfdfa2 | ||
|
c55f7645a4 | ||
|
0460702710 | ||
|
290f0a123e | ||
|
275d6a858c | ||
|
b4ad2de2e5 | ||
|
ecaf75e5ec | ||
|
a968260b18 | ||
|
0385e3a8d6 | ||
|
e94e06246b | ||
|
5787687997 | ||
|
61997912fd | ||
|
5eedce91f9 | ||
|
701742f550 | ||
|
2549ce89b0 | ||
|
74c496fe3e | ||
|
e074104004 | ||
|
867d0ef191 | ||
|
8d98c52803 | ||
|
343a6b4e6b | ||
|
d115f38361 | ||
|
1d458e8ab3 | ||
|
46be514b4d | ||
|
a9b66e3ea5 | ||
|
281cb65046 | ||
|
564113669e | ||
|
0baa2dd03e | ||
|
6ba90ec43c | ||
|
135c8567a5 | ||
|
ac09011690 | ||
|
7df24407dc | ||
|
b51ce43d36 | ||
|
c4b1f6171d | ||
|
b17ca3543f | ||
|
48be5af55f | ||
|
323d31ba05 | ||
|
eaddfa7fd1 | ||
|
678bc7b4d4 | ||
|
815c534da8 | ||
|
0af8ee341c | ||
|
1153e6120d | ||
|
290f53f4a6 | ||
|
817d344521 | ||
|
9548f43998 | ||
|
7eb736227e | ||
|
1e75283250 | ||
|
24aefa109c | ||
|
e6a9829dd2 | ||
|
86fff5839a | ||
|
8339ebf3dc | ||
|
d3542202b5 | ||
|
e9b4a2a021 | ||
|
09d87965fb | ||
|
aa24a0f779 | ||
|
89eea3636f | ||
|
07263370d9 | ||
|
cc67bfd8db | ||
|
bc5f64bffe | ||
|
4cb2d0ca93 | ||
|
c9e4b332bf | ||
|
aaf64732b0 | ||
|
15a1873d97 | ||
|
fd246f7e5a | ||
|
ab4d86dde7 | ||
|
198dc2c6b4 | ||
|
df7b399e04 | ||
|
134c75ae01 | ||
|
9e0466d1e6 | ||
|
199ae3a4d8 | ||
|
4ba41540fd | ||
|
5cdfd0ec50 | ||
|
24a9ac2908 | ||
|
2c224d0f18 | ||
|
3cf21e2d37 | ||
|
60ab03afb1 | ||
|
c393e60891 | ||
|
7fd6a37e67 | ||
|
dc00a92499 | ||
|
5d3ee60ca4 | ||
|
bbede8bbeb | ||
|
e1a2f248af | ||
|
a88c2d48c0 | ||
|
d1a456f3e3 | ||
|
ddafa65849 | ||
|
17b1fcc3ea | ||
|
34f2a63190 | ||
|
0298f0181e | ||
|
894b5892a9 | ||
|
20eebe638b | ||
|
ad063d00cc | ||
|
beb216c300 | ||
|
7f45e210af | ||
|
f1c947f0d6 | ||
|
9ae997cab8 | ||
|
4a9753bebc | ||
|
5eb2d9af83 | ||
|
689ded1607 | ||
|
a0d0ed34ae | ||
|
c20d8ac69e | ||
|
d2cfac222e | ||
|
b00c561f81 | ||
|
ed740b4868 | ||
|
43b466704a | ||
|
3bde4dbedb | ||
|
e6f8b7d9fa | ||
|
a2cb009f4c | ||
|
df992d2566 | ||
|
ad60bc002c | ||
|
49a3f6f281 | ||
|
ac687d6bbd | ||
|
59978e157c | ||
|
3626e4b3a0 | ||
|
c2fbdbde83 | ||
|
86b1865eec | ||
|
726393f8da | ||
|
349dd8291d | ||
|
c39008983e | ||
|
a27cbfbf56 | ||
|
7d63b06d84 | ||
|
d06013fbaf | ||
|
a5e40672c1 | ||
|
a9b957e8a2 | ||
|
0ca4a33bfb | ||
|
c0b3a3ff0c | ||
|
335058b78b | ||
|
c4b1df1bf3 | ||
|
c3f0503a91 | ||
|
8ccb2005b3 | ||
|
356199978e | ||
|
92a6e956fd | ||
|
300326fba3 | ||
|
251f2479c2 | ||
|
6f9f871928 | ||
|
c7a14092a8 | ||
|
6217e33a87 | ||
|
c430848ade | ||
|
bac249c8dd | ||
|
32da65f910 | ||
|
93dad9b737 | ||
|
d58d822215 | ||
|
f37098a54f | ||
|
1bb38e25f2 | ||
|
f16690ae1f | ||
|
91ec4839ac | ||
|
28733e052f | ||
|
4fdb0d92fe | ||
|
f88b8c703e | ||
|
17791a703e | ||
|
7dd9545ea3 | ||
|
1d572c61d0 | ||
|
0911669b07 | ||
|
1274b0ef39 | ||
|
f0798216d5 | ||
|
4a1a59f0c8 | ||
|
01bad12708 | ||
|
58c6f9bfb2 | ||
|
fab0a45955 | ||
|
ba9ba8ffe2 | ||
|
f30df7a535 | ||
|
ae83efe4a6 | ||
|
3978c04782 | ||
|
1e7647e385 | ||
|
d1fc90f981 | ||
|
336daea875 | ||
|
a3e11415ec | ||
|
2eef37174e | ||
|
19c2ca520c | ||
|
82870b27ed | ||
|
c416948f8b | ||
|
28ebf927fb | ||
|
d2c5a939ed | ||
|
edc6ce4ff2 | ||
|
7c0eae8059 | ||
|
ae84ff2f0c | ||
|
000f59d614 | ||
|
bf5b2f73f5 | ||
|
31fd425c9a | ||
|
8850a1fbe3 | ||
|
ad36a4ba89 | ||
|
56f8fff935 | ||
|
1e335d527b | ||
|
fccce229c6 | ||
|
0569a1e769 | ||
|
39fdf4a333 | ||
|
6140861143 | ||
|
43521891f0 | ||
|
ba98fe4f86 | ||
|
a6c5430cdd | ||
|
a47430c2f7 | ||
|
40005cec1b | ||
|
77c0fb0b2a | ||
|
3ff40a9733 | ||
|
eacc7ed1e6 | ||
|
62a88a4db8 | ||
|
20f6a4704c | ||
|
e71acdef29 | ||
|
0619685e55 | ||
|
ba143a2730 | ||
|
0b239243d9 | ||
|
3acaec7bcd | ||
|
a83365ee95 | ||
|
7b1efe15cd | ||
|
41ae4af1b8 | ||
|
ad5bcb7d43 | ||
|
a9a3ef0f67 | ||
|
f64018d0a2 | ||
|
ca7b397828 | ||
|
494a04ffb1 | ||
|
d85854b686 | ||
|
943d0391d4 | ||
|
c5743067ad | ||
|
635210d278 | ||
|
0dede0e1de | ||
|
aa6955a0d6 | ||
|
12fd5c46ef | ||
|
3b3aa5b584 | ||
|
14428da108 | ||
|
571eb2f7f9 | ||
|
5f63c397fa | ||
|
ffb49c7217 | ||
|
3e9fd0185a | ||
|
da298cfe59 | ||
|
38c936b8ef | ||
|
a6b729df43 | ||
|
d122d224bb | ||
|
64420f79e5 | ||
|
893751a1d2 | ||
|
5f1e30288a | ||
|
921d567dcb | ||
|
da6076028f | ||
|
3bea5b25cd | ||
217f6603c0 | |||
|
20ec9ff2c6 | ||
|
a85bba0010 | ||
|
6af02b2cc5 | ||
|
000bec2c7b | ||
|
7f1de73784 | ||
|
07a5ccf4d7 | ||
|
4d38ba906f | ||
|
616241be11 | ||
|
c296d6f446 | ||
|
41afd552e9 | ||
|
95db6db935 | ||
|
4ab8fe13de | ||
|
708d7c5b98 | ||
|
6acb80a83a | ||
|
9dce42ac7f | ||
|
a3e136b550 | ||
|
0bb3ae37f0 | ||
|
5dd5685885 | ||
|
c59eb75a59 | ||
|
62d0eebe5c | ||
|
c3e2d2cfba | ||
|
fb97f9d18f | ||
|
abdb8bfb65 | ||
|
501034fe0e | ||
|
fdb6b0e30d | ||
|
f815a7cd26 | ||
|
95bf0630f0 | ||
|
c116f735dc | ||
|
a319446d41 | ||
|
d875061407 | ||
|
73e2389eee | ||
|
58d213f291 | ||
|
42fb9539f8 | ||
|
cfccf5e90d | ||
|
7bb67ee660 | ||
|
703d95fcf8 | ||
|
c3bdec1ce9 | ||
|
bcf99db3df | ||
|
f49158a44b | ||
|
be91c0741f | ||
|
c40372fc0d | ||
|
f46cbb38a9 | ||
|
d0bad09f13 | ||
|
1867571368 | ||
|
ae491764f2 | ||
|
534013fd0c | ||
|
ade89beb96 | ||
|
15c8cb8ac6 | ||
|
e8bf5cada4 | ||
|
f05f97251c | ||
|
20b4e756fe | ||
|
f510b2ba2d | ||
|
0f9058ffef | ||
|
e10a0b0c4c | ||
|
43dd681239 | ||
|
85f36e9dbc | ||
|
f6b22dad20 | ||
|
042939e44d | ||
|
aa472a0098 | ||
|
b0a314411f | ||
|
50817956c2 | ||
|
88523bbb50 | ||
|
5e4b55a0ff | ||
|
5e79e94e77 | ||
|
a1ac4fd665 | ||
|
b2c278c91b | ||
|
293e820a58 | ||
|
61b0681109 | ||
|
5ffb87059c | ||
|
4cae283cff | ||
|
15f220747f | ||
|
55c1129a65 | ||
|
2262921ff4 | ||
|
ede92235d7 | ||
|
5bd70cfee8 | ||
|
5e151c7311 | ||
|
11e58607c9 | ||
|
aea664a0ec | ||
|
1de74c2337 | ||
|
b9fc7ebe24 | ||
|
312387d844 | ||
|
ac06cb2e4f | ||
|
739648e909 | ||
|
c3e8fb3446 | ||
|
739a2d609d | ||
|
164d341915 | ||
|
904edf5d59 | ||
|
bd765c59ce | ||
|
b7f326372d | ||
|
6358f641e7 | ||
|
72b781b845 | ||
|
41dcd8005b | ||
|
cd9a29718b | ||
|
9d1e8a34b2 | ||
|
196c8e593f | ||
|
0664d6ac7b | ||
|
3e3cb047be | ||
|
242887447c | ||
|
6b592435cd | ||
|
f1c0b7372f | ||
|
e5f154316c | ||
|
b60c902810 | ||
|
6f8f35031f | ||
|
3553b15c9f | ||
|
aa21797f43 | ||
|
0eaf7669f7 | ||
|
a3eb540f05 | ||
|
025cbf7d44 | ||
|
02c6793ca9 | ||
|
0329c9c738 | ||
|
8455e5b5dd | ||
|
e1aeb376ac | ||
|
dc8967d8fc | ||
|
65b5504e68 | ||
|
2cd43f7042 | ||
|
d42c82abf2 | ||
|
2225b0b6d5 | ||
|
b4a259837e | ||
|
8ffcc11f27 | ||
|
ebccb67a72 | ||
|
f0b1761ec3 | ||
|
06cadab7cc | ||
|
aeba964a65 | ||
|
60211a315e | ||
|
23ef1c660a | ||
|
fd6ed5b989 | ||
|
26ec6f7782 | ||
|
84120a341a | ||
|
210de7d781 | ||
|
ecb4615f2a | ||
|
a557d38e4d | ||
|
8228e82f51 | ||
|
fbb7cb99f7 | ||
|
8a1c4fe69e | ||
|
2c1f7e115c | ||
|
724ca8c9a9 | ||
|
c7a519498a | ||
|
fac1d4e0bd | ||
|
c6e54e7e5a | ||
|
8471fbd9b7 | ||
|
fb9ba0a734 | ||
|
baf76d883c | ||
|
bca29cf7fd | ||
|
b9de159e97 | ||
|
34bcc59f72 | ||
|
6a458b853c | ||
|
1ec3d86eb7 | ||
|
8553d5a563 | ||
|
ef7857ac8d | ||
|
c7ff196f58 | ||
|
7410e2023a | ||
|
1221cff561 | ||
|
a7fd629c05 | ||
|
016a57f123 | ||
|
29a849cb92 | ||
|
d65273ce39 | ||
|
e747ecef4d | ||
|
ec473a4437 | ||
|
48f172fc9e | ||
|
d4877177b7 | ||
|
56afdcc94a | ||
|
1f2b2c8834 | ||
|
1bd68a42b2 | ||
|
3c45f00443 | ||
|
d56214f096 | ||
|
f4a33a007c | ||
|
569b9f4e66 | ||
|
53125dbccc | ||
|
9b07059b6e | ||
|
efab290c28 | ||
|
adca670196 | ||
|
0bd4105b1d | ||
|
be38b1e5f4 | ||
|
e956c7b2a2 | ||
|
3c6c424d31 | ||
|
0f405c2e11 | ||
|
e9e31b1c9b | ||
|
a83aae341f | ||
|
cfeb67d71d | ||
|
fb7359e6a3 | ||
|
c1716a35e3 | ||
|
fc96dcaa4d | ||
|
5b271e1ed8 | ||
|
e75c2cd731 | ||
|
1738673c53 | ||
|
4b93351f8f | ||
|
30dbf97a1c | ||
|
5f9476448f | ||
|
0587ba2ad2 | ||
|
beca748634 | ||
|
76828950ee | ||
|
8b44400940 | ||
|
b5e7e528eb | ||
|
f24649c819 | ||
|
212d1a8c91 | ||
|
933538a39d | ||
|
b519411d34 | ||
|
7be331bbb2 | ||
|
09816b61df | ||
|
fdd88aa530 | ||
|
a7c7a42136 | ||
|
da31582911 | ||
|
540f6510de | ||
|
9e1393bc1c | ||
|
6a6cb43b17 | ||
|
ca1fe61f09 | ||
|
0407111416 | ||
|
2b1286585b | ||
|
0cfc1a738e | ||
|
4d6e5a8b08 | ||
|
08174e3b05 | ||
|
b23cb5a9e4 | ||
|
ef605e4cbd | ||
|
e65068d226 | ||
|
f81e44d339 | ||
|
52a5e72b02 | ||
|
043e19dd65 | ||
|
8f066d00e0 | ||
|
fd61d67dab | ||
|
05d0c9f4fe | ||
|
403db3b080 | ||
|
32abc76689 | ||
|
1d2a24c9c0 | ||
|
e6af502055 | ||
|
89edd83609 | ||
|
160dfa49a0 | ||
|
9bb8683048 | ||
|
4b62bd256d | ||
|
2b9b700c96 | ||
|
6a0b9971aa | ||
|
f2ccf073bd | ||
|
9dcf074a79 | ||
|
648e29db2c | ||
|
64dbb069ab | ||
|
5fb77a9739 | ||
|
8881b71079 | ||
|
f4d6b676e9 | ||
|
bbbc30e823 | ||
|
5a5e0e7121 | ||
|
8d90b3fbf1 | ||
|
6f7fb7dec6 | ||
|
a3a13dd9dc | ||
|
024e697cee | ||
|
9636627ef0 | ||
|
a954e32b16 | ||
|
069ddddbc1 | ||
|
efc8fc5353 | ||
|
7453f2ca9a | ||
|
8f69017d5a | ||
|
b54b4ca78a | ||
|
506b83ddc6 | ||
|
c173d78950 | ||
|
07c7f5bc08 | ||
|
7e712d9d4c | ||
|
b1b13ba0e5 | ||
|
4a4ced1e69 | ||
|
b9002d7fd5 | ||
|
b5719fd747 | ||
|
183dad281c | ||
|
999d65c187 | ||
|
7e06065df2 | ||
|
8d13a77bc6 | ||
|
96575d6290 | ||
|
add4302385 | ||
|
4220fa948b | ||
|
bd986834fc | ||
|
15d1623ec6 | ||
|
811e2eaeec | ||
|
cf62fb5605 | ||
|
650abf1c52 | ||
|
6bb9983d58 | ||
|
6308dcfdd4 | ||
|
88cdd03f0f | ||
|
bf64276fa7 | ||
|
eff173ebc2 | ||
|
a95c451f1e | ||
|
2bb033267b | ||
|
2c4a6b0912 | ||
|
e2d506c96a | ||
|
a32a577e36 | ||
|
7eb228d1a5 | ||
|
1848b46195 | ||
|
9baa87e5c9 | ||
|
204cee4a17 | ||
|
b265341848 | ||
|
fc594e249a | ||
|
91b0605bc2 | ||
|
74cf8320bc | ||
|
aaf3ecaf41 | ||
|
ca262d3523 | ||
|
b0c19d6bac | ||
|
8f4b7686c9 | ||
|
3db7087658 | ||
|
9a56e1620b | ||
|
bc9f6d91ac | ||
|
384c441990 | ||
|
5298f4e2aa | ||
|
cb70df7a34 | ||
|
eff9e417e0 |
11
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
### Reporting Bugs and getting help
|
||||
|
||||
The issue tracker on Github is for bug reports only. It is not a general support forum.
|
||||
|
||||
**A bug is everything you can reproduce. ie *if I do this that happens but something else should happen instead*.**
|
||||
|
||||
Please search the issue tracker (including closed issues). Your bug might be a server bug that has already been addressed.
|
||||
|
||||
Please fill in the template when creating new issues and provide information about your device and your server. The [adb logcat](https://wiki.cyanogenmod.org/w/Doc:_debugging_with_logcat) can be omitted if you can reproduce the bug under every condition. (ie *click button X and the application crashes*) But the adb logcat is required if the bug happens only under certain conditions or involves network problem (Server not found, messages not arriving)
|
||||
|
||||
**If you are seeking help or are unable to reproduce the exact problem please join our MUC at conversations@conference.siacs.eu**
|
30
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
#### General information
|
||||
|
||||
* **Version:** eg 1.12.2
|
||||
* **Device:** eg Google Nexus 5
|
||||
* **Android Version:** eg Android 6.0 Stock or Android 5.1 Cyanogenmod
|
||||
* **Server name:** eg conversations.im, jabber.at or self hosted
|
||||
* **Server software:** ejabberd 16.04 or prosody 0.10 (if known)
|
||||
* **Installed server modules:** eg Stream Managment, CSI, MAM
|
||||
* **Conversations source:** eg PlayStore, PlayStore Beta Channel, F-Droid, self build (latest HEAD)
|
||||
|
||||
|
||||
#### Steps to reproduce
|
||||
|
||||
1. …
|
||||
2. …
|
||||
|
||||
|
||||
#### Expected result
|
||||
|
||||
What is the expected output? What do you see instead?
|
||||
|
||||
|
||||
#### Debug output
|
||||
|
||||
Please post the output of adb logcat. The log should begin with the start of Conversations and include all the
|
||||
steps it takes to reproduce the problem.
|
||||
|
||||
````
|
||||
adb logcat -s conversations
|
||||
````
|
4
.gitignore
vendored
@ -2,9 +2,13 @@
|
||||
*.swp
|
||||
.settings
|
||||
|
||||
src/playstore/res/values/gcm.xml
|
||||
|
||||
# https://github.com/github/gitignore/blob/master/Gradle.gitignore
|
||||
.gradle/
|
||||
build/
|
||||
captures/
|
||||
gradle.properties
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
|
15
.travis.yml
@ -1,11 +1,16 @@
|
||||
language: android
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
android:
|
||||
components:
|
||||
- platform-tools
|
||||
- tools
|
||||
- build-tools-23.0.0
|
||||
- build-tools-22.0.1
|
||||
- build-tools-21.1.2
|
||||
- build-tools-19.1.0
|
||||
- android-23
|
||||
- build-tools-26.0.1
|
||||
- android-25
|
||||
- extra-android-m2repository
|
||||
- extra-google-m2repository
|
||||
- extra-google-google_play_services
|
||||
licenses:
|
||||
- '.+'
|
||||
script:
|
||||
- ./gradlew assembleFreeRelease
|
||||
|
454
CHANGELOG.md
@ -1,53 +1,403 @@
|
||||
###Changelog
|
||||
# Changelog
|
||||
|
||||
####Version 1.6.6
|
||||
### Version 1.20.0
|
||||
* presence subscription no longer required for OMEMO on compatible servers
|
||||
* display emoji-only messages slightly larger
|
||||
|
||||
### Version 1.19.5
|
||||
* fixed connection loop on Android <4.4
|
||||
|
||||
### Version 1.19.4
|
||||
* work around for OpensFire’s self signed certs
|
||||
* use VPN’s DNS servers first
|
||||
|
||||
### Version 1.19.3
|
||||
* Do not create foreground service when all accounts are disabled
|
||||
* bug fixes
|
||||
|
||||
### Version 1.19.2
|
||||
* bug fixes
|
||||
|
||||
### Version 1.19.1
|
||||
* Made DNSSEC hostname validation opt-in
|
||||
|
||||
### Version 1.19.0
|
||||
* Added 'App Shortcuts' to quickly access frequent contacts
|
||||
* Use DNSSEC to verify hostname instead of domain in certificate
|
||||
* Setting to enable Heads-up notifications
|
||||
* Added date separators in message view
|
||||
|
||||
### Version 1.18.5
|
||||
* colorize send button only after history is caught up
|
||||
* improved MAM catchup strategy
|
||||
|
||||
### Version 1.18.4
|
||||
* fixed UI freezes during connection timeout
|
||||
* fixed notification sound playing twice
|
||||
* fixed conversations being marked as read
|
||||
* removed 'copy text' in favor of 'select text' and 'share with'
|
||||
|
||||
### Version 1.18.3
|
||||
* limited GPG encryption for MUC offline members
|
||||
|
||||
### Version 1.18.2
|
||||
* added support for Android Auto
|
||||
* fixed HTTP Download over Tor
|
||||
* work around for nimbuzz.com MUCs
|
||||
|
||||
### Version 1.18.1
|
||||
* bug fixes
|
||||
|
||||
### Version 1.18.0
|
||||
* Conversations <1.16.0 will be unable to receive OMEMO encrypted messages
|
||||
* OMEMO: put auth tag into key (verify auth tag as well)
|
||||
* offer to block entire domain in message from stranger snackbar
|
||||
* treat URL as file if URL is in oob or contains key
|
||||
|
||||
### Version 1.17.1
|
||||
* Switch Aztec to QR for faster scans
|
||||
* Fixed unread counter for image messages
|
||||
|
||||
### Version 1.17.0
|
||||
* Do not notify for messages from strangers by default
|
||||
* Blocking a JID closes the corresponding conversation
|
||||
* Show message sender in conversation overview
|
||||
* Show unread counter for every conversation
|
||||
* Send typing notifications in private, non-anonymous MUCs
|
||||
* Support for the latest MAM namespace
|
||||
* Icons for attach menu
|
||||
|
||||
### Version 1.16.2
|
||||
* change mam catchup strategie. support mam:1
|
||||
* bug fixes
|
||||
|
||||
|
||||
### Version 1.16.1
|
||||
* UI performance fixes
|
||||
* bug fixes
|
||||
|
||||
### Version 1.16.0
|
||||
* configurable client side message retention period
|
||||
* compress videos before sending them
|
||||
|
||||
### Version 1.15.5
|
||||
* show nick as bold text when mentioned in conference
|
||||
* bug fixes
|
||||
|
||||
### Version 1.15.4
|
||||
* bug fixes
|
||||
|
||||
### Version 1.15.3
|
||||
* show offline contacts in MUC as grayed-out
|
||||
* don't transcode gifs. add overlay indication to gifs
|
||||
* bug fixes
|
||||
|
||||
### Version 1.15.2
|
||||
* bug fixes
|
||||
|
||||
### Version 1.15.1
|
||||
* support for POSH (RFC7711)
|
||||
* support for quoting messages (via select text)
|
||||
* verified messages show shield icon. unverified messages show lock
|
||||
|
||||
### Version 1.15.0
|
||||
* New [Blind Trust Before Verification](https://gultsch.de/trust.html) mode
|
||||
* Easily share Barcode and XMPP uri from Account details
|
||||
* Automatically deactivate own devices after 7 day of inactivity
|
||||
* Improvements fo doze/push mode
|
||||
* bug fixes
|
||||
|
||||
### Version 1.14.9
|
||||
* warn in account details when data saver is enabled
|
||||
* automatically enable foreground service after detecting frequent restarts
|
||||
* bug fixes
|
||||
|
||||
### Version 1.14.8
|
||||
* bug fixes
|
||||
|
||||
### Version 1.14.7
|
||||
* error message accessible via context menu for failed messages
|
||||
* don't include pgp signature in anonymous mucs
|
||||
* bug fixes
|
||||
|
||||
### Version 1.14.6
|
||||
* make error notification dismissable
|
||||
* bug fixes
|
||||
|
||||
|
||||
### Version 1.14.5
|
||||
* expert setting to delete OMEMO identities
|
||||
* bug fixes
|
||||
|
||||
### Version 1.14.4
|
||||
* bug fixes
|
||||
|
||||
### Version 1.14.3
|
||||
* XEP-0377: Spam Reporting
|
||||
* fix rare start up crashes
|
||||
|
||||
### Version 1.14.2
|
||||
* support ANONYMOUS SASL
|
||||
* bug fixes
|
||||
|
||||
### Version 1.14.1
|
||||
* Press lock icon to see why OMEMO is deactivated
|
||||
* bug fixes
|
||||
|
||||
### Version 1.14.0
|
||||
* Improvments for N
|
||||
* Quick Reply to Notifications on N
|
||||
* Don't download avatars and files when data saver is on
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.9
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.8
|
||||
* show identities instead of resources in selection dialog
|
||||
* allow TLS direct connect when port is set to 5223
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.7
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.6
|
||||
* thumbnails for videos
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.5
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.4
|
||||
* support jingle ft:4
|
||||
* show contact as DND if one resource is
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.3
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.2
|
||||
* new PGP decryption logic
|
||||
* bug fixes
|
||||
|
||||
### Version 1.13.1
|
||||
* changed some colors in dark theme
|
||||
* fixed fall-back message for OMEMO
|
||||
|
||||
### Version 1.13.0
|
||||
* configurable dark theme
|
||||
* opt-in to share Last User Interaction
|
||||
|
||||
### Version 1.12.9
|
||||
* make grace period configurable
|
||||
|
||||
### Version 1.12.8
|
||||
* more bug fixes :-(
|
||||
|
||||
### Version 1.12.7
|
||||
* bug fixes
|
||||
|
||||
### Version 1.12.6
|
||||
* bug fixes
|
||||
|
||||
### Version 1.12.5
|
||||
* new create conference dialog
|
||||
* show first unread message on top
|
||||
* show geo uri as links
|
||||
* circumvent long message DOS
|
||||
|
||||
### Version 1.12.4
|
||||
* show offline members in conference (needs server support)
|
||||
* various bug fixes
|
||||
|
||||
### Version 1.12.3
|
||||
* make omemo default when all resources support it
|
||||
* show presence of other resources as template
|
||||
* start typing in StartConversationsActivity to search
|
||||
* various bug fixes and improvements
|
||||
|
||||
### Version 1.12.2
|
||||
* fixed pgp presence signing
|
||||
|
||||
### Version 1.12.1
|
||||
* small bug fixes
|
||||
|
||||
### Version 1.12.0
|
||||
* new welcome screen that makes it easier to register account
|
||||
* expert setting to modify presence
|
||||
|
||||
### Version 1.11.7
|
||||
* Share xmpp uri from conference details
|
||||
* add setting to allow quick sharing
|
||||
* various bug fixes
|
||||
|
||||
### Version 1.11.6
|
||||
* added preference to disable notification light
|
||||
* various bug fixes
|
||||
|
||||
### Version 1.11.5
|
||||
* check file ownership to not accidentally share private files
|
||||
|
||||
### Version 1.11.4
|
||||
* fixed a bug where contacts are shown as offline
|
||||
* improved broken PEP detection
|
||||
|
||||
### Version 1.11.3
|
||||
* check maximum file size when using HTTP Upload
|
||||
* properly calculate caps hash
|
||||
|
||||
### Version 1.11.2
|
||||
* only add image files to media scanner
|
||||
* allow to delete files
|
||||
* various bug fixes
|
||||
|
||||
### Version 1.11.1
|
||||
* fixed some bugs when sharing files with Conversations
|
||||
|
||||
### Version 1.11.0
|
||||
* OMEMO encrypted conferences
|
||||
|
||||
### Version 1.10.1
|
||||
* made message correction opt-in
|
||||
* various bug fixes
|
||||
|
||||
### Version 1.10.0
|
||||
* Support for XEP-0357: Push Notifications
|
||||
* Support for XEP-0308: Last Message Correction
|
||||
* introduced build flavors to make dependence on play-services optional
|
||||
|
||||
### Version 1.9.4
|
||||
* prevent cleared Conversations from reloading history with MAM
|
||||
* various MAM fixes
|
||||
|
||||
### Version 1.9.3
|
||||
* expert setting that enables host and port configuration
|
||||
* expert setting opt-out of bookmark autojoin handling
|
||||
* offer to rejoin a conference after server sent unavailable
|
||||
* internal rewrites
|
||||
|
||||
### Version 1.9.2
|
||||
* prevent startup crash on Sailfish OS
|
||||
* minor bug fixes
|
||||
|
||||
### Version 1.9.1
|
||||
* minor bug fixes incl. a workaround for nimbuzz.com
|
||||
|
||||
### Version 1.9.0
|
||||
* Per conference notification settings
|
||||
* Let user decide whether to compress pictures
|
||||
* Support for XEP-0368
|
||||
* Ask user to exclude Conversations from battery optimizations
|
||||
|
||||
### Version 1.8.4
|
||||
* prompt to trust own OMEMO devices
|
||||
* fixed rotation issues in avatar publication
|
||||
* invite non-contact JIDs to conferences
|
||||
|
||||
### Version 1.8.3
|
||||
* brought text selection back
|
||||
|
||||
### Version 1.8.2
|
||||
* fixed stuck at 'connecting...' bug
|
||||
* make message box behave correctly with multiple links
|
||||
|
||||
### Version 1.8.1
|
||||
* enabled direct share on Android 6.0
|
||||
* ask for permissions on Android 6.0
|
||||
* notify on MAM catchup messages
|
||||
* bug fixes
|
||||
|
||||
### Version 1.8.0
|
||||
* TOR/ORBOT support in advanced settings
|
||||
* show vcard avatars of participants in a conference
|
||||
|
||||
### Version 1.7.3
|
||||
* fixed PGP encrypted file transfer
|
||||
* fixed repeating messages in slack conferences
|
||||
|
||||
### Version 1.7.2
|
||||
* decode PGP messages in background
|
||||
|
||||
####Versrion 1.7.1
|
||||
* performance improvements when opening a conversation
|
||||
|
||||
### Version 1.7.0
|
||||
* CAPTCHA support
|
||||
* SASL EXTERNAL (client certifiates)
|
||||
* fetching MUC history via MAM
|
||||
* redownload deleted files from HTTP hosts
|
||||
* Expert setting to automatically set presence
|
||||
* bug fixes
|
||||
|
||||
### Version 1.6.11
|
||||
* tab completion for MUC nicks
|
||||
* history export
|
||||
* bug fixes
|
||||
|
||||
### Version 1.6.10
|
||||
* fixed facebook login
|
||||
* fixed bug with ejabberd mam
|
||||
* use official HTTP File Upload namespace
|
||||
|
||||
### Version 1.6.9
|
||||
* basic keyboard support
|
||||
|
||||
### Version 1.6.8
|
||||
* reworked 'enter is send' setting
|
||||
* reworked DNS server discovery on lolipop devices
|
||||
* various bug fixes
|
||||
|
||||
### Version 1.6.7
|
||||
* bug fixes
|
||||
|
||||
### Version 1.6.6
|
||||
* best 1.6 release yet
|
||||
|
||||
####Version 1.6.5
|
||||
### Version 1.6.5
|
||||
* more OMEMO fixes
|
||||
|
||||
####Version 1.6.4
|
||||
### Version 1.6.4
|
||||
* setting to enable white chat bubbles
|
||||
* limit OMEMO key publish attempts to work around broken PEP
|
||||
* various bug fixes
|
||||
|
||||
####Version 1.6.3
|
||||
### Version 1.6.3
|
||||
* bug fixes
|
||||
|
||||
####Version 1.6.2
|
||||
### Version 1.6.2
|
||||
* fixed issues with connection time out when server does not support ping
|
||||
|
||||
####Version 1.6.1
|
||||
### Version 1.6.1
|
||||
* fixed crashes
|
||||
|
||||
####Version 1.6.0
|
||||
### Version 1.6.0
|
||||
* new multi-end-to-multi-end encryption method
|
||||
* redesigned chat bubbles
|
||||
* show unexpected encryption changes as red chat bubbles
|
||||
* always notify in private/non-anonymous conferences
|
||||
|
||||
####Version 1.5.1
|
||||
### Version 1.5.1
|
||||
* fixed rare crashes
|
||||
* improved otr support
|
||||
|
||||
####Version 1.5.0
|
||||
### Version 1.5.0
|
||||
* upload files to HTTP host and share them in MUCs. requires new [HttpUploadComponent](https://github.com/siacs/HttpUploadComponent) on server side
|
||||
|
||||
####Version 1.4.5
|
||||
### Version 1.4.5
|
||||
* fixes to message parser to not display some ejabberd muc status messages
|
||||
|
||||
####Version 1.4.4
|
||||
### Version 1.4.4
|
||||
* added unread count badges on supported devices
|
||||
* rewrote message parser
|
||||
|
||||
####Version 1.4.0
|
||||
### Version 1.4.0
|
||||
* send button turns into quick action button to offer faster access to take photo, send location or record audio
|
||||
* visually seperate merged messages
|
||||
* visually separate merged messages
|
||||
* faster reconnects of failed accounts after network switches
|
||||
* r/o vcard avatars for contacts
|
||||
* various bug fixes
|
||||
|
||||
####Version 1.3.0
|
||||
### Version 1.3.0
|
||||
* swipe conversations to end them
|
||||
* quickly enable / disable account via slider
|
||||
* share multiple images at once
|
||||
@ -55,32 +405,32 @@
|
||||
* mlink compatibility
|
||||
* bug fixes
|
||||
|
||||
####Version 1.2.0
|
||||
### Version 1.2.0
|
||||
* Send current location. (requires [plugin](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation))
|
||||
* Invite multiple contacts at once
|
||||
* performance improvements
|
||||
* bug fixes
|
||||
|
||||
####Version 1.1.0
|
||||
### Version 1.1.0
|
||||
* Typing notifications (must be turned on in settings)
|
||||
* Various UI performance improvements
|
||||
* bug fixes
|
||||
|
||||
####Version 1.0.4
|
||||
### Version 1.0.4
|
||||
* load avatars asynchronously on start up
|
||||
* support for XEP-0092: Software Version
|
||||
|
||||
####Version 1.0.3
|
||||
### Version 1.0.3
|
||||
* load messages asynchronously on start up
|
||||
* bug fixes
|
||||
|
||||
####Version 1.0.2
|
||||
### Version 1.0.2
|
||||
* skipped
|
||||
|
||||
####Version 1.0.1
|
||||
### Version 1.0.1
|
||||
* accept more ciphers
|
||||
|
||||
####Version 1.0
|
||||
### Version 1.0
|
||||
* MUC controls (Affiliaton changes)
|
||||
* Added download button to notification
|
||||
* Added check box to hide offline contacts
|
||||
@ -88,7 +438,7 @@
|
||||
* Improved security
|
||||
* bug fixes + code clean up
|
||||
|
||||
####Version 0.10
|
||||
### Version 0.10
|
||||
* Support for Message Archive Management
|
||||
* Dynamically load message history
|
||||
* Ability to block contacts
|
||||
@ -98,16 +448,16 @@
|
||||
* quiet hours
|
||||
* fixed connection issues on ipv6 servers
|
||||
|
||||
####Version 0.9.3
|
||||
### Version 0.9.3
|
||||
* bug fixes
|
||||
|
||||
####Version 0.9.2
|
||||
### Version 0.9.2
|
||||
* more bug fixes
|
||||
|
||||
####Version 0.9.1
|
||||
### Version 0.9.1
|
||||
* bug fixes including some that caused Conversations to crash on start
|
||||
|
||||
####Version 0.9
|
||||
### Version 0.9
|
||||
* arbitrary file transfer
|
||||
* more options to verify OTR (SMP, QR Codes, NFC)
|
||||
* ability to create instant conferences
|
||||
@ -116,44 +466,44 @@
|
||||
* added SCRAM-SHA1 login method
|
||||
* bug fixes
|
||||
|
||||
####Version 0.8.4
|
||||
### Version 0.8.4
|
||||
* bug fixes
|
||||
|
||||
####Version 0.8.3
|
||||
### Version 0.8.3
|
||||
* increased UI performance
|
||||
* fixed rotation bugs
|
||||
|
||||
####Version 0.8.2
|
||||
### Version 0.8.2
|
||||
* Share contacts via QR codes or NFC
|
||||
* Slightly improved UI
|
||||
* minor bug fixes
|
||||
|
||||
####Version 0.8.1
|
||||
### Version 0.8.1
|
||||
* minor bug fixes
|
||||
|
||||
####Version 0.8
|
||||
### Version 0.8
|
||||
* Download HTTP images
|
||||
* Show avatars in MUC tiles
|
||||
* Disabled SSLv3
|
||||
* Performance improvments
|
||||
* Performance improvements
|
||||
* bug fixes
|
||||
|
||||
####Version 0.7.3
|
||||
### Version 0.7.3
|
||||
* revised tablet ui
|
||||
* internal rewrites
|
||||
* bug fixes
|
||||
|
||||
####Version 0.7.2
|
||||
### Version 0.7.2
|
||||
* show full timestamp in messages
|
||||
* brought back option to use JID to identify conferences
|
||||
* optionally request delivery receipts (expert option)
|
||||
* more languages
|
||||
* bug fixes
|
||||
|
||||
####Version 0.7.1
|
||||
### Version 0.7.1
|
||||
* Optionally use send button as status indicator
|
||||
|
||||
####Version 0.7
|
||||
### Version 0.7
|
||||
* Ability to disable notifications for single conversations
|
||||
* Merge messages in chat bubbles
|
||||
* Fixes for OpenPGP and OTR (please republish your public key)
|
||||
@ -162,35 +512,35 @@
|
||||
* Configurable font size
|
||||
* Expert options for encryption
|
||||
|
||||
####Version 0.6
|
||||
### Version 0.6
|
||||
* Support for server side avatars
|
||||
* save images in gallery
|
||||
* show contact name and picture in non-anonymous conferences
|
||||
* reworked account creation
|
||||
* various bug fixes
|
||||
|
||||
####Version 0.5.2
|
||||
### Version 0.5.2
|
||||
* minor bug fixes
|
||||
|
||||
####Version 0.5.1
|
||||
### Version 0.5.1
|
||||
* couple of small bug fixes that have been missed in 0.5
|
||||
* complete translations for Swedish, Dutch, German, Spanish, French, Russian
|
||||
|
||||
####Version 0.5
|
||||
### Version 0.5
|
||||
* UI overhaul
|
||||
* MUC / Conference bookmarks
|
||||
* A lot of bug fixes
|
||||
|
||||
####Version 0.4
|
||||
### Version 0.4
|
||||
* OTR file encryption
|
||||
* keep OTR messages and files on device until both parties or online at the same time
|
||||
* XEP-0333. Mark wether the other party has read your messages
|
||||
* XEP-0333. Mark whether the other party has read your messages
|
||||
* Delayed messages are now tagged properly
|
||||
* Share images from the Gallery
|
||||
* Infinit history scrolling
|
||||
* Mark the last used presence in presence selection dialog
|
||||
|
||||
####Version 0.3
|
||||
### Version 0.3
|
||||
* Mostly bug fixes and internal rewrites
|
||||
* Touch contact picture in conference to highlight
|
||||
* Long press on received image to share
|
||||
@ -198,27 +548,27 @@
|
||||
* improved issues with occasional message lost
|
||||
* experimental conference encryption. (see FAQ)
|
||||
|
||||
####Version 0.2.3
|
||||
### Version 0.2.3
|
||||
* regression fix with receiving encrypted images
|
||||
|
||||
####Version 0.2.2
|
||||
### Version 0.2.2
|
||||
* Ability to take photos directly
|
||||
* Improved openPGP offline handling
|
||||
* Various bug fixes
|
||||
* Updated Translations
|
||||
|
||||
####Version 0.2.1
|
||||
### Version 0.2.1
|
||||
* Various bug fixes
|
||||
* Updated Translations
|
||||
|
||||
####Version 0.2
|
||||
### Version 0.2
|
||||
* Image file transfer
|
||||
* Better integration with OpenKeychain (PGP encryption)
|
||||
* Nicer conversation tiles for conferences
|
||||
* Ability to clear conversation history
|
||||
* A lot of bug fixes and code clean up
|
||||
|
||||
####Version 0.1.3
|
||||
### Version 0.1.3
|
||||
* Switched to minidns library to resolve SRV records
|
||||
* Faster DNS in some cases
|
||||
* Enabled stream compression
|
||||
@ -226,12 +576,12 @@
|
||||
* Various bug fixes involving message notifications
|
||||
* Added support for DIGEST-MD5 auth
|
||||
|
||||
####Version 0.1.2
|
||||
### Version 0.1.2
|
||||
* Various bug fixes relating to conferences
|
||||
* Further DNS lookup improvements
|
||||
|
||||
####Version 0.1.1
|
||||
### Version 0.1.1
|
||||
* Fixed the 'server not found' bug
|
||||
|
||||
####Version 0.1
|
||||
### Version 0.1
|
||||
* Initial release
|
||||
|
181
README.md
@ -1,8 +1,31 @@
|
||||
# Conversations
|
||||
<h1 align="center">Conversations</h1>
|
||||
|
||||
Conversations: the very last word in instant messaging
|
||||
<p align="center">Conversations: the very last word in instant messaging</p>
|
||||
|
||||
[![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_60.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations) [![Amazon App Store](https://images-na.ssl-images-amazon.com/images/G/01/AmazonMobileApps/amazon-apps-store-us-black.png)](http://www.amazon.com/dp/B00WD35AAC/)
|
||||
<p align="center">
|
||||
<a href="https://conversations.im/j/conversations@conference.siacs.eu">
|
||||
<img src="https://camo.githubusercontent.com/a839cc0a3d4dac7ec82237381b165dd144365b6d/68747470733a2f2f74696e7975726c2e636f6d2f6a6f696e7468656d7563"
|
||||
alt="chat on our conference room">
|
||||
</a>
|
||||
<a href="https://travis-ci.org/siacs/Conversations">
|
||||
<img src="https://travis-ci.org/siacs/Conversations.svg?branch=development"
|
||||
alt="build status">
|
||||
</a>
|
||||
<a href="https://bountysource.com/teams/siacs">
|
||||
<img src="https://api.bountysource.com/badge/tracker?tracker_id=519483" alt="Bountysource">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://play.google.com/store/apps/details?id=eu.siacs.conversations&referrer=utm_source%3Dgithub">
|
||||
<img src="https://conversations.im/images/en-play-badge.png"
|
||||
alt="Google Play">
|
||||
</a>
|
||||
<a href="http://www.amazon.com/dp/B00WD35AAC/">
|
||||
<img src="https://images-na.ssl-images-amazon.com/images/G/01/AmazonMobileApps/amazon-apps-store-us-black.png"
|
||||
alt="Amazon App Store">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
![screenshots](https://raw.githubusercontent.com/siacs/Conversations/master/screenshots.png)
|
||||
|
||||
@ -16,9 +39,9 @@ Conversations: the very last word in instant messaging
|
||||
|
||||
## Features
|
||||
|
||||
* End-to-end encryption with either [OTR](https://otr.cypherpunks.ca/) or [OpenPGP](http://www.openpgp.org/about_openpgp/)
|
||||
* End-to-end encryption with [OMEMO](http://conversations.im/omemo/), [OTR](https://otr.cypherpunks.ca/), or [OpenPGP](http://openpgp.org/about/)
|
||||
* Send and receive images as well as other kind of files
|
||||
* Share your location via an external [plug-in](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation)
|
||||
* Share your location via an external [plug-in](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation&referrer=utm_source%3Dgithub)
|
||||
* Indication when your contact has read your message
|
||||
* Intuitive UI that follows Android Design guidelines
|
||||
* Pictures / Avatars for your Contacts
|
||||
@ -41,7 +64,7 @@ run your own XMPP server for you and your friends. These XEP's are:
|
||||
|
||||
* [XEP-0065: SOCKS5 Bytestreams](http://xmpp.org/extensions/xep-0065.html) (or mod_proxy65). Will be used to transfer
|
||||
files if both parties are behind a firewall (NAT).
|
||||
* [XEP-0163: Personal Eventing Protocol](http://xmpp.org/extensions/xep-0163.html) for avatars
|
||||
* [XEP-0163: Personal Eventing Protocol](http://xmpp.org/extensions/xep-0163.html) for avatars and OMEMO.
|
||||
* [XEP-0191: Blocking command](http://xmpp.org/extensions/xep-0191.html) lets you blacklist spammers or block contacts
|
||||
without removing them from your roster.
|
||||
* [XEP-0198: Stream Management](http://xmpp.org/extensions/xep-0198.html) allows XMPP to survive small network outages and
|
||||
@ -56,9 +79,8 @@ run your own XMPP server for you and your friends. These XEP's are:
|
||||
* [XEP-0352: Client State Indication](http://xmpp.org/extensions/xep-0352.html) lets the server know whether or not
|
||||
Conversations is in the background. Allows the server to save bandwidth by
|
||||
withholding unimportant packages.
|
||||
* [XEP-xxxx: HTTP File Upload](http://xmpp.org/extensions/inbox/http-upload.html) allows you to share files in conferences and with offline
|
||||
contacts. Requires an [additional component](https://github.com/siacs/HttpUploadComponent)
|
||||
on your server.
|
||||
* [XEP-0363: HTTP File Upload](http://xmpp.org/extensions/xep-0363.html) allows you to share files in conferences
|
||||
and with offline contacts.
|
||||
|
||||
## Team
|
||||
|
||||
@ -93,12 +115,12 @@ Translations are managed on [Transifex](https://www.transifex.com/projects/p/con
|
||||
#### How do I install Conversations?
|
||||
|
||||
Conversations is entirely open source and licensed under GPLv3. So if you are a
|
||||
software developer you can check out the sources from GitHub and use ant to
|
||||
software developer you can check out the sources from GitHub and use Gradle to
|
||||
build your apk file.
|
||||
|
||||
The more convenient way — which not only gives you automatic updates but also
|
||||
supports the further development of Conversations — is to buy the App in the
|
||||
Google [Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conversations).
|
||||
Google [Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conversations&referrer=utm_source%3Dgithub).
|
||||
|
||||
Buying the App from the Play Store will also give you access to our [beta test](#beta).
|
||||
|
||||
@ -119,20 +141,48 @@ My Bitcoin Address is: `1NxSU1YxYzJVDpX1rcESAA3NJki7kRgeeu`
|
||||
[![Flattr this!](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=inputmice&url=http%3A%2F%2Fconversations.siacs.eu&title=Conversations&tags=github&category=software)
|
||||
|
||||
#### How do I create an account?
|
||||
XMPP, like email, is a federated protocol, which means that there is not one company you can create an *official XMPP account* with. Instead there are hundreds, or even thousands, of providers out there. One of those providers is our very own [conversations.im](https://account.conversations.im). If you don’t like to use *conversations.im* use a web search engine of your choice to find another provider. Or maybe your university has one. Or you can run your own. Or ask a friend to run one. Once you've found one, you can use Conversations to create an account. Just select *register new account* on server within the create account dialog.
|
||||
|
||||
XMPP, like email, is a federated protocol which means that there is not one
|
||||
company you can create an 'official XMPP account' with. Instead there are
|
||||
hundreds, or even thousands, of provider out there. To find one use a web search
|
||||
engine of your choice. Or maybe your university has one. Or you can run your
|
||||
own. Or ask a friend to run one. Once you've found one, you can use
|
||||
Conversations to create an account. Just select 'register new account on server'
|
||||
within the create account dialog.
|
||||
##### Domain hosting
|
||||
Using your own domain not only gives you a more recognizable Jabber ID, it also gives you the flexibility to migrate your account between different XMPP providers. This is a good compromise between the responsibilities of having to operate your own server and the downsides of being dependent on a single provider.
|
||||
|
||||
Learn more about [conversations.im Jabber/XMPP domain hosting](https://account.conversations.im/domain/).
|
||||
|
||||
##### Running your own
|
||||
If you already have a server somewhere and are willing and able to put the necessary work in, one alternative-in the spirit of federation-is to run your own. We recommend either [Prosody](https://prosody.im/) or [ejabberd](https://www.ejabberd.im/). Both of which have their own strengths. Ejabberd is slightly more mature nowadays but Prosody is arguably easier to set up.
|
||||
|
||||
For Prosody you need a couple of so called [community modules](https://modules.prosody.im/) most of which are maintained by the same people that develop Prosody.
|
||||
|
||||
If you pick ejabberd make sure you use the latest version. Linux Distributions might bundle some very old versions of it.
|
||||
|
||||
#### Where can I set up a custom hostname / port
|
||||
Conversations will automatically look up the SRV records for your domain name
|
||||
which can point to any hostname port combination. If your server doesn’t provide
|
||||
those please contact your admin and have them read
|
||||
[this](http://prosody.im/doc/dns#srv_records)
|
||||
[this](http://prosody.im/doc/dns#srv_records). If your server operator is unwilling
|
||||
to fix this you can enable advanced server settings in the expert settings of
|
||||
Conversations.
|
||||
|
||||
#### I get 'Incompatible Server'
|
||||
|
||||
As regular user you should be picking a different server. The server you selected
|
||||
is probably insecure and/or very old.
|
||||
|
||||
If you are a server administrator you should make sure that your server provides
|
||||
STARTTLS. XMPP over TLS (on a different port) is not sufficient.
|
||||
|
||||
On rare occasions this error message might also be caused by a server not providing
|
||||
a login (SASL) mechanism that Conversations is able to handle. Conversations supports
|
||||
SCRAM-SHA1, PLAIN, EXTERNAL (client certs) and DIGEST-MD5.
|
||||
|
||||
#### How do XEP-0357: Push Notifications work?
|
||||
You need to be running the Play Store version of Conversations and your server needs to support push notifications.¹ Because *Google Cloud Notifications (GCM)* are tied with an API key to a specific app your server can not initiate the push message directly. Instead your server will send the push notification to the Conversations App server (operated by us) which then acts as a proxy and initiates the push message for you. The push message sent from our App server through GCM doesn’t contain any personal information. It is just an empty message which will wake up your device and tell Conversations to reconnect to your server. The information send from your server to our App server depends on the configuration of your server but can be limited to your account name. (In any case the Conversations App server won't redirect any information through GCM even if your server sends this information.)
|
||||
|
||||
In summary Google will never get hold of any personal information besides that *something* happened. (Which doesn’t even have to be a message but can be some automated event as well.) We - as the operator of the App server - will just get hold of your account name (without being able to tie this to your specific device).
|
||||
|
||||
If you don’t want this simply pick a server which does not offer Push Notifications or build Conversations yourself without support for push notifications. (This is available via a gradle build flavor.) Non-play store source of Conversations like the Amazon App store will also offer a version without push notifications. Conversations will just work as before and maintain its own TCP connection in the background.
|
||||
|
||||
¹ Your server only needs to support the server side of [XEP-0357: Push Notifications](http://xmpp.org/extensions/xep-0357.html). If you use the Play Store version you do **not** need to run your own app server. The server modules are called *mod_cloud_notify* on Prosody and *mod_push* on ejabberd.
|
||||
|
||||
#### Conversations doesn't work for me. Where can I get help?
|
||||
|
||||
@ -174,6 +224,12 @@ connection again. When the client fails to do so because the network
|
||||
connectivity is out for longer than that all messages sent to that client will
|
||||
be returned to the sender resulting in a delivery failed.
|
||||
|
||||
Instead of returning a message to the sender both ejabberd and prosody have the
|
||||
ability to store messages in offline storage when the disconnecting client is
|
||||
the only client. In prosody this is available via an extra module called
|
||||
```mod_smacks_offline```. In ejabberd this is available via some configuration
|
||||
settings.
|
||||
|
||||
Other less common reasons are that the message you sent didn't meet some
|
||||
criteria enforced by the server (too large, too many). Another reason could be
|
||||
that the recipient is offline and the server doesn't provide offline storage.
|
||||
@ -214,6 +270,11 @@ Making these status and priority optional isn't a solution either because
|
||||
Conversations is trying to get rid of old behaviours and set an example for
|
||||
other clients.
|
||||
|
||||
#### How do I backup / move Conversations to a new device?
|
||||
On the one hand Conversations supports Message Archive Management to keep a server side history of your messages so when migrating to a new device that device can display your entire history. However that does not work if you enable OMEMO due to its forward secrecy. (Read [The State of Mobile XMPP in 2016](https://gultsch.de/xmpp_2016.html) especially the section on encryption.)
|
||||
|
||||
If you migrate to a new device and would still like to keep your history please use a third party backup tool like [oandbackup](https://github.com/jensstein/oandbackup) or ```adb backup``` from your computer. It is important that your deactivate your account before backup and activate it only after a successful restore. Otherwise OMEMO might not work afterwards.
|
||||
|
||||
#### Conversations is missing a certain feature
|
||||
|
||||
I'm open for new feature suggestions. You can use the [issue tracker][issues] on
|
||||
@ -236,11 +297,11 @@ I am available for hire. Contact me via XMPP: `inputmice@siacs.eu`
|
||||
|
||||
### Security
|
||||
|
||||
#### Why are there two end-to-end encryption methods and which one should I choose?
|
||||
#### Why are there three end-to-end encryption methods and which one should I choose?
|
||||
|
||||
In most cases OTR should be the encryption method of choice. It works out of the
|
||||
box with most contacts as long as they are online. However PGP can, in some
|
||||
cases, (message carbons to multiple clients) be more flexible.
|
||||
* OTR is a legacy encryption method. It works out of the box with most contacts as long as they are online.
|
||||
* OMEMO works even when a contact is offline, and works with multiple devices. It also allows asynchronous file-transfer when the server has [HTTP File Upload](http://xmpp.org/extensions/xep-0363.html). However, OMEMO is not as widely supported as OTR and is currently implemented only by Conversations and Gajim. OMEMO should be preferred over OTR for contacts who use Conversations.
|
||||
* OpenPGP (XEP-0027) is a very old encryption method that has some advantages over OTR but should only be used by experts who know what they are doing.
|
||||
|
||||
#### How do I use OpenPGP
|
||||
|
||||
@ -259,14 +320,36 @@ To use OpenPGP you have to install the open source app
|
||||
[OpenKeychain](http://www.openkeychain.org) and then long press on the account in
|
||||
manage accounts and choose renew PGP announcement from the contextual menu.
|
||||
|
||||
#### OMEMO is grayed out. What do I do?
|
||||
OMEMO has two requirements: Your server and the server of your contact need to support PEP. Both of you can verify that individually by opening your account details and selecting ```Server info``` from the menu. The appearing table should list PEP as available. The second requirement is mutual presence subscription. You can verify that by opening the contact details and see if both check boxes *Send presence updates* and *Receive presence updates* are checked.
|
||||
|
||||
#### How does the encryption for conferences work?
|
||||
|
||||
For conferences the only supported encryption method is OpenPGP (OTR does not
|
||||
work with multiple participants). Every participant has to announce their
|
||||
OpenPGP key (see answer above). If you would like to send encrypted messages to
|
||||
a conference you have to make sure that you have every participant's public key
|
||||
in your OpenKeychain. Right now there is no check in Conversations to ensure
|
||||
that. You have to take care of that yourself. Go to the conference details and
|
||||
For conferences only OMEMO and OpenPGP are supported as encryption method. (OTR
|
||||
does not work with multiple participants).
|
||||
|
||||
##### OMEMO
|
||||
|
||||
OMEMO encryption works only in private (members only) conferences that are non-anonymous.
|
||||
You need to have presence subscription with every member of the conference.
|
||||
You can verify that by going into the conference details, long press every member and start
|
||||
a conversation with them. (Or select 'contact details' if they are already in your contact
|
||||
list)
|
||||
|
||||
The owner of a conference can make a public conference private by going into the conference
|
||||
details and hit the settings button (the one with the gears) and select both *private* and
|
||||
*members only*.
|
||||
|
||||
If OMEMO is grayed out long pressing the lock icon will reveal some quick hints on why OMEMO
|
||||
is disabled.
|
||||
|
||||
##### OpenPGP
|
||||
|
||||
Every participant has to announce their OpenPGP key (see answer above).
|
||||
If you would like to send encrypted messages to a conference you have to make
|
||||
sure that you have every participant's public key in your OpenKeychain.
|
||||
Right now there is no check in Conversations to ensure that.
|
||||
You have to take care of that yourself. Go to the conference details and
|
||||
touch every key id (The hexadecimal number below a contact). This will send you
|
||||
to OpenKeychain which will assist you on adding the key. This works best in
|
||||
very small conferences with contacts you are already using OpenPGP with. This
|
||||
@ -274,6 +357,23 @@ feature is regarded experimental. Conversations is the only client that uses
|
||||
XEP-0027 with conferences. (The XEP neither specifically allows nor disallows
|
||||
this.)
|
||||
|
||||
#### Why is Conversations not end-to-end encrypted by default
|
||||
We briefly had OMEMO as the default E2EE but it turned out to be a usability nightmare and thus we reverted that. You can find more information in [the commit message](https://github.com/siacs/Conversations/commit/035d0c79572d5981c53d1bff7f30b484c6542f17) of that change.
|
||||
|
||||
Quick reminder that Conversations **always** uses TLS to connect to your server. It won‘t even connect to a server without TLS.
|
||||
|
||||
#### What is Blind Trust Before Verification / why are messages marked with a red lock?
|
||||
|
||||
Read more about the concept on https://gultsch.de/trust.html
|
||||
|
||||
### What clients do I use on other platforms
|
||||
There are XMPP Clients available for all major platforms.
|
||||
#### Windows / Linux
|
||||
For your desktop computer we recommend that you use [Gajim](https://gajim.org). You need to install the plugins `OMEMO`, `HTTP Upload` and `URL image preview` to get the best compatibility with Conversations. Plugins can be installed from within the app.
|
||||
#### iOS
|
||||
Unfortunately we don‘t have a recommendation for iPhones right now. There are two clients available [ChatSecure](https://chatsecure.org/) and [Monal](https://monal.im/). Both with their own pros and cons.
|
||||
|
||||
|
||||
### Development
|
||||
|
||||
<a name="beta"></a>
|
||||
@ -286,16 +386,18 @@ to sign up for the beta test.
|
||||
|
||||
#### How do I build Conversations
|
||||
|
||||
Make sure to have ANDROID_HOME point to your Android SDK
|
||||
Make sure to have ANDROID_HOME point to your Android SDK. Use the Android SDK Manager to install missing dependencies.
|
||||
|
||||
git clone https://github.com/siacs/Conversations.git
|
||||
cd Conversations
|
||||
./gradlew build
|
||||
./gradlew assembleFreeDebug
|
||||
|
||||
There are two build flavors available. *free* and *playstore*. Unless you know what you are doing you only need *free*.
|
||||
|
||||
|
||||
[![Build Status](https://travis-ci.org/siacs/Conversations.svg?branch=development)](https://travis-ci.org/siacs/Conversations)
|
||||
|
||||
### How do I update/add external libraries?
|
||||
#### How do I update/add external libraries?
|
||||
|
||||
If the library you want to update is in Maven Central or JCenter (or has its own
|
||||
Maven repo), add it or update its version in `build.gradle`. If the library is
|
||||
@ -318,16 +420,27 @@ To add a new dependency to the `libs/` directory (replacing "name", "branch" and
|
||||
|
||||
If something goes wrong Conversations usually exposes very little information in
|
||||
the UI (other than the fact that something didn't work). However with adb
|
||||
(android debug bridge) you squeeze some more information out of Conversations.
|
||||
(android debug bridge) you can squeeze some more information out of Conversations.
|
||||
These information are especially useful if you are experiencing trouble with
|
||||
your connection or with file transfer.
|
||||
|
||||
To use adb you have to connect your mobile phone to your computer with an USB cable
|
||||
and install `adb`. Most Linux systems have prebuilt packages for that tool. On
|
||||
Debian/Ubuntu for example it is called `android-tools-adb`.
|
||||
|
||||
Furthermore you might have to enable 'USB debugging' in the Developer options of your
|
||||
phone. After that you can just execute the following on your computer:
|
||||
|
||||
adb -d logcat -v time -s conversations
|
||||
|
||||
If need be there are also some Apps on the PlayStore that can be used to show the logcat
|
||||
directly on your rooted phone. (Search for logcat). However in regards to further processing
|
||||
(for example to create an issue here on Github) it is more convenient to just use your PC.
|
||||
|
||||
#### I found a bug
|
||||
|
||||
Please report it to our [issue tracker][issues]. If your app crashes please
|
||||
provide a stack trace. If you are experiencing misbehaviour please provide
|
||||
provide a stack trace. If you are experiencing misbehavior please provide
|
||||
detailed steps to reproduce. Always mention whether you are running the latest
|
||||
Play Store version or the current HEAD. If you are having problems connecting to
|
||||
your XMPP server your file transfer doesn’t work as expected please always
|
||||
|
@ -1,390 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="512"
|
||||
height="512"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.5 r10040"
|
||||
sodipodi:docname="conversations_baloon.svg"
|
||||
inkscape:export-filename="/home/diesys/diesys/grafica/conversation/conversation_bubble.png"
|
||||
inkscape:export-xdpi="100"
|
||||
inkscape:export-ydpi="100">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3874">
|
||||
<stop
|
||||
style="stop-color:#00a000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3876" />
|
||||
<stop
|
||||
style="stop-color:#00a000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3878" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3913">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3915" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3917" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3818">
|
||||
<stop
|
||||
style="stop-color:#669900;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3820" />
|
||||
<stop
|
||||
style="stop-color:#99cc00;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3822" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3818"
|
||||
id="radialGradient3824"
|
||||
cx="212.07048"
|
||||
cy="1045.9178"
|
||||
fx="212.07048"
|
||||
fy="1045.9178"
|
||||
r="238.57143"
|
||||
gradientTransform="matrix(1.9491621,-0.90817722,0.65829208,1.4128498,-879.63121,-248.98648)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3913"
|
||||
id="radialGradient3919"
|
||||
cx="362.98563"
|
||||
cy="379.77524"
|
||||
fx="362.98563"
|
||||
fy="379.77524"
|
||||
r="139.95312"
|
||||
gradientTransform="matrix(1.3800477,1.0445431,-1.3325077,1.7605059,339.09383,-577.83938)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="-155.75885"
|
||||
x2="114.59022"
|
||||
y1="35.545681"
|
||||
x1="114.55434"
|
||||
id="linearGradient3794"
|
||||
xlink:href="#linearGradient3788"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient3788">
|
||||
<stop
|
||||
id="stop3790"
|
||||
offset="0"
|
||||
style="stop-color:#1eed00;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3792"
|
||||
offset="1"
|
||||
style="stop-color:#abff28;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3821">
|
||||
<stop
|
||||
style="stop-color:#ff283d;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3823" />
|
||||
<stop
|
||||
style="stop-color:#ff28ae;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3825" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4543">
|
||||
<stop
|
||||
style="stop-color:#2e45bf;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4545" />
|
||||
<stop
|
||||
style="stop-color:#28a7ff;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop4547" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4098">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4100" />
|
||||
<stop
|
||||
style="stop-color:#e6e6e6;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4102" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4098"
|
||||
id="linearGradient3833"
|
||||
x1="273.81851"
|
||||
y1="764.74677"
|
||||
x2="304.14023"
|
||||
y2="936.47272"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4098"
|
||||
id="linearGradient3853"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="273.81851"
|
||||
y1="764.74677"
|
||||
x2="304.14023"
|
||||
y2="936.47272" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3818"
|
||||
id="radialGradient3863"
|
||||
cx="262.33273"
|
||||
cy="945.23846"
|
||||
fx="262.33273"
|
||||
fy="945.23846"
|
||||
r="185.49754"
|
||||
gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,170.11831)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3818"
|
||||
id="radialGradient3866"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,170.11831)"
|
||||
cx="262.33273"
|
||||
cy="945.23846"
|
||||
fx="262.33273"
|
||||
fy="945.23846"
|
||||
r="185.49754" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3913"
|
||||
id="radialGradient3873"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.3800477,1.0445431,-1.3325077,1.7605059,339.09383,-577.83938)"
|
||||
cx="321.75275"
|
||||
cy="386.38751"
|
||||
fx="321.75275"
|
||||
fy="386.38751"
|
||||
r="139.95312" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3818"
|
||||
id="radialGradient3880"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.2253203,-0.54206726,0.43090148,0.97403458,-466.4135,-370.24387)"
|
||||
cx="262.33273"
|
||||
cy="945.23846"
|
||||
fx="262.33273"
|
||||
fy="945.23846"
|
||||
r="185.49754" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3913"
|
||||
id="radialGradient3883"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.4430075,-0.63865195,0.50745433,1.1475866,-594.40824,44.803037)"
|
||||
cx="262.33273"
|
||||
cy="945.23846"
|
||||
fx="262.33273"
|
||||
fy="945.23846"
|
||||
r="185.49754" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter3895">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="2.0013623"
|
||||
id="feGaussianBlur3897" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3874"
|
||||
id="radialGradient3881"
|
||||
cx="150.35715"
|
||||
cy="236.28571"
|
||||
fx="150.35715"
|
||||
fy="236.28571"
|
||||
r="26.887305"
|
||||
gradientTransform="matrix(1,0,0,0.98671703,0,3.1385771)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.4142136"
|
||||
inkscape:cx="385.13513"
|
||||
inkscape:cy="237.84331"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer4"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1020"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-to-guides="true"
|
||||
inkscape:snap-grids="false"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:object-nodes="false"
|
||||
inkscape:snap-nodes="false">
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="0,534.28571"
|
||||
id="guide3004" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="394.28571,511.42857"
|
||||
id="guide3006" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="511.42857,320"
|
||||
id="guide3008" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="401.42857,0"
|
||||
id="guide3010" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="17.142857,258.57143"
|
||||
id="guide3012" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="327.14286,494.28571"
|
||||
id="guide3014" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="324.28571,17.142857"
|
||||
id="guide3016" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="494.28571,237.14286"
|
||||
id="guide3018" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="255.71429,302.85714"
|
||||
id="guide3022" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="660,-315"
|
||||
id="guide3904" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="554.28571,475.71429"
|
||||
id="guide3931" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="581.42857,244.28571"
|
||||
id="guide3933" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-540.36218)"
|
||||
style="display:inline">
|
||||
<path
|
||||
d="m 253.34375,605.78125 c -107.90463,0 -195.9375,85.86121 -195.9375,191.84375 0,105.98253 88.02779,191.90625 195.9375,191.90625 33.55862,0 59.4324,-6.89467 88.96875,-17.625 l 93.8125,37.81255 A 12.359798,12.359798 0 0 0 452.75,995.28125 L 427.34375,892.59375 C 443.67389,863.93074 449.25,831.2919 449.25,797.625 449.25,691.64506 361.24842,605.78125 253.34375,605.78125 z"
|
||||
id="path3885"
|
||||
style="opacity:0.6;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter3895)"
|
||||
inkscape:original="M 253.34375 618.125 C 151.96941 618.125 69.75 698.4746 69.75 797.625 C 69.75 896.77539 151.96941 977.1875 253.34375 977.1875 C 287.00054 977.1875 311.5728 970.27778 342.65625 958.71875 L 440.75 998.25 L 414.1875 890.8125 C 431.0772 863.65332 436.90625 831.73711 436.90625 797.625 C 436.90625 698.4746 354.71813 618.125 253.34375 618.125 z "
|
||||
inkscape:radius="12.358562"
|
||||
sodipodi:type="inkscape:offset"
|
||||
transform="matrix(1.1776575,0,0,1.1781783,-45.132882,-150.91395)" />
|
||||
<path
|
||||
sodipodi:type="inkscape:offset"
|
||||
inkscape:radius="12.358562"
|
||||
inkscape:original="M 253.34375 618.125 C 151.96941 618.125 69.75 698.4746 69.75 797.625 C 69.75 896.77539 151.96941 977.1875 253.34375 977.1875 C 287.00054 977.1875 311.5728 970.27778 342.65625 958.71875 L 440.75 998.25 L 414.1875 890.8125 C 431.0772 863.65332 436.90625 831.73711 436.90625 797.625 C 436.90625 698.4746 354.71813 618.125 253.34375 618.125 z "
|
||||
style="fill:#00a000;fill-opacity:1;stroke:none"
|
||||
id="path3868"
|
||||
d="m 253.34375,605.78125 c -107.90463,0 -195.9375,85.86121 -195.9375,191.84375 0,105.98253 88.02779,191.90625 195.9375,191.90625 33.55862,0 59.4324,-6.89467 88.96875,-17.625 l 93.8125,37.81255 A 12.359798,12.359798 0 0 0 452.75,995.28125 L 427.34375,892.59375 C 443.67389,863.93074 449.25,831.2919 449.25,797.625 449.25,691.64506 361.24842,605.78125 253.34375,605.78125 z"
|
||||
transform="matrix(1.1776575,0,0,1.1781783,-45.132882,-155.6267)" />
|
||||
<path
|
||||
style="opacity:0.19211821;fill:url(#radialGradient3883);fill-opacity:1;stroke:none"
|
||||
d="m 442.08605,700.89397 c -129.66422,0 -234.75863,103.19621 -234.75863,230.48113 0,26.84957 4.6841,52.62718 13.28548,76.5811 10.65333,1.4828 21.54531,2.2461 32.60637,2.2461 39.52053,0 69.99101,-8.1231 104.7747,-20.7651 l 110.479,44.5494 a 14.555607,14.562048 0 0 0 19.57853,-17.0097 L 458.13167,895.99293 c 19.23127,-33.77016 25.79804,-72.22452 25.79804,-111.89014 0,-28.84573 -5.53074,-56.41202 -15.60395,-81.77294 -8.61503,-0.94041 -17.37147,-1.43588 -26.23971,-1.43588 z"
|
||||
id="path3878"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccsssscc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3845"
|
||||
d="M 478.64112,1025.218 447.36049,898.60749 c 19.89028,-31.99834 26.74288,-69.57172 26.74288,-109.76189 0,-116.81686 -96.79943,-211.48385 -216.18374,-211.48385 -119.38425,0 -216.183656,94.66699 -216.183656,211.48385 0,116.81685 96.799406,211.5536 216.183656,211.5536 39.63617,0 68.58847,-8.14219 105.19417,-21.76075 z"
|
||||
style="opacity:0;fill:none;stroke:#000000;stroke-width:23.55835724;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:94.23343197, 94.23343197;stroke-dashoffset:0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3855"
|
||||
d="m 253.18246,561.05889 c -5.38379,-0.002 -10.7413,0.0871 -12.77023,0.22089 -16.80965,1.10727 -29.68729,3.05317 -44.38296,6.77453 -5.64799,1.43026 -9.96811,2.69833 -15.19914,4.41816 -3.34052,1.09828 -8.41764,2.85364 -8.68521,3.01909 -0.082,0.0507 3.32705,9.32907 7.98597,21.79631 0.0466,0.12496 0.17057,0.13832 0.33123,0.0736 1.11322,-0.44815 6.45699,-2.29745 8.94283,-3.09273 21.39718,-6.84518 43.95735,-10.19531 66.31683,-9.86723 3.14874,0.0461 7.13319,0.15915 8.83245,0.25775 1.69921,0.0987 3.12161,0.15378 3.16493,0.11037 0.0685,-0.0684 1.53237,-23.21444 1.47209,-23.26905 -0.0122,-0.0117 -1.41064,-0.10943 -3.12817,-0.22089 -2.0869,-0.13546 -7.49683,-0.21852 -12.88062,-0.22088 z m 110.81021,28.16581 c -0.10125,0.10911 -11.15095,20.28455 -11.15095,20.36043 0,0.0184 0.50701,0.31641 1.14084,0.66267 8.38104,4.57856 17.56037,10.63803 25.90897,17.04889 3.23527,2.48434 6.34578,5.02146 9.23674,7.54561 4.2123,3.67784 8.41256,7.68117 12.42754,11.81905 6.38417,6.5796 12.29989,13.4994 17.05071,19.99177 0.65274,0.89212 0.79099,1.01157 1.03047,0.84681 1.13402,-0.7802 18.39736,-13.76959 18.40089,-13.84358 0.005,-0.1 -3.33561,-4.52525 -4.74744,-6.29593 -5.64395,-7.07831 -10.59769,-12.59166 -17.26005,-19.25582 -8.26499,-8.26722 -16.14264,-15.121 -25.02569,-21.71453 -2.5667,-1.90515 -5.21733,-3.78858 -7.9855,-5.6781 -6.60132,-4.50598 -18.71149,-11.82683 -19.02653,-11.48727 z m -274.762206,39.94759 -2.428914,2.57732 c -21.579098,22.69359 -38.068397,49.23025 -48.467963,78.05431 -0.50904,1.41091 -0.957247,2.67589 -0.993643,2.83498 -0.04781,0.20904 2.956962,1.31003 10.78292,3.93954 5.956638,2.00143 10.92488,3.63791 11.040538,3.64502 0.115645,0.007 0.916879,-1.94564 1.803285,-4.34458 9.098432,-24.62317 23.187184,-47.25662 41.659643,-66.93523 l 3.05453,-3.27681 -8.206806,-8.24726 z m 388.222146,115.167 -9.89972,1.91451 c -5.44839,1.06994 -10.56998,2.07187 -11.40857,2.20908 -1.04711,0.17137 -1.54564,0.35016 -1.54564,0.55228 0,0.16187 0.23325,1.63204 0.51522,3.2768 2.70275,15.76547 3.28356,34.63258 1.69287,55.26394 -0.7281,9.44363 -2.34823,21.04449 -3.90099,28.01857 -0.23345,1.0486 -0.37949,1.97667 -0.33118,2.02499 0.0483,0.0483 5.12585,1.1561 11.29809,2.46683 6.17232,1.31067 11.30751,2.37915 11.3718,2.39315 0.0641,0.014 0.45734,-1.73307 0.88322,-3.90272 3.35867,-17.11028 4.82653,-33.18977 4.85786,-53.27572 0.0219,-14.08945 -0.79161,-24.35571 -2.87056,-36.96537 z m -427.268845,64.06345 -0.772838,0.11039 c -0.421858,0.0612 -5.59823,0.67716 -11.482161,1.36226 -5.883927,0.68512 -10.759171,1.30169 -10.819725,1.36228 -0.141991,0.142 0.252313,2.91986 1.140854,8.32086 4.869392,29.59836 15.038358,56.25732 31.539139,82.61977 0.450701,0.72005 0.931445,1.27763 1.06725,1.25182 0.361709,-0.0685 19.423106,-12.2036 19.431349,-12.3709 0.0036,-0.0779 -0.796734,-1.40341 -1.766487,-2.94542 -4.266677,-6.78447 -9.935035,-17.45299 -13.064635,-24.5945 -7.52905,-17.18062 -12.488823,-34.71382 -15.051936,-53.27567 z M 462.40066,926.44149 c -0.46898,0.009 -22.08567,5.38002 -22.2283,5.52269 -0.098,0.0981 22.04129,90.06142 22.37549,91.01382 0.40286,1.1482 3.73284,10.5298 13.56323,8.9156 10.95786,-2.3434 9.8458,-14.6677 8.99628,-14.4751 -0.11284,0.025 -5.02627,-20.61508 -11.18774,-45.58033 -8.79763,-35.64656 -11.26829,-45.40142 -11.51896,-45.39668 z M 143.91794,953.1714 c -0.40943,0.0131 -1.21588,1.3276 -6.29312,9.64634 -3.31435,5.43031 -6.03549,9.92123 -6.03549,9.9777 0,0.13674 3.42858,2.19027 7.06593,4.27089 22.35182,12.78549 47.08561,21.82095 72.42596,26.43487 3.59043,0.654 5.67261,1.0064 11.04051,1.804 0.69401,0.1031 1.36954,0.2073 1.50889,0.2212 0.31484,0.031 0.24386,0.6279 1.87691,-11.45018 0.75094,-5.5542 1.40492,-10.43428 1.47207,-10.86126 0.11781,-0.74877 0.0863,-0.77677 -0.58887,-0.88362 -0.38487,-0.0607 -2.68651,-0.4127 -5.11543,-0.77322 -22.30454,-3.3101 -45.25895,-10.90321 -65.32317,-21.57538 -3.55401,-1.89038 -10.16752,-5.64292 -11.85018,-6.73769 -0.0531,-0.0345 -0.12551,-0.0755 -0.18401,-0.0737 z m 214.22322,9.09404 c -1.98095,0.13013 -4.60205,1.01767 -10.4517,3.12954 -11.29964,4.0795 -24.13159,8.26507 -29.91986,9.75681 -0.83741,0.21582 -1.5445,0.50692 -1.54566,0.62592 -0.002,0.21503 5.72469,22.22722 5.81468,22.34847 0.0552,0.075 6.34708,-1.70267 10.2677,-2.90858 5.09669,-1.5677 13.67295,-4.44246 19.79936,-6.62722 l 6.07232,-2.17225 22.30187,8.98356 c 18.14341,7.31671 22.30098,8.95081 22.41231,8.68881 0.9061,-2.1322 8.61707,-21.32757 8.57483,-21.35419 -0.38803,-0.24492 -49.13929,-19.77601 -49.90327,-19.99221 -1.25781,-0.35596 -2.23399,-0.55669 -3.42258,-0.47866 z"
|
||||
style="opacity:0.5;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:5.88958931;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:nodetypes="ccssccssscccccccssssscccsssscccscsssccccccssssssssssccccscssccsscccsscsscsssssccsccsscsscsccscccccssc" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="Dots">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:0.928;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="path3047"
|
||||
sodipodi:cx="173.57143"
|
||||
sodipodi:cy="241.28571"
|
||||
sodipodi:rx="26.428572"
|
||||
sodipodi:ry="20"
|
||||
d="m 200,241.28571 a 26.428572,20 0 1 1 -52.85715,0 26.428572,20 0 1 1 52.85715,0 z"
|
||||
transform="matrix(0.94594594,0,0,1.25,-18.332045,-54.607132)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:0.928;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="path3047-1"
|
||||
sodipodi:cx="173.57143"
|
||||
sodipodi:cy="241.28571"
|
||||
sodipodi:rx="26.428572"
|
||||
sodipodi:ry="20"
|
||||
d="m 200,241.28571 a 26.428572,20 0 1 1 -52.85715,0 26.428572,20 0 1 1 52.85715,0 z"
|
||||
transform="matrix(0.94594594,0,0,1.25,91.38502,-54.607132)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:0.928;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="path3047-1-8"
|
||||
sodipodi:cx="173.57143"
|
||||
sodipodi:cy="241.28571"
|
||||
sodipodi:rx="26.428572"
|
||||
sodipodi:ry="20"
|
||||
d="m 200,241.28571 a 26.428572,20 0 1 1 -52.85715,0 26.428572,20 0 1 1 52.85715,0 z"
|
||||
transform="matrix(0.94594594,0,0,1.25,201.38502,-54.607132)" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 18 KiB |
168
art/date_bubble_grey.svg
Normal file
@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="26"
|
||||
height="26"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="date_bubble_white.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<filter
|
||||
x="-0.25"
|
||||
y="-0.25"
|
||||
width="1.5"
|
||||
height="1.5"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter3811"
|
||||
style="color-interpolation-filters:sRGB">
|
||||
<feFlood
|
||||
flood-opacity="0.25"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood3813" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite3815" />
|
||||
<feGaussianBlur
|
||||
stdDeviation="0.5"
|
||||
result="blur"
|
||||
id="feGaussianBlur3817" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="1"
|
||||
result="offset"
|
||||
id="feOffset3819" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite3821" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="9.745257"
|
||||
inkscape:cy="9.618802"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1916"
|
||||
inkscape:window-height="1156"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="20"
|
||||
inkscape:window-maximized="0"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
guidecolor="#000000"
|
||||
guideopacity="0.49803922"
|
||||
fit-margin-top="-2"
|
||||
fit-margin-left="-2"
|
||||
fit-margin-right="-2"
|
||||
fit-margin-bottom="-2">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2985"
|
||||
empspacing="4"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
originx="-9"
|
||||
originy="-1"
|
||||
color="#0000ff"
|
||||
opacity="0.03137255" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="11,26"
|
||||
id="guide3060"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="15,26"
|
||||
id="guide3062"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="26,21"
|
||||
id="guide3064"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="26,5"
|
||||
id="guide3066"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="17,0"
|
||||
id="guide3068"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="9,0"
|
||||
id="guide3070"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,18"
|
||||
id="guide3074"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,16"
|
||||
id="guide3076"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer"
|
||||
transform="translate(-9,-1)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3805"
|
||||
d="m 8,8 c 2,2 4,6 4,10 L 16,8 Z"
|
||||
style="display:none;fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3811)" />
|
||||
<rect
|
||||
style="fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3811)"
|
||||
id="rect2987"
|
||||
width="20"
|
||||
height="20"
|
||||
x="12"
|
||||
y="4"
|
||||
ry="2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
168
art/date_bubble_white.svg
Normal file
@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="26"
|
||||
height="26"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="date_bubble_white.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<filter
|
||||
x="-0.25"
|
||||
y="-0.25"
|
||||
width="1.5"
|
||||
height="1.5"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter3811"
|
||||
style="color-interpolation-filters:sRGB">
|
||||
<feFlood
|
||||
flood-opacity="0.25"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood3813" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite3815" />
|
||||
<feGaussianBlur
|
||||
stdDeviation="0.5"
|
||||
result="blur"
|
||||
id="feGaussianBlur3817" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="1"
|
||||
result="offset"
|
||||
id="feOffset3819" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite3821" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="9.745257"
|
||||
inkscape:cy="9.618802"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1916"
|
||||
inkscape:window-height="1156"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="20"
|
||||
inkscape:window-maximized="0"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
guidecolor="#000000"
|
||||
guideopacity="0.49803922"
|
||||
fit-margin-top="-2"
|
||||
fit-margin-left="-2"
|
||||
fit-margin-right="-2"
|
||||
fit-margin-bottom="-2">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2985"
|
||||
empspacing="4"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
originx="-9"
|
||||
originy="-1"
|
||||
color="#0000ff"
|
||||
opacity="0.03137255" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="11,26"
|
||||
id="guide3060"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="15,26"
|
||||
id="guide3062"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="26,21"
|
||||
id="guide3064"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="26,5"
|
||||
id="guide3066"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="17,0"
|
||||
id="guide3068"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="9,0"
|
||||
id="guide3070"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,18"
|
||||
id="guide3074"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,16"
|
||||
id="guide3076"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer"
|
||||
transform="translate(-9,-1)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3805"
|
||||
d="m 8,8 c 2,2 4,6 4,10 L 16,8 Z"
|
||||
style="display:none;fill:#fafafa;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3811)" />
|
||||
<rect
|
||||
style="fill:#fafafa;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3811)"
|
||||
id="rect2987"
|
||||
width="20"
|
||||
height="20"
|
||||
x="12"
|
||||
y="4"
|
||||
ry="2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
427
art/ic_launcher.svg
Normal file
@ -0,0 +1,427 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="57mm"
|
||||
height="57mm"
|
||||
viewBox="0 0 201.96849 201.96849"
|
||||
id="svg4211"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="conversations_baloon.svg">
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showguides="false"
|
||||
inkscape:zoom="2.2196812"
|
||||
inkscape:cx="39.109276"
|
||||
inkscape:cy="132.27753"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:window-height="836"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer8" />
|
||||
<defs
|
||||
id="defs4213">
|
||||
<linearGradient
|
||||
osb:paint="solid"
|
||||
id="linearGradient5393">
|
||||
<stop
|
||||
id="stop5395"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
id="clipPath4831"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<circle
|
||||
style="display:inline;opacity:1;fill:#a00e00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="circle4833"
|
||||
cx="883.16943"
|
||||
cy="677.19611"
|
||||
r="229.80969" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="clipPath4859"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<circle
|
||||
style="display:inline;opacity:1;fill:#a00e00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="circle4861"
|
||||
cx="883.16943"
|
||||
cy="677.19611"
|
||||
r="229.80969" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="clipPath5624"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g5626"
|
||||
transform="matrix(0.3835576,0,0,0.3835576,-250.60108,-156.11014)">
|
||||
<path
|
||||
sodipodi:nodetypes="ccsssc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5628"
|
||||
d="m 1120.8042,772.36056 -118.0025,103.66316 118.5792,46.01918 c 8.4859,3.29325 19.6524,7.94481 27.2622,0.71376 7.3868,-7.01907 5.6502,-14.13839 3.0935,-24.54095 z"
|
||||
style="display:inline;fill:#4caf50;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<circle
|
||||
transform="matrix(1.0878566,0,0,1.0878566,-57.401992,-79.686482)"
|
||||
clip-path="url(#clipPath4859)"
|
||||
r="229.80969"
|
||||
cy="677.19611"
|
||||
cx="883.16943"
|
||||
id="circle5630"
|
||||
style="display:inline;opacity:1;fill:#4caf50;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath10653">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g10655"
|
||||
transform="matrix(0.3835576,0,0,0.3835576,-250.60108,-156.11015)"
|
||||
inkscape:export-xdpi="100"
|
||||
inkscape:export-ydpi="100">
|
||||
<path
|
||||
sodipodi:nodetypes="ccsssc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10657"
|
||||
d="m 1120.8042,772.36056 -118.0025,103.66316 118.5792,46.01918 c 8.4859,3.29325 19.6524,7.94481 27.2622,0.71376 7.3868,-7.01907 5.6502,-14.13839 3.0935,-24.54095 z"
|
||||
style="display:inline;fill:#4caf50;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<circle
|
||||
transform="matrix(1.0878566,0,0,1.0878566,-57.401992,-79.686482)"
|
||||
clip-path="url(#clipPath4859)"
|
||||
r="229.80969"
|
||||
cy="677.19611"
|
||||
cx="883.16943"
|
||||
id="circle10659"
|
||||
style="display:inline;opacity:1;fill:#4caf50;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</clipPath>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3913"
|
||||
id="radialGradient3883"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.68662089,-0.30388739,0.24146012,0.54605188,-300.74233,-264.46964)"
|
||||
cx="262.33273"
|
||||
cy="945.23846"
|
||||
fx="262.33273"
|
||||
fy="945.23846"
|
||||
r="185.49754" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3913">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3915" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3917" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath5315">
|
||||
<g
|
||||
inkscape:export-ydpi="100"
|
||||
inkscape:export-xdpi="100"
|
||||
transform="matrix(0.3835576,0,0,0.3835576,-246.60108,-156.11013)"
|
||||
id="g5317"
|
||||
style="display:inline;fill:#00a000;fill-opacity:1">
|
||||
<path
|
||||
style="display:inline;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 1120.8042,772.36056 -118.0025,103.66316 118.5792,46.01918 c 8.4859,3.29325 19.6524,7.94481 27.2622,0.71376 7.3868,-7.01907 5.6502,-14.13839 3.0935,-24.54095 z"
|
||||
id="path5319"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccsssc" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="circle5321"
|
||||
cx="883.16943"
|
||||
cy="677.19611"
|
||||
r="229.80969"
|
||||
clip-path="url(#clipPath4859)"
|
||||
transform="matrix(1.0878566,0,0,1.0878566,-57.401992,-79.686482)" />
|
||||
</g>
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath6882">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6884"
|
||||
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath6886">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6888"
|
||||
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath6890">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6892"
|
||||
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath6894">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6896"
|
||||
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath6898">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6900"
|
||||
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath6902">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6904"
|
||||
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath6906">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6908"
|
||||
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath6910">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6912"
|
||||
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter5640"
|
||||
x="-0.012227737"
|
||||
width="1.0244555"
|
||||
y="-0.011780591"
|
||||
height="1.0235612">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.9782166"
|
||||
id="feGaussianBlur5642" />
|
||||
</filter>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath5745">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5747"
|
||||
d="M 99.908581,-2.3831968e-4 A 95.889392,95.889392 0 0 0 4.0199102,95.888436 95.889392,95.889392 0 0 0 99.908581,191.77906 95.889392,95.889392 0 0 0 142.61366,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 L 183.8285,142.24002 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.79921,95.888466 95.889392,95.889392 0 0 0 99.908581,-2.0831968e-4 Z"
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata4216">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer9"
|
||||
inkscape:label="shaddow"
|
||||
transform="translate(-4,2.6816164)"
|
||||
style="display:inline">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6914"
|
||||
d="M 104.88867,0.06226191 A 95.889392,95.889392 0 0 0 8.9999996,95.950936 95.889392,95.889392 0 0 0 104.88867,191.84156 95.889392,95.889392 0 0 0 147.59375,181.76343 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 200.7793,95.950966 95.889392,95.889392 0 0 0 104.88867,0.06229191 Z"
|
||||
style="display:inline;opacity:0.4;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter5640)" />
|
||||
</g>
|
||||
<g
|
||||
style="display:inline"
|
||||
inkscape:label="bubble"
|
||||
id="layer4"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(-4,2.6816348)">
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 104.88867,-1.9377566 A 95.889392,95.889392 0 0 0 8.9999996,93.950918 95.889392,95.889392 0 0 0 104.88867,189.84154 95.889392,95.889392 0 0 0 147.59375,179.76341 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 L 188.80859,140.3025 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 200.7793,93.950948 95.889392,95.889392 0 0 0 104.88867,-1.9377266 Z"
|
||||
id="circle6661"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:125px;line-height:1000%;font-family:Sans;letter-spacing:-10.89000034px;word-spacing:5px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="85.862968"
|
||||
y="-55.271603"
|
||||
id="text6634"
|
||||
sodipodi:linespacing="1000%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6636"
|
||||
x="85.862968"
|
||||
y="-55.271603" /></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer8"
|
||||
inkscape:label="dotted line"
|
||||
style="display:inline"
|
||||
transform="translate(-4,2.6816164)">
|
||||
<path
|
||||
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
clip-path="url(#clipPath6910)"
|
||||
d="m 145.16406,11.183594 -5.13232,9.649402 c -0.77924,1.465076 -0.65974,2.41396 0.66876,3.18097 9.66686,5.488467 18.12303,12.874168 24.86104,21.711122 1.05534,1.616079 2.08054,1.713076 3.67763,0.571565 L 178.04883,40 C 169.45271,27.990203 158.19857,18.128379 145.16406,11.183594 Z"
|
||||
id="path7364"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csccscc"
|
||||
transform="translate(4.9999996,-1.9374999)" />
|
||||
<path
|
||||
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
clip-path="url(#clipPath6906)"
|
||||
d="m 193.80469,75.615234 -9.62713,2.062751 c -2.66266,0.570512 -3.40763,1.172953 -2.90593,3.917433 0.85823,4.714633 1.30424,9.497137 1.33189,14.293254 -0.028,5.578758 -0.62194,11.137108 -1.77093,16.589918 -0.86591,3.23162 0.13682,3.77092 3.16149,4.58138 l 8.98639,2.30136 c 1.98177,-7.66828 3.00584,-15.55255 3.04883,-23.472658 -0.0187,-6.817681 -0.76446,-13.613926 -2.22461,-20.273438 z"
|
||||
id="path7366"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csccccccc"
|
||||
transform="translate(4.9999996,-1.9374999)" />
|
||||
<path
|
||||
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
clip-path="url(#clipPath6902)"
|
||||
d="m 14.264281,102.76512 -10.2076406,0.87943 c 1.2093798,14.83154 5.8540346,29.17808 13.5664056,41.90429 l 8.544301,-5.23239 c 2.394983,-1.46665 1.895406,-3.37834 0.986202,-5.04513 -5.118253,-9.40257 -8.359018,-19.71635 -9.536202,-30.36553 0,-2.09418 -1.881577,-2.26744 -3.353066,-2.14067 z"
|
||||
id="path7372"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sccsccs"
|
||||
transform="translate(4.9999996,-1.9374999)" />
|
||||
<path
|
||||
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
clip-path="url(#clipPath6898)"
|
||||
d="m 51.504371,166.60235 -5.82273,8.50898 c 12.710503,8.71282 27.333669,14.23394 42.630859,16.0957 l 1.220329,-9.90843 c 0.355066,-2.88295 -1.085712,-3.52946 -3.332252,-3.90256 -10.402329,-1.73697 -20.373956,-5.45322 -29.373754,-10.94516 -1.647505,-1.06744 -3.639993,-2.30718 -5.322452,0.15147 z"
|
||||
id="path7370"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sccsccs"
|
||||
transform="translate(4.9999996,-1.9374999)" />
|
||||
<path
|
||||
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
clip-path="url(#clipPath6894)"
|
||||
d="M 32.208984,27.683594 C 21.779177,38.079001 13.883707,50.736882 9.1347656,64.675781 L 19.33617,68.090365 c 1.658147,0.55501 2.832564,-0.120955 3.374272,-1.591979 3.777598,-10.021698 9.470788,-19.210103 16.759132,-27.052307 1.561136,-1.561136 1.567283,-2.960058 0.447507,-4.076606 z"
|
||||
id="path7374"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccsccsc"
|
||||
transform="translate(4.9999996,-1.9374999)" />
|
||||
<path
|
||||
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
clip-path="url(#clipPath6890)"
|
||||
d="M 99.888672,-0.25 C 87.701045,-0.2239408 75.630114,2.1252837 64.322266,6.671875 l 3.530435,8.74898 c 1.063314,2.635062 1.616754,3.526314 4.973913,2.352259 8.692057,-3.031338 17.839027,-4.588849 27.062058,-4.599286 5.555828,0 6.486278,0.350026 6.780788,-3.4460223 l 0.74851,-9.64772758 C 104.9135,-0.12857239 102.40179,-0.23868346 99.888672,-0.25 Z"
|
||||
id="path7376"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccsccscc"
|
||||
transform="translate(4.9999996,-1.9374999)" />
|
||||
<path
|
||||
style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
clip-path="url(#clipPath6886)"
|
||||
d="m 138.72416,168.48439 c -4.17634,2.25458 -8.55959,4.09055 -13.0504,5.63418 -1.00363,0.34498 -1.20742,1.18222 -0.8682,2.27372 l 3.44056,11.0706 c 4.92985,-1.53124 9.72799,-3.45808 14.34766,-5.76172 l 0.12695,0.0137 14.0293,5.44532 4.12174,-10.20577 c 0.7548,-1.86894 -0.0184,-2.7016 -1.59462,-3.31324 l -14.72114,-5.71251 c -1.86679,-0.7244 -3.68834,-0.60144 -5.83185,0.55572 z"
|
||||
id="path5005"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cssccccsssc"
|
||||
transform="translate(4.9999996,-1.9374999)" />
|
||||
<path
|
||||
style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
clip-path="url(#clipPath6882)"
|
||||
d="m 186.53125,152.80469 -10.6386,2.70888 c -0.78879,0.20085 -1.67397,1.02386 -1.35494,2.33801 l 9.75918,40.15428 c 8.56713,5.97538 15.30408,3.06731 11.01563,-9.47266 z"
|
||||
id="path5071"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cssccc"
|
||||
transform="translate(4.9999996,-1.9374999)" />
|
||||
</g>
|
||||
<g
|
||||
style="display:inline"
|
||||
inkscape:label="dots"
|
||||
id="layer2"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(-4,2.6816348)">
|
||||
<g
|
||||
inkscape:export-ydpi="100"
|
||||
inkscape:export-xdpi="100"
|
||||
style="fill:#f5f5f5;fill-opacity:1"
|
||||
transform="matrix(0.3835576,0,0,0.3835576,-248.17635,-138.86977)"
|
||||
id="g5126">
|
||||
<circle
|
||||
r="27.299093"
|
||||
style="opacity:1;fill:#f5f5f5;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="path3047-4"
|
||||
cx="799.11273"
|
||||
cy="609.86285" />
|
||||
<circle
|
||||
r="27.299093"
|
||||
style="opacity:1;fill:#f5f5f5;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="path3047-1-2"
|
||||
cx="918.91962"
|
||||
cy="609.86285" />
|
||||
<circle
|
||||
r="27.299093"
|
||||
style="opacity:1;fill:#f5f5f5;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="path3047-1-8-6"
|
||||
cx="1039.0352"
|
||||
cy="609.86285" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="light"
|
||||
style="display:inline"
|
||||
transform="translate(-4,2.6816164)">
|
||||
<path
|
||||
style="display:inline;opacity:0.19211821;fill:url(#radialGradient3883);fill-opacity:1;stroke:none"
|
||||
d="m 192.44891,47.715674 c -61.69765,0 -111.704333,49.103472 -111.704333,109.668976 0,12.77573 2.228815,25.0414 6.321575,36.4393 5.069139,0.70557 10.251828,1.06876 15.514978,1.06876 18.80489,0 30.91434,7.28449 47.46533,1.26909 l 54.00234,6.06606 c 5.24363,2.11897 11.63381,1.37954 10.27166,-4.11162 l -14.23663,-57.56735 c 9.15073,-16.06873 12.27539,-34.36633 12.27539,-53.240271 0,-13.72556 -2.63167,-26.842322 -7.42478,-38.909717 -4.09925,-0.447474 -8.2658,-0.683228 -12.48553,-0.683228 z"
|
||||
id="path3878"
|
||||
inkscape:connector-curvature="0"
|
||||
clip-path="url(#clipPath5745)"
|
||||
transform="translate(4.9800894,-1.9374999)"
|
||||
sodipodi:nodetypes="sscsccccscs" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 25 KiB |
54
art/ic_notifications_none_white80.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_notifications_none_white80.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="1093"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.8333333"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z"
|
||||
id="path4"
|
||||
style="fill:#ffffff;fill-opacity:1;opacity:0.8" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
54
art/ic_notifications_off_white80.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_notifications_off_white80.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1244"
|
||||
inkscape:window-height="936"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.8333333"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M20 18.69L7.84 6.14 5.27 3.49 4 4.76l2.8 2.8v.01c-.52.99-.8 2.16-.8 3.42v5l-2 2v1h13.73l2 2L21 19.72l-1-1.03zM12 22c1.11 0 2-.89 2-2h-4c0 1.11.89 2 2 2zm6-7.32V11c0-3.08-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68c-.15.03-.29.08-.42.12-.1.03-.2.07-.3.11h-.01c-.01 0-.01 0-.02.01-.23.09-.46.2-.68.31 0 0-.01 0-.01.01L18 14.68z"
|
||||
id="path4"
|
||||
style="fill:#ffffff;fill-opacity:1;opacity:0.8" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
54
art/ic_notifications_paused_white80.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_notifications_paused_white80.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1375"
|
||||
inkscape:window-height="999"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.8333333"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.93 6 11v5l-2 2v1h16v-1l-2-2zm-3.5-6.2l-2.8 3.4h2.8V15h-5v-1.8l2.8-3.4H9.5V8h5v1.8z"
|
||||
id="path4"
|
||||
style="fill:#ffffff;fill-opacity:1;opacity:0.8" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
54
art/ic_notifications_white80.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg32"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_notifications_white80.svg">
|
||||
<metadata
|
||||
id="metadata40">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs38" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1471"
|
||||
inkscape:window-height="985"
|
||||
id="namedview36"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.8333333"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg32" />
|
||||
<path
|
||||
d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"
|
||||
id="path34"
|
||||
style="fill:#ffffff;fill-opacity:1;opacity:0.8" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
54
art/ic_send_cancel_offline_white.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_send_cancel_offline_white.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1080"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="-36.305085"
|
||||
inkscape:cy="23.898305"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm10 27.17L31.17 34 24 26.83 16.83 34 14 31.17 21.17 24 14 16.83 16.83 14 24 21.17 31.17 14 34 16.83 26.83 24 34 31.17z"
|
||||
id="path4"
|
||||
style="fill:#ffffff;fill-opacity:0.627451" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
54
art/ic_send_location_offline_white.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_send_location_offline_white.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="956"
|
||||
inkscape:window-height="1056"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="-36.305085"
|
||||
inkscape:cy="23.898305"
|
||||
inkscape:window-x="2880"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M24 4c-7.73 0-14 6.27-14 14 0 10.5 14 26 14 26s14-15.5 14-26c0-7.73-6.27-14-14-14zm0 19c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"
|
||||
id="path4"
|
||||
style="fill:#ffffff;fill-opacity:0.627451" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
60
art/ic_send_photo_offline_white.svg
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_send_photo_offline_white.svg">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="956"
|
||||
inkscape:window-height="567"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="10.5688"
|
||||
inkscape:cy="23.898305"
|
||||
inkscape:window-x="960"
|
||||
inkscape:window-y="609"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<circle
|
||||
cx="24"
|
||||
cy="24"
|
||||
r="6.4"
|
||||
id="circle4"
|
||||
style="fill:#ffffff;fill-opacity:0.627451" />
|
||||
<path
|
||||
d="M18 4l-3.66 4H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4h-6.34L30 4H18zm6 30c-5.52 0-10-4.48-10-10s4.48-10 10-10 10 4.48 10 10-4.48 10-10 10z"
|
||||
id="path6"
|
||||
style="fill:#ffffff;fill-opacity:0.627451" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
55
art/ic_send_picture_offline_white.svg
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_send_picture_offline_white.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1392"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="-21.864407"
|
||||
inkscape:cy="23.898305"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M42 38V10c0-2.21-1.79-4-4-4H10c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4zM17 27l5 6.01L29 24l9 12H10l7-9z"
|
||||
id="path4"
|
||||
style="fill:#ffffff;fill-opacity:0.627451" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
70
art/ic_send_text_offline_white.svg
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg3621"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
width="96"
|
||||
height="96"
|
||||
sodipodi:docname="ic_send_text_offline_white.svg"
|
||||
inkscape:export-filename="/home/daniel/workspace/Conversations/res/drawable-xxhdpi/ic_action_send_now_online.png"
|
||||
inkscape:export-xdpi="154.28572"
|
||||
inkscape:export-ydpi="154.28572">
|
||||
<metadata
|
||||
id="metadata3627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs3625" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1344"
|
||||
inkscape:window-height="1056"
|
||||
id="namedview3623"
|
||||
showgrid="true"
|
||||
showguides="true"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="31.783303"
|
||||
inkscape:cy="56.698828"
|
||||
inkscape:window-x="2880"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3621"
|
||||
inkscape:snap-others="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3631" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.627451;stroke:none"
|
||||
d="M 3.887575,4.1549246 90.999747,47.676331 3.887575,91.286663 13.203552,52.344101 63.012683,47.720794 13.203552,43.008558 Z"
|
||||
id="path3633"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
inkscape:export-filename="/home/daniel/workspace/Conversations/res/drawable-mdpi/ic_action_send_now_dnd.png"
|
||||
inkscape:export-xdpi="51.42857"
|
||||
inkscape:export-ydpi="51.42857" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
54
art/ic_send_voice_offline_white.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_send_voice_offline_white.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1516"
|
||||
inkscape:window-height="1056"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="-36.711864"
|
||||
inkscape:cy="24"
|
||||
inkscape:window-x="2880"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M24 30c3.31 0 5.98-2.69 5.98-6L30 12c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V44h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z"
|
||||
id="path4"
|
||||
style="fill:#ffffff;fill-opacity:0.627451" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
54
art/ic_verified_fingerprint.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ic_verified_fingerprint.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1916"
|
||||
inkscape:window-height="1156"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="-3.3559322"
|
||||
inkscape:cy="24"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="20"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M24 2L6 10v12c0 11.11 7.67 21.47 18 24 10.33-2.53 18-12.89 18-24V10L24 2zm-4 32l-8-8 2.83-2.83L20 28.34l13.17-13.17L36 18 20 34z"
|
||||
id="path4"
|
||||
style="fill:#259b24;fill-opacity:0.87" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
art/main_logo.svg
Symbolic link
@ -0,0 +1 @@
|
||||
ic_launcher.svg
|
165
art/message_bubble_received_dark.svg
Normal file
@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="36"
|
||||
height="26"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.5 r10040"
|
||||
sodipodi:docname="message_bubble_received.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<filter
|
||||
x="-0.25"
|
||||
y="-0.25"
|
||||
width="1.5"
|
||||
height="1.5"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter3811"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood
|
||||
flood-opacity="0.25"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood3813" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite3815" />
|
||||
<feGaussianBlur
|
||||
stdDeviation="0.5"
|
||||
result="blur"
|
||||
id="feGaussianBlur3817" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="1"
|
||||
result="offset"
|
||||
id="feOffset3819" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite3821" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="25.745257"
|
||||
inkscape:cy="9.618802"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:window-width="989"
|
||||
inkscape:window-height="755"
|
||||
inkscape:window-x="22"
|
||||
inkscape:window-y="16"
|
||||
inkscape:window-maximized="0"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
guidecolor="#000000"
|
||||
guideopacity="0.49803922">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2985"
|
||||
empspacing="4"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="1px"
|
||||
spacingy="1px"
|
||||
originx="0px"
|
||||
originy="0px"
|
||||
color="#0000ff"
|
||||
opacity="0.03137255" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="20,26"
|
||||
id="guide3060" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="24,26"
|
||||
id="guide3062" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="36,22"
|
||||
id="guide3064" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="36,6"
|
||||
id="guide3066" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="26,0"
|
||||
id="guide3068" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="18,0"
|
||||
id="guide3070" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,10"
|
||||
id="guide3074" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,8"
|
||||
id="guide3076" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer"
|
||||
transform="translate(0,-2)">
|
||||
<g
|
||||
id="g3759"
|
||||
style="fill:#326130;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
|
||||
<path
|
||||
style="display:none"
|
||||
d="m 8,6 c 2,2 4,6 4,10 L 16,6 z"
|
||||
id="path3805"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,2)"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path2989"
|
||||
d="M 4,4 16,16 16,4 z"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<rect
|
||||
ry="2"
|
||||
y="4"
|
||||
x="12"
|
||||
height="20"
|
||||
width="20"
|
||||
id="rect2987" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
167
art/message_bubble_received_grey.svg
Normal file
@ -0,0 +1,167 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="36"
|
||||
height="26"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="message_bubble_received_grey.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<filter
|
||||
x="-0.25"
|
||||
y="-0.25"
|
||||
width="1.5"
|
||||
height="1.5"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter3811"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood
|
||||
flood-opacity="0.25"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood3813" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite3815" />
|
||||
<feGaussianBlur
|
||||
stdDeviation="0.5"
|
||||
result="blur"
|
||||
id="feGaussianBlur3817" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="1"
|
||||
result="offset"
|
||||
id="feOffset3819" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite3821" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="-9.879743"
|
||||
inkscape:cy="9.618802"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer"
|
||||
showgrid="true"
|
||||
inkscape:window-width="2135"
|
||||
inkscape:window-height="911"
|
||||
inkscape:window-x="22"
|
||||
inkscape:window-y="16"
|
||||
inkscape:window-maximized="0"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
guidecolor="#000000"
|
||||
guideopacity="0.49803922">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2985"
|
||||
empspacing="4"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="1px"
|
||||
spacingy="1px"
|
||||
originx="0px"
|
||||
originy="0px"
|
||||
color="#0000ff"
|
||||
opacity="0.03137255" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="20,26"
|
||||
id="guide3060" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="24,26"
|
||||
id="guide3062" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="36,22"
|
||||
id="guide3064" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="36,6"
|
||||
id="guide3066" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="26,0"
|
||||
id="guide3068" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="18,0"
|
||||
id="guide3070" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,10"
|
||||
id="guide3074" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,8"
|
||||
id="guide3076" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer"
|
||||
transform="translate(0,-2)">
|
||||
<g
|
||||
id="g3759"
|
||||
style="fill:#424242;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
|
||||
<path
|
||||
style="display:none;fill:#424242;fill-opacity:1"
|
||||
d="m 8,6 c 2,2 4,6 4,10 L 16,6 z"
|
||||
id="path3805"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,2)"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path2989"
|
||||
d="M 4,4 16,16 16,4 z"
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:#424242;fill-opacity:1" />
|
||||
<rect
|
||||
ry="2"
|
||||
y="4"
|
||||
x="12"
|
||||
height="20"
|
||||
width="20"
|
||||
id="rect2987"
|
||||
style="fill:#424242;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
167
art/message_bubble_sent_grey.svg
Normal file
@ -0,0 +1,167 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="36"
|
||||
height="26"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="message_bubble_sent_grey.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<filter
|
||||
x="-0.25"
|
||||
y="-0.25"
|
||||
width="1.5"
|
||||
height="1.5"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter3811"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood
|
||||
flood-opacity="0.25"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood3813" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite3815" />
|
||||
<feGaussianBlur
|
||||
stdDeviation="0.5"
|
||||
result="blur"
|
||||
id="feGaussianBlur3817" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="1"
|
||||
result="offset"
|
||||
id="feOffset3819" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite3821" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="6.244862"
|
||||
inkscape:cy="16.118802"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1554"
|
||||
inkscape:window-height="900"
|
||||
inkscape:window-x="878"
|
||||
inkscape:window-y="369"
|
||||
inkscape:window-maximized="0"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
guidecolor="#404040"
|
||||
guideopacity="0.49803922">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2985"
|
||||
empspacing="4"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="1px"
|
||||
spacingy="1px"
|
||||
originx="0px"
|
||||
originy="0px"
|
||||
color="#0000ff"
|
||||
opacity="0.03137255" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="12,26"
|
||||
id="guide3146" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="16,26"
|
||||
id="guide3148" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="36,22"
|
||||
id="guide3150" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="36,6"
|
||||
id="guide3152" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="18,0"
|
||||
id="guide3154" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="10,0"
|
||||
id="guide3160" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,20"
|
||||
id="guide3162" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="0,18"
|
||||
id="guide3164" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer"
|
||||
transform="translate(0,-2)">
|
||||
<g
|
||||
id="g3759"
|
||||
style="fill:#424242;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
|
||||
<path
|
||||
style="display:none;fill:#424242;fill-opacity:1"
|
||||
d="M 28,18 C 26,16 24,12 24,8 l -4,10 z"
|
||||
id="path3809"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,2)"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path2989"
|
||||
d="m 20,12 0,12 12,0 z"
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:#424242;fill-opacity:1" />
|
||||
<rect
|
||||
ry="2"
|
||||
y="4"
|
||||
x="4"
|
||||
height="20"
|
||||
width="20"
|
||||
id="rect2987"
|
||||
style="fill:#424242;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
68
art/play_gif.svg
Normal file
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="play_gif.svg">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1200"
|
||||
id="namedview12"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.8333333"
|
||||
inkscape:cx="1.5762712"
|
||||
inkscape:cy="11.084746"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<path
|
||||
id="a"
|
||||
d="M24 24H0V0h24v24z" />
|
||||
</defs>
|
||||
<clipPath
|
||||
id="b">
|
||||
<use
|
||||
xlink:href="#a"
|
||||
overflow="visible"
|
||||
id="use8" />
|
||||
</clipPath>
|
||||
<path
|
||||
d="M11.5 9H13v6h-1.5zM9 9H6c-.6 0-1 .5-1 1v4c0 .5.4 1 1 1h3c.6 0 1-.5 1-1v-2H8.5v1.5h-2v-3H10V10c0-.5-.4-1-1-1zm10 1.5V9h-4.5v6H16v-2h2v-1.5h-2v-1z"
|
||||
clip-path="url(#b)"
|
||||
id="path10"
|
||||
style="fill:#ffffff;fill-opacity:0.7019608" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
59
art/play_video.svg
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="play_video.svg">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1916"
|
||||
inkscape:window-height="1156"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="0.91525424"
|
||||
inkscape:cy="24"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="20"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M0 0h48v48H0z"
|
||||
fill="none"
|
||||
id="path4" />
|
||||
<path
|
||||
d="M20 33l12-9-12-9v18zm4-29C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16z"
|
||||
id="path6"
|
||||
style="fill:#ffffff;fill-opacity:0.7019608;opacity:1;stroke:none;stroke-opacity:0.38039216" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -11,42 +11,61 @@ resolutions = {
|
||||
}
|
||||
|
||||
images = {
|
||||
'conversations_baloon.svg' => ['ic_launcher', 48],
|
||||
'ic_launcher.svg' => ['ic_launcher', 48],
|
||||
'main_logo.svg' => ['main_logo', 200],
|
||||
'play_video.svg' => ['play_video', 128],
|
||||
'play_gif.svg' => ['play_gif', 128],
|
||||
'conversations_mono.svg' => ['ic_notification', 24],
|
||||
'ic_received_indicator.svg' => ['ic_received_indicator', 12],
|
||||
'ic_send_text_offline.svg' => ['ic_send_text_offline', 36],
|
||||
'ic_send_text_offline_white.svg' => ['ic_send_text_offline_white', 36],
|
||||
'ic_send_text_online.svg' => ['ic_send_text_online', 36],
|
||||
'ic_send_text_away.svg' => ['ic_send_text_away', 36],
|
||||
'ic_send_text_dnd.svg' => ['ic_send_text_dnd', 36],
|
||||
'ic_send_photo_online.svg' => ['ic_send_photo_online', 36],
|
||||
'ic_send_photo_offline.svg' => ['ic_send_photo_offline', 36],
|
||||
'ic_send_photo_offline_white.svg' => ['ic_send_photo_offline_white', 36],
|
||||
'ic_send_photo_away.svg' => ['ic_send_photo_away', 36],
|
||||
'ic_send_photo_dnd.svg' => ['ic_send_photo_dnd', 36],
|
||||
'ic_send_location_online.svg' => ['ic_send_location_online', 36],
|
||||
'ic_send_location_offline.svg' => ['ic_send_location_offline', 36],
|
||||
'ic_send_location_offline_white.svg' => ['ic_send_location_offline_white', 36],
|
||||
'ic_send_location_away.svg' => ['ic_send_location_away', 36],
|
||||
'ic_send_location_dnd.svg' => ['ic_send_location_dnd', 36],
|
||||
'ic_send_voice_online.svg' => ['ic_send_voice_online', 36],
|
||||
'ic_send_voice_offline.svg' => ['ic_send_voice_offline', 36],
|
||||
'ic_send_voice_offline_white.svg' => ['ic_send_voice_offline_white', 36],
|
||||
'ic_send_voice_away.svg' => ['ic_send_voice_away', 36],
|
||||
'ic_send_voice_dnd.svg' => ['ic_send_voice_dnd', 36],
|
||||
'ic_send_cancel_online.svg' => ['ic_send_cancel_online', 36],
|
||||
'ic_send_cancel_offline.svg' => ['ic_send_cancel_offline', 36],
|
||||
'ic_send_cancel_offline_white.svg' => ['ic_send_cancel_offline_white', 36],
|
||||
'ic_send_cancel_away.svg' => ['ic_send_cancel_away', 36],
|
||||
'ic_send_cancel_dnd.svg' => ['ic_send_cancel_dnd', 36],
|
||||
'ic_send_picture_online.svg' => ['ic_send_picture_online', 36],
|
||||
'ic_send_picture_offline.svg' => ['ic_send_picture_offline', 36],
|
||||
'ic_send_picture_offline_white.svg' => ['ic_send_picture_offline_white', 36],
|
||||
'ic_send_picture_away.svg' => ['ic_send_picture_away', 36],
|
||||
'ic_send_picture_dnd.svg' => ['ic_send_picture_dnd', 36],
|
||||
'ic_notifications_none_white80.svg' => ['ic_notifications_none_white80', 24],
|
||||
'ic_notifications_off_white80.svg' => ['ic_notifications_off_white80', 24],
|
||||
'ic_notifications_paused_white80.svg' => ['ic_notifications_paused_white80', 24],
|
||||
'ic_notifications_white80.svg' => ['ic_notifications_white80', 24],
|
||||
'ic_verified_fingerprint.svg' => ['ic_verified_fingerprint', 36],
|
||||
'md_switch_thumb_disable.svg' => ['switch_thumb_disable', 48],
|
||||
'md_switch_thumb_off_normal.svg' => ['switch_thumb_off_normal', 48],
|
||||
'md_switch_thumb_off_pressed.svg' => ['switch_thumb_off_pressed', 48],
|
||||
'md_switch_thumb_on_normal.svg' => ['switch_thumb_on_normal', 48],
|
||||
'md_switch_thumb_on_pressed.svg' => ['switch_thumb_on_pressed', 48],
|
||||
'message_bubble_received.svg' => ['message_bubble_received.9', 0],
|
||||
'message_bubble_received_grey.svg' => ['message_bubble_received_grey.9', 0],
|
||||
'message_bubble_received_dark.svg' => ['message_bubble_received_dark.9', 0],
|
||||
'message_bubble_received_warning.svg' => ['message_bubble_received_warning.9', 0],
|
||||
'message_bubble_received_white.svg' => ['message_bubble_received_white.9', 0],
|
||||
'message_bubble_sent.svg' => ['message_bubble_sent.9', 0],
|
||||
'message_bubble_sent_grey.svg' => ['message_bubble_sent_grey.9', 0],
|
||||
'date_bubble_white.svg' => ['date_bubble_white.9', 0],
|
||||
'date_bubble_grey.svg' => ['date_bubble_grey.9', 0],
|
||||
}
|
||||
|
||||
# Executable paths for Mac OSX
|
||||
|
188
build.gradle
@ -1,114 +1,122 @@
|
||||
// Top-level build file where you can add configuration options common to all
|
||||
// sub-projects/modules.
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.3.1'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configurations {
|
||||
playstoreCompile
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':libs:openpgp-api-lib')
|
||||
compile project(':libs:MemorizingTrustManager')
|
||||
compile 'com.android.support:support-v13:23.0.0'
|
||||
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
||||
compile 'org.jitsi:org.otr4j:0.22'
|
||||
compile 'org.gnu.inet:libidn:1.15'
|
||||
compile 'com.google.zxing:core:3.2.1'
|
||||
compile 'com.google.zxing:android-integration:3.2.1'
|
||||
compile 'de.measite.minidns:minidns:0.1.7'
|
||||
compile 'de.timroes.android:EnhancedListView:0.3.4'
|
||||
compile 'me.leolin:ShortcutBadger:1.1.3@aar'
|
||||
compile 'com.kyleduo.switchbutton:library:1.2.8'
|
||||
compile 'org.whispersystems:axolotl-android:1.3.4'
|
||||
compile 'com.makeramen:roundedimageview:2.2.0'
|
||||
compile project(':libs:MemorizingTrustManager')
|
||||
playstoreCompile 'com.google.android.gms:play-services-gcm:11.0.4'
|
||||
compile 'org.sufficientlysecure:openpgp-api:10.0'
|
||||
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
|
||||
compile 'com.android.support:support-v13:25.3.1'
|
||||
compile 'com.android.support:appcompat-v7:25.3.1'
|
||||
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
||||
compile 'org.bouncycastle:bcmail-jdk15on:1.52'
|
||||
compile 'org.jitsi:org.otr4j:0.22'
|
||||
compile 'org.gnu.inet:libidn:1.15'
|
||||
compile 'com.google.zxing:core:3.2.1'
|
||||
compile 'com.google.zxing:android-integration:3.2.1'
|
||||
compile 'de.measite.minidns:minidns-hla:0.2.2'
|
||||
compile 'de.timroes.android:EnhancedListView:0.3.4'
|
||||
compile 'me.leolin:ShortcutBadger:1.1.17@aar'
|
||||
compile 'com.kyleduo.switchbutton:library:1.2.8'
|
||||
compile 'org.whispersystems:signal-protocol-java:2.6.2'
|
||||
compile 'com.makeramen:roundedimageview:2.3.0'
|
||||
compile "com.wefika:flowlayout:0.4.1"
|
||||
compile 'net.ypresto.androidtranscoder:android-transcoder:0.2.0'
|
||||
compile ('com.vdurmont:emoji-java:3.3.0') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
travisBuild = System.getenv("TRAVIS") == "true"
|
||||
preDexEnabled = System.getProperty("pre-dex", "true")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.0"
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "26.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 21
|
||||
versionCode 91
|
||||
versionName "1.6.6"
|
||||
project.ext.set(archivesBaseName, archivesBaseName + "-" + versionName);
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 25
|
||||
versionCode 229
|
||||
versionName "1.20.0-beta"
|
||||
archivesBaseName += "-$versionName"
|
||||
applicationId "eu.siacs.conversations"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
dexOptions {
|
||||
// Skip pre-dexing when running on Travis CI or when disabled via -Dpre-dex=false.
|
||||
preDexLibraries = preDexEnabled && !travisBuild
|
||||
jumboMode true
|
||||
}
|
||||
|
||||
//
|
||||
// To sign release builds, create the file `gradle.properties` in
|
||||
// $HOME/.gradle or in your project directory with this content:
|
||||
//
|
||||
// mStoreFile=/path/to/key.store
|
||||
// mStorePassword=xxx
|
||||
// mKeyAlias=alias
|
||||
// mKeyPassword=xxx
|
||||
//
|
||||
if (project.hasProperty('mStoreFile') &&
|
||||
project.hasProperty('mStorePassword') &&
|
||||
project.hasProperty('mKeyAlias') &&
|
||||
project.hasProperty('mKeyPassword')) {
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file(mStoreFile)
|
||||
storePassword mStorePassword
|
||||
keyAlias mKeyAlias
|
||||
keyPassword mKeyPassword
|
||||
}
|
||||
}
|
||||
buildTypes.release.signingConfig = signingConfigs.release
|
||||
} else {
|
||||
buildTypes.release.signingConfig = null
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
if (variant.name.equals('release')) {
|
||||
variant.outputs.each { output ->
|
||||
if (output.zipAlign != null) {
|
||||
output.zipAlign.outputFile = new File(output.outputFile.parent, rootProject.name + "-${variant.versionName}.apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
productFlavors {
|
||||
playstore
|
||||
free
|
||||
}
|
||||
if (project.hasProperty('mStoreFile') &&
|
||||
project.hasProperty('mStorePassword') &&
|
||||
project.hasProperty('mKeyAlias') &&
|
||||
project.hasProperty('mKeyPassword')) {
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file(mStoreFile)
|
||||
storePassword mStorePassword
|
||||
keyAlias mKeyAlias
|
||||
keyPassword mKeyPassword
|
||||
}
|
||||
}
|
||||
buildTypes.release.signingConfig = signingConfigs.release
|
||||
} else {
|
||||
buildTypes.release.signingConfig = null
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource'
|
||||
}
|
||||
lintOptions {
|
||||
disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
subprojects {
|
||||
|
||||
afterEvaluate {
|
||||
if (getPlugins().hasPlugin('android') ||
|
||||
getPlugins().hasPlugin('android-library')) {
|
||||
afterEvaluate {
|
||||
if (getPlugins().hasPlugin('android') ||
|
||||
getPlugins().hasPlugin('android-library')) {
|
||||
|
||||
configure(android.lintOptions) {
|
||||
disable 'AndroidGradlePluginVersion', 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
configure(android.lintOptions) {
|
||||
disable 'AndroidGradlePluginVersion', 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/BCKEY.DSA'
|
||||
exclude 'META-INF/BCKEY.SF'
|
||||
}
|
||||
}
|
||||
|
12
docs/XEPs.md
@ -2,13 +2,17 @@
|
||||
* XEP-0030: Service Discovery
|
||||
* XEP-0045: Multi-User Chat
|
||||
* XEP-0048: Bookmarks
|
||||
* XEP-0084: User Avatar
|
||||
* XEP-0085: Chat State Notifications
|
||||
* XEP-0092: Software Version
|
||||
* XEP-0115: Entity Capabilities
|
||||
* XEP-0163: Personal Eventing Protocol (avatars and nicks)
|
||||
* XEP-0166: Jingle (only used for file transfer)
|
||||
* XEP-0172: User Nickname
|
||||
* XEP-0184: Message Delivery Receipts (reply only)
|
||||
* XEP-0191: Blocking command
|
||||
* XEP-0198: Stream Management
|
||||
* XEP-0199: XMPP Ping
|
||||
* XEP-0234: Jingle File Transfer
|
||||
* XEP-0237: Roster Versioning
|
||||
* XEP-0245: The /me Command
|
||||
@ -16,7 +20,13 @@
|
||||
* XEP-0260: Jingle SOCKS5 Bytestreams Transport Method
|
||||
* XEP-0261: Jingle In-Band Bytestreams Transport Method
|
||||
* XEP-0280: Message Carbons
|
||||
* XEP-0308: Last Message Correction
|
||||
* XEP-0313: Message Archive Management
|
||||
* XEP-0319: Last User Interaction in Presence
|
||||
* XEP-0333: Chat Markers
|
||||
* XEP-0352: Client State Indication
|
||||
* XEP-0191: Blocking command
|
||||
* XEP-0357: Push Notifications
|
||||
* XEP-0363: HTTP File Upload
|
||||
* XEP-0368: SRV records for XMPP over TLS
|
||||
* XEP-0377: Spam Reporting
|
||||
* XEP-0384: OMEMO Encryption
|
||||
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,5 @@
|
||||
#Sat Nov 22 17:47:57 CET 2014
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
||||
|
@ -3,18 +3,18 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
classpath 'com.android.tools.build:gradle:2.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android-library'
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion "19.1"
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "26.0.1"
|
||||
defaultConfig {
|
||||
minSdkVersion 7
|
||||
targetSdkVersion 19
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 25
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
@ -0,0 +1,10 @@
|
||||
package de.duenndns.ssl;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
public interface DomainHostnameVerifier extends HostnameVerifier {
|
||||
|
||||
boolean verify(String domain, String hostname, SSLSession sslSession);
|
||||
|
||||
}
|
@ -28,22 +28,36 @@ package de.duenndns.ssl;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.*;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.text.SimpleDateFormat;
|
||||
@ -51,8 +65,10 @@ import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
@ -68,7 +84,15 @@ import javax.net.ssl.X509TrustManager;
|
||||
* <b>WARNING:</b> This only works if a dedicated thread is used for
|
||||
* opening sockets!
|
||||
*/
|
||||
public class MemorizingTrustManager implements X509TrustManager {
|
||||
public class MemorizingTrustManager {
|
||||
|
||||
|
||||
private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
|
||||
private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
|
||||
private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
|
||||
private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
|
||||
private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
|
||||
|
||||
final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
|
||||
final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
|
||||
final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
|
||||
@ -94,6 +118,7 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
private KeyStore appKeyStore;
|
||||
private X509TrustManager defaultTrustManager;
|
||||
private X509TrustManager appTrustManager;
|
||||
private String poshCacheDir;
|
||||
|
||||
/** Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager.
|
||||
*
|
||||
@ -149,28 +174,11 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
|
||||
keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
|
||||
|
||||
poshCacheDir = app.getFilesDir().getAbsolutePath()+"/posh_cache/";
|
||||
|
||||
appKeyStore = loadAppKeyStore();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a X509TrustManager list containing a new instance of
|
||||
* TrustManagerFactory.
|
||||
*
|
||||
* This function is meant for convenience only. You can use it
|
||||
* as follows to integrate TrustManagerFactory for HTTPS sockets:
|
||||
*
|
||||
* <pre>
|
||||
* SSLContext sc = SSLContext.getInstance("TLS");
|
||||
* sc.init(null, MemorizingTrustManager.getInstanceList(this),
|
||||
* new java.security.SecureRandom());
|
||||
* HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
* </pre>
|
||||
* @param c Activity or Service to show the Dialog / Notification
|
||||
*/
|
||||
public static X509TrustManager[] getInstanceList(Context c) {
|
||||
return new X509TrustManager[] { new MemorizingTrustManager(c) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds an Activity to the MTM for displaying the query dialog.
|
||||
@ -284,18 +292,11 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
*
|
||||
* @throws IllegalArgumentException if the defaultVerifier parameter is null
|
||||
*/
|
||||
public HostnameVerifier wrapHostnameVerifier(final HostnameVerifier defaultVerifier) {
|
||||
public DomainHostnameVerifier wrapHostnameVerifier(final HostnameVerifier defaultVerifier, final boolean interactive) {
|
||||
if (defaultVerifier == null)
|
||||
throw new IllegalArgumentException("The default verifier may not be null");
|
||||
|
||||
return new MemorizingHostnameVerifier(defaultVerifier);
|
||||
}
|
||||
|
||||
public HostnameVerifier wrapHostnameVerifierNonInteractive(final HostnameVerifier defaultVerifier) {
|
||||
if (defaultVerifier == null)
|
||||
throw new IllegalArgumentException("The default verifier may not be null");
|
||||
|
||||
return new NonInteractiveMemorizingHostnameVerifier(defaultVerifier);
|
||||
return new MemorizingHostnameVerifier(defaultVerifier, interactive);
|
||||
}
|
||||
|
||||
X509TrustManager getTrustManager(KeyStore ks) {
|
||||
@ -389,7 +390,7 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer, boolean interactive)
|
||||
public void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive)
|
||||
throws CertificateException
|
||||
{
|
||||
LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
|
||||
@ -419,6 +420,15 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
else
|
||||
defaultTrustManager.checkClientTrusted(chain, authType);
|
||||
} catch (CertificateException e) {
|
||||
boolean trustSystemCAs = !PreferenceManager.getDefaultSharedPreferences(master).getBoolean("dont_trust_system_cas", false);
|
||||
if (domain != null && isServer && trustSystemCAs && !isIp(domain)) {
|
||||
String hash = getBase64Hash(chain[0],"SHA-256");
|
||||
List<String> fingerprints = getPoshFingerprints(domain);
|
||||
if (hash != null && fingerprints.contains(hash)) {
|
||||
Log.d("mtm","trusted cert fingerprint of "+domain+" via posh");
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.printStackTrace();
|
||||
if (interactive) {
|
||||
interactCert(chain, authType, e);
|
||||
@ -429,20 +439,147 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException
|
||||
{
|
||||
checkCertTrusted(chain, authType, false,true);
|
||||
private List<String> getPoshFingerprints(String domain) {
|
||||
List<String> cached = getPoshFingerprintsFromCache(domain);
|
||||
if (cached == null) {
|
||||
return getPoshFingerprintsFromServer(domain);
|
||||
} else {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException
|
||||
{
|
||||
checkCertTrusted(chain, authType, true,true);
|
||||
private List<String> getPoshFingerprintsFromServer(String domain) {
|
||||
return getPoshFingerprintsFromServer(domain, "https://"+domain+"/.well-known/posh/xmpp-client.json",-1,true);
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers()
|
||||
{
|
||||
private List<String> getPoshFingerprintsFromServer(String domain, String url, int maxTtl, boolean followUrl) {
|
||||
Log.d("mtm","downloading json for "+domain+" from "+url);
|
||||
try {
|
||||
List<String> results = new ArrayList<>();
|
||||
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String inputLine;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
builder.append(inputLine);
|
||||
}
|
||||
JSONObject jsonObject = new JSONObject(builder.toString());
|
||||
in.close();
|
||||
int expires = jsonObject.getInt("expires");
|
||||
if (expires <= 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
if (maxTtl >= 0) {
|
||||
expires = Math.min(maxTtl,expires);
|
||||
}
|
||||
String redirect;
|
||||
try {
|
||||
redirect = jsonObject.getString("url");
|
||||
} catch (JSONException e) {
|
||||
redirect = null;
|
||||
}
|
||||
if (followUrl && redirect != null && redirect.toLowerCase().startsWith("https")) {
|
||||
return getPoshFingerprintsFromServer(domain, redirect, expires, false);
|
||||
}
|
||||
JSONArray fingerprints = jsonObject.getJSONArray("fingerprints");
|
||||
for(int i = 0; i < fingerprints.length(); i++) {
|
||||
JSONObject fingerprint = fingerprints.getJSONObject(i);
|
||||
String sha256 = fingerprint.getString("sha-256");
|
||||
if (sha256 != null) {
|
||||
results.add(sha256);
|
||||
}
|
||||
}
|
||||
writeFingerprintsToCache(domain, results,1000L * expires+System.currentTimeMillis());
|
||||
return results;
|
||||
} catch (Exception e) {
|
||||
Log.d("mtm","error fetching posh "+e.getMessage());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private File getPoshCacheFile(String domain) {
|
||||
return new File(poshCacheDir+domain+".json");
|
||||
}
|
||||
|
||||
private void writeFingerprintsToCache(String domain, List<String> results, long expires) {
|
||||
File file = getPoshCacheFile(domain);
|
||||
file.getParentFile().mkdirs();
|
||||
try {
|
||||
file.createNewFile();
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("expires",expires);
|
||||
jsonObject.put("fingerprints",new JSONArray(results));
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
outputStream.write(jsonObject.toString().getBytes());
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getPoshFingerprintsFromCache(String domain) {
|
||||
File file = getPoshCacheFile(domain);
|
||||
try {
|
||||
InputStream is = new FileInputStream(file);
|
||||
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
|
||||
|
||||
String line = buf.readLine();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
while(line != null){
|
||||
sb.append(line).append("\n");
|
||||
line = buf.readLine();
|
||||
}
|
||||
JSONObject jsonObject = new JSONObject(sb.toString());
|
||||
is.close();
|
||||
long expires = jsonObject.getLong("expires");
|
||||
long expiresIn = expires - System.currentTimeMillis();
|
||||
if (expiresIn < 0) {
|
||||
file.delete();
|
||||
return null;
|
||||
} else {
|
||||
Log.d("mtm","posh fingerprints expire in "+(expiresIn/1000)+"s");
|
||||
}
|
||||
List<String> result = new ArrayList<>();
|
||||
JSONArray jsonArray = jsonObject.getJSONArray("fingerprints");
|
||||
for(int i = 0; i < jsonArray.length(); ++i) {
|
||||
result.add(jsonArray.getString(i));
|
||||
}
|
||||
return result;
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
} catch (JSONException e) {
|
||||
file.delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isIp(final String server) {
|
||||
return server != null && (
|
||||
PATTERN_IPV4.matcher(server).matches()
|
||||
|| PATTERN_IPV6.matcher(server).matches()
|
||||
|| PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
|
||||
|| PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
|
||||
|| PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches());
|
||||
}
|
||||
|
||||
private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException {
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance(digest);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
md.update(certificate.getEncoded());
|
||||
return Base64.encodeToString(md.digest(),Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
private X509Certificate[] getAcceptedIssuers() {
|
||||
LOGGER.log(Level.FINE, "getAcceptedIssuers()");
|
||||
return defaultTrustManager.getAcceptedIssuers();
|
||||
}
|
||||
@ -553,22 +690,6 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
certDetails(si, cert);
|
||||
return si.toString();
|
||||
}
|
||||
|
||||
// We can use Notification.Builder once MTM's minSDK is >= 11
|
||||
@SuppressWarnings("deprecation")
|
||||
void startActivityNotification(Intent intent, int decisionId, String certName) {
|
||||
Notification n = new Notification(android.R.drawable.ic_lock_lock,
|
||||
master.getString(R.string.mtm_notification),
|
||||
System.currentTimeMillis());
|
||||
PendingIntent call = PendingIntent.getActivity(master, 0, intent, 0);
|
||||
n.setLatestEventInfo(master.getApplicationContext(),
|
||||
master.getString(R.string.mtm_notification),
|
||||
certName, call);
|
||||
n.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID + decisionId, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top-most entry of the activity stack.
|
||||
*
|
||||
@ -598,7 +719,6 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
getUI().startActivity(ni);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e);
|
||||
startActivityNotification(ni, myId, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -654,31 +774,41 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
}
|
||||
}
|
||||
|
||||
class MemorizingHostnameVerifier implements HostnameVerifier {
|
||||
private HostnameVerifier defaultVerifier;
|
||||
class MemorizingHostnameVerifier implements DomainHostnameVerifier {
|
||||
private final HostnameVerifier defaultVerifier;
|
||||
private final boolean interactive;
|
||||
|
||||
public MemorizingHostnameVerifier(HostnameVerifier wrapped) {
|
||||
defaultVerifier = wrapped;
|
||||
public MemorizingHostnameVerifier(HostnameVerifier wrapped, boolean interactive) {
|
||||
this.defaultVerifier = wrapped;
|
||||
this.interactive = interactive;
|
||||
}
|
||||
|
||||
protected boolean verify(String hostname, SSLSession session, boolean interactive) {
|
||||
LOGGER.log(Level.FINE, "hostname verifier for " + hostname + ", trying default verifier first");
|
||||
@Override
|
||||
public boolean verify(String domain, String hostname, SSLSession session) {
|
||||
LOGGER.log(Level.FINE, "hostname verifier for " + domain + ", trying default verifier first");
|
||||
// if the default verifier accepts the hostname, we are done
|
||||
if (defaultVerifier.verify(hostname, session)) {
|
||||
LOGGER.log(Level.FINE, "default verifier accepted " + hostname);
|
||||
return true;
|
||||
if (defaultVerifier instanceof DomainHostnameVerifier) {
|
||||
if (((DomainHostnameVerifier) defaultVerifier).verify(domain,hostname, session)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (defaultVerifier.verify(domain, session)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// otherwise, we check if the hostname is an alias for this cert in our keystore
|
||||
try {
|
||||
X509Certificate cert = (X509Certificate)session.getPeerCertificates()[0];
|
||||
//Log.d(TAG, "cert: " + cert);
|
||||
if (cert.equals(appKeyStore.getCertificate(hostname.toLowerCase(Locale.US)))) {
|
||||
LOGGER.log(Level.FINE, "certificate for " + hostname + " is in our keystore. accepting.");
|
||||
if (cert.equals(appKeyStore.getCertificate(domain.toLowerCase(Locale.US)))) {
|
||||
LOGGER.log(Level.FINE, "certificate for " + domain + " is in our keystore. accepting.");
|
||||
return true;
|
||||
} else {
|
||||
LOGGER.log(Level.FINE, "server " + hostname + " provided wrong certificate, asking user.");
|
||||
LOGGER.log(Level.FINE, "server " + domain + " provided wrong certificate, asking user.");
|
||||
if (interactive) {
|
||||
return interactHostname(cert, hostname);
|
||||
return interactHostname(cert, domain);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -688,42 +818,47 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return verify(hostname, session, true);
|
||||
}
|
||||
}
|
||||
|
||||
class NonInteractiveMemorizingHostnameVerifier extends MemorizingHostnameVerifier {
|
||||
|
||||
public NonInteractiveMemorizingHostnameVerifier(HostnameVerifier wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return verify(hostname, session, true);
|
||||
public boolean verify(String domain, SSLSession sslSession) {
|
||||
return verify(domain,null,sslSession);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public X509TrustManager getNonInteractive(String domain) {
|
||||
return new NonInteractiveMemorizingTrustManager(domain);
|
||||
}
|
||||
|
||||
public X509TrustManager getInteractive(String domain) {
|
||||
return new InteractiveMemorizingTrustManager(domain);
|
||||
}
|
||||
|
||||
public X509TrustManager getNonInteractive() {
|
||||
return new NonInteractiveMemorizingTrustManager();
|
||||
return new NonInteractiveMemorizingTrustManager(null);
|
||||
}
|
||||
|
||||
public X509TrustManager getInteractive() {
|
||||
return new InteractiveMemorizingTrustManager(null);
|
||||
}
|
||||
|
||||
private class NonInteractiveMemorizingTrustManager implements X509TrustManager {
|
||||
|
||||
private final String domain;
|
||||
|
||||
public NonInteractiveMemorizingTrustManager(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, false, false);
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, true, false);
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -732,4 +867,28 @@ public class MemorizingTrustManager implements X509TrustManager {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class InteractiveMemorizingTrustManager implements X509TrustManager {
|
||||
private final String domain;
|
||||
|
||||
public InteractiveMemorizingTrustManager(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return MemorizingTrustManager.this.getAcceptedIssuers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
libs/openpgp-api-lib/.gitignore
vendored
@ -1,29 +0,0 @@
|
||||
#Android specific
|
||||
bin
|
||||
gen
|
||||
obj
|
||||
lint.xml
|
||||
local.properties
|
||||
release.properties
|
||||
ant.properties
|
||||
*.class
|
||||
*.apk
|
||||
|
||||
#Gradle
|
||||
.gradle
|
||||
build
|
||||
gradle.properties
|
||||
|
||||
#Maven
|
||||
target
|
||||
pom.xml.*
|
||||
|
||||
#Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
.metadata
|
||||
|
||||
#IntelliJ IDEA
|
||||
.idea
|
||||
*.iml
|
@ -1,8 +0,0 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw
|
||||
|
||||
[open-keychain.api-strings]
|
||||
file_filter = res/values-<lang>/strings.xml
|
||||
source_file = res/values/strings.xml
|
||||
source_lang = en
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.openintents.openpgp"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="9"
|
||||
android:targetSdkVersion="19" />
|
||||
|
||||
<application/>
|
||||
|
||||
</manifest>
|
@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
@ -1,21 +0,0 @@
|
||||
# OpenPGP API library
|
||||
|
||||
The OpenPGP API provides methods to execute OpenPGP operations, such as sign, encrypt, decrypt, verify, and more without user interaction from background threads. This is done by connecting your client application to a remote service provided by [OpenKeychain](http://www.openkeychain.org) or other OpenPGP providers.
|
||||
|
||||
For usage instructions, please consult our Wiki page about the [OpenPGP API](https://github.com/open-keychain/open-keychain/wiki/OpenPGP-API).
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
@ -1,35 +0,0 @@
|
||||
// please leave this here, so this library builds on its own
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android-library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion '19.1'
|
||||
|
||||
// NOTE: We are using the old folder structure to also support Eclipse
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
}
|
||||
|
||||
// Do not abort build if lint finds errors
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="keychain-api-library" default="help">
|
||||
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contains the path to the SDK. It should *NOT* be checked into
|
||||
Version Control Systems. -->
|
||||
<property file="local.properties" />
|
||||
|
||||
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||
'android' tool to add properties to it.
|
||||
This is the place to change some Ant specific build properties.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
source.dir
|
||||
The name of the source directory. Default is 'src'.
|
||||
out.dir
|
||||
The name of the output directory. Default is 'bin'.
|
||||
|
||||
For other overridable properties, look at the beginning of the rules
|
||||
files in the SDK, at tools/ant/build.xml
|
||||
|
||||
Properties related to the SDK location or the project target should
|
||||
be updated using the 'android' tool with the 'update' action.
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems.
|
||||
|
||||
-->
|
||||
<property file="ant.properties" />
|
||||
|
||||
<!-- if sdk.dir was not set from one of the property file, then
|
||||
get it from the ANDROID_HOME env var.
|
||||
This must be done before we load project.properties since
|
||||
the proguard config can use sdk.dir -->
|
||||
<property environment="env" />
|
||||
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
|
||||
<isset property="env.ANDROID_HOME" />
|
||||
</condition>
|
||||
|
||||
<!-- The project.properties file is created and updated by the 'android'
|
||||
tool, as well as ADT.
|
||||
|
||||
This contains project specific properties such as project target, and library
|
||||
dependencies. Lower level build properties are stored in ant.properties
|
||||
(or in .classpath for Eclipse projects).
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems. -->
|
||||
<loadproperties srcFile="project.properties" />
|
||||
|
||||
<!-- quick check on sdk.dir -->
|
||||
<fail
|
||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
|
||||
unless="sdk.dir"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Import per project custom build rules if present at the root of the project.
|
||||
This is the place to put custom intermediary targets such as:
|
||||
-pre-build
|
||||
-pre-compile
|
||||
-post-compile (This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||
-post-package
|
||||
-post-build
|
||||
-pre-clean
|
||||
-->
|
||||
<import file="custom_rules.xml" optional="true" />
|
||||
|
||||
<!-- Import the actual build file.
|
||||
|
||||
To customize existing targets, there are two options:
|
||||
- Customize only one target:
|
||||
- copy/paste the target into this file, *before* the
|
||||
<import> task.
|
||||
- customize it to your needs.
|
||||
- Customize the whole content of build.xml
|
||||
- copy/paste the content of the rules files (minus the top node)
|
||||
into this file, replacing the <import> task.
|
||||
- customize to your needs.
|
||||
|
||||
***********************
|
||||
****** IMPORTANT ******
|
||||
***********************
|
||||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||
in order to avoid having your file be overridden by tools such as "android update project"
|
||||
-->
|
||||
<!-- version-tag: 1 -->
|
||||
<import file="${sdk.dir}/tools/ant/build.xml" />
|
||||
|
||||
</project>
|
@ -1,20 +0,0 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
@ -1,15 +0,0 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-19
|
||||
android.library=true
|
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.3 KiB |
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">Žádný</string>
|
||||
<string name="openpgp_install_openkeychain_via">Instalovat OpenKeychain pomocí %s</string>
|
||||
</resources>
|
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">Keine Auswahl</string>
|
||||
<string name="openpgp_install_openkeychain_via">Installiere OpenKeychain mit %s</string>
|
||||
</resources>
|
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">Ninguno</string>
|
||||
<string name="openpgp_install_openkeychain_via">Instalar OpenKeychain mediante %s</string>
|
||||
</resources>
|
@ -1,2 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
@ -1,2 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">Aucun</string>
|
||||
<string name="openpgp_install_openkeychain_via">Installer OpenKeychain par %s</string>
|
||||
</resources>
|
@ -1,2 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">Nessuno</string>
|
||||
<string name="openpgp_install_openkeychain_via">Installa OpenKeychain via %s</string>
|
||||
</resources>
|
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">無し</string>
|
||||
<string name="openpgp_install_openkeychain_via">%s 経由でOpenKeychainをインストール</string>
|
||||
</resources>
|
@ -1,2 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
@ -1,2 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
@ -1,2 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">Нет</string>
|
||||
<string name="openpgp_install_openkeychain_via">Установить OpenKeychain через %s</string>
|
||||
</resources>
|
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">Brez</string>
|
||||
<string name="openpgp_install_openkeychain_via">Namesti OpenKeychain prek %s</string>
|
||||
</resources>
|
@ -1,2 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="openpgp_list_preference_none">Жоден</string>
|
||||
<string name="openpgp_install_openkeychain_via">Встановити OpenKeychain через %s</string>
|
||||
</resources>
|
@ -1,2 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="openpgp_list_preference_none">None</string>
|
||||
<string name="openpgp_install_openkeychain_via">Install OpenKeychain via %s</string>
|
||||
|
||||
</resources>
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp;
|
||||
|
||||
interface IOpenPgpService {
|
||||
|
||||
// see OpenPgpApi for documentation
|
||||
Intent execute(in Intent data, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
|
||||
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Parcelable versioning has been copied from Dashclock Widget
|
||||
* https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java
|
||||
*/
|
||||
public class OpenPgpError implements Parcelable {
|
||||
/**
|
||||
* Since there might be a case where new versions of the client using the library getting
|
||||
* old versions of the protocol (and thus old versions of this class), we need a versioning
|
||||
* system for the parcels sent between the clients and the providers.
|
||||
*/
|
||||
public static final int PARCELABLE_VERSION = 1;
|
||||
|
||||
// possible values for errorId
|
||||
public static final int CLIENT_SIDE_ERROR = -1;
|
||||
public static final int GENERIC_ERROR = 0;
|
||||
public static final int INCOMPATIBLE_API_VERSIONS = 1;
|
||||
public static final int NO_OR_WRONG_PASSPHRASE = 2;
|
||||
public static final int NO_USER_IDS = 3;
|
||||
|
||||
int errorId;
|
||||
String message;
|
||||
|
||||
public OpenPgpError() {
|
||||
}
|
||||
|
||||
public OpenPgpError(int errorId, String message) {
|
||||
this.errorId = errorId;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public OpenPgpError(OpenPgpError b) {
|
||||
this.errorId = b.errorId;
|
||||
this.message = b.message;
|
||||
}
|
||||
|
||||
public int getErrorId() {
|
||||
return errorId;
|
||||
}
|
||||
|
||||
public void setErrorId(int errorId) {
|
||||
this.errorId = errorId;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
/**
|
||||
* NOTE: When adding fields in the process of updating this API, make sure to bump
|
||||
* {@link #PARCELABLE_VERSION}.
|
||||
*/
|
||||
dest.writeInt(PARCELABLE_VERSION);
|
||||
// Inject a placeholder that will store the parcel size from this point on
|
||||
// (not including the size itself).
|
||||
int sizePosition = dest.dataPosition();
|
||||
dest.writeInt(0);
|
||||
int startPosition = dest.dataPosition();
|
||||
// version 1
|
||||
dest.writeInt(errorId);
|
||||
dest.writeString(message);
|
||||
// Go back and write the size
|
||||
int parcelableSize = dest.dataPosition() - startPosition;
|
||||
dest.setDataPosition(sizePosition);
|
||||
dest.writeInt(parcelableSize);
|
||||
dest.setDataPosition(startPosition + parcelableSize);
|
||||
}
|
||||
|
||||
public static final Creator<OpenPgpError> CREATOR = new Creator<OpenPgpError>() {
|
||||
public OpenPgpError createFromParcel(final Parcel source) {
|
||||
int parcelableVersion = source.readInt();
|
||||
int parcelableSize = source.readInt();
|
||||
int startPosition = source.dataPosition();
|
||||
|
||||
OpenPgpError error = new OpenPgpError();
|
||||
error.errorId = source.readInt();
|
||||
error.message = source.readString();
|
||||
|
||||
// skip over all fields added in future versions of this parcel
|
||||
source.setDataPosition(startPosition + parcelableSize);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
public OpenPgpError[] newArray(final int size) {
|
||||
return new OpenPgpError[size];
|
||||
}
|
||||
};
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Parcelable versioning has been copied from Dashclock Widget
|
||||
* https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java
|
||||
*/
|
||||
public class OpenPgpMetadata implements Parcelable {
|
||||
/**
|
||||
* Since there might be a case where new versions of the client using the library getting
|
||||
* old versions of the protocol (and thus old versions of this class), we need a versioning
|
||||
* system for the parcels sent between the clients and the providers.
|
||||
*/
|
||||
public static final int PARCELABLE_VERSION = 1;
|
||||
|
||||
String filename;
|
||||
String mimeType;
|
||||
long modificationTime;
|
||||
long originalSize;
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public long getModificationTime() {
|
||||
return modificationTime;
|
||||
}
|
||||
|
||||
public long getOriginalSize() {
|
||||
return originalSize;
|
||||
}
|
||||
|
||||
public OpenPgpMetadata() {
|
||||
}
|
||||
|
||||
public OpenPgpMetadata(String filename, String mimeType, long modificationTime,
|
||||
long originalSize) {
|
||||
this.filename = filename;
|
||||
this.mimeType = mimeType;
|
||||
this.modificationTime = modificationTime;
|
||||
this.originalSize = originalSize;
|
||||
}
|
||||
|
||||
public OpenPgpMetadata(OpenPgpMetadata b) {
|
||||
this.filename = b.filename;
|
||||
this.mimeType = b.mimeType;
|
||||
this.modificationTime = b.modificationTime;
|
||||
this.originalSize = b.originalSize;
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
/**
|
||||
* NOTE: When adding fields in the process of updating this API, make sure to bump
|
||||
* {@link #PARCELABLE_VERSION}.
|
||||
*/
|
||||
dest.writeInt(PARCELABLE_VERSION);
|
||||
// Inject a placeholder that will store the parcel size from this point on
|
||||
// (not including the size itself).
|
||||
int sizePosition = dest.dataPosition();
|
||||
dest.writeInt(0);
|
||||
int startPosition = dest.dataPosition();
|
||||
// version 1
|
||||
dest.writeString(filename);
|
||||
dest.writeString(mimeType);
|
||||
dest.writeLong(modificationTime);
|
||||
dest.writeLong(originalSize);
|
||||
// Go back and write the size
|
||||
int parcelableSize = dest.dataPosition() - startPosition;
|
||||
dest.setDataPosition(sizePosition);
|
||||
dest.writeInt(parcelableSize);
|
||||
dest.setDataPosition(startPosition + parcelableSize);
|
||||
}
|
||||
|
||||
public static final Creator<OpenPgpMetadata> CREATOR = new Creator<OpenPgpMetadata>() {
|
||||
public OpenPgpMetadata createFromParcel(final Parcel source) {
|
||||
int parcelableVersion = source.readInt();
|
||||
int parcelableSize = source.readInt();
|
||||
int startPosition = source.dataPosition();
|
||||
|
||||
OpenPgpMetadata vr = new OpenPgpMetadata();
|
||||
vr.filename = source.readString();
|
||||
vr.mimeType = source.readString();
|
||||
vr.modificationTime = source.readLong();
|
||||
vr.originalSize = source.readLong();
|
||||
|
||||
// skip over all fields added in future versions of this parcel
|
||||
source.setDataPosition(startPosition + parcelableSize);
|
||||
|
||||
return vr;
|
||||
}
|
||||
|
||||
public OpenPgpMetadata[] newArray(final int size) {
|
||||
return new OpenPgpMetadata[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String out = "\nfilename: " + filename;
|
||||
out += "\nmimeType: " + mimeType;
|
||||
out += "\nmodificationTime: " + modificationTime;
|
||||
out += "\noriginalSize: " + originalSize;
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Parcelable versioning has been copied from Dashclock Widget
|
||||
* https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java
|
||||
*/
|
||||
public class OpenPgpSignatureResult implements Parcelable {
|
||||
/**
|
||||
* Since there might be a case where new versions of the client using the library getting
|
||||
* old versions of the protocol (and thus old versions of this class), we need a versioning
|
||||
* system for the parcels sent between the clients and the providers.
|
||||
*/
|
||||
public static final int PARCELABLE_VERSION = 2;
|
||||
|
||||
// generic error on signature verification
|
||||
public static final int SIGNATURE_ERROR = 0;
|
||||
// successfully verified signature, with certified key
|
||||
public static final int SIGNATURE_SUCCESS_CERTIFIED = 1;
|
||||
// no key was found for this signature verification
|
||||
public static final int SIGNATURE_KEY_MISSING = 2;
|
||||
// successfully verified signature, but with uncertified key
|
||||
public static final int SIGNATURE_SUCCESS_UNCERTIFIED = 3;
|
||||
// key has been revoked
|
||||
public static final int SIGNATURE_KEY_REVOKED = 4;
|
||||
// key is expired
|
||||
public static final int SIGNATURE_KEY_EXPIRED = 5;
|
||||
|
||||
int status;
|
||||
boolean signatureOnly;
|
||||
String primaryUserId;
|
||||
ArrayList<String> userIds;
|
||||
long keyId;
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public boolean isSignatureOnly() {
|
||||
return signatureOnly;
|
||||
}
|
||||
|
||||
public void setSignatureOnly(boolean signatureOnly) {
|
||||
this.signatureOnly = signatureOnly;
|
||||
}
|
||||
|
||||
public String getPrimaryUserId() {
|
||||
return primaryUserId;
|
||||
}
|
||||
|
||||
public void setPrimaryUserId(String primaryUserId) {
|
||||
this.primaryUserId = primaryUserId;
|
||||
}
|
||||
|
||||
public ArrayList<String> getUserIds() {
|
||||
return userIds;
|
||||
}
|
||||
|
||||
public void setUserIds(ArrayList<String> userIds) {
|
||||
this.userIds = userIds;
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public void setKeyId(long keyId) {
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult() {
|
||||
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult(int signatureStatus, String signatureUserId,
|
||||
boolean signatureOnly, long keyId, ArrayList<String> userIds) {
|
||||
this.status = signatureStatus;
|
||||
this.signatureOnly = signatureOnly;
|
||||
this.primaryUserId = signatureUserId;
|
||||
this.keyId = keyId;
|
||||
this.userIds = userIds;
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult(OpenPgpSignatureResult b) {
|
||||
this.status = b.status;
|
||||
this.primaryUserId = b.primaryUserId;
|
||||
this.signatureOnly = b.signatureOnly;
|
||||
this.keyId = b.keyId;
|
||||
this.userIds = b.userIds;
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
/**
|
||||
* NOTE: When adding fields in the process of updating this API, make sure to bump
|
||||
* {@link #PARCELABLE_VERSION}.
|
||||
*/
|
||||
dest.writeInt(PARCELABLE_VERSION);
|
||||
// Inject a placeholder that will store the parcel size from this point on
|
||||
// (not including the size itself).
|
||||
int sizePosition = dest.dataPosition();
|
||||
dest.writeInt(0);
|
||||
int startPosition = dest.dataPosition();
|
||||
// version 1
|
||||
dest.writeInt(status);
|
||||
dest.writeByte((byte) (signatureOnly ? 1 : 0));
|
||||
dest.writeString(primaryUserId);
|
||||
dest.writeLong(keyId);
|
||||
// version 2
|
||||
dest.writeStringList(userIds);
|
||||
// Go back and write the size
|
||||
int parcelableSize = dest.dataPosition() - startPosition;
|
||||
dest.setDataPosition(sizePosition);
|
||||
dest.writeInt(parcelableSize);
|
||||
dest.setDataPosition(startPosition + parcelableSize);
|
||||
}
|
||||
|
||||
public static final Creator<OpenPgpSignatureResult> CREATOR = new Creator<OpenPgpSignatureResult>() {
|
||||
public OpenPgpSignatureResult createFromParcel(final Parcel source) {
|
||||
int parcelableVersion = source.readInt();
|
||||
int parcelableSize = source.readInt();
|
||||
int startPosition = source.dataPosition();
|
||||
|
||||
OpenPgpSignatureResult vr = new OpenPgpSignatureResult();
|
||||
vr.status = source.readInt();
|
||||
vr.signatureOnly = source.readByte() == 1;
|
||||
vr.primaryUserId = source.readString();
|
||||
vr.keyId = source.readLong();
|
||||
vr.userIds = new ArrayList<String>();
|
||||
source.readStringList(vr.userIds);
|
||||
|
||||
// skip over all fields added in future versions of this parcel
|
||||
source.setDataPosition(startPosition + parcelableSize);
|
||||
|
||||
return vr;
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult[] newArray(final int size) {
|
||||
return new OpenPgpSignatureResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String out = "\nstatus: " + status;
|
||||
out += "\nprimaryUserId: " + primaryUserId;
|
||||
out += "\nuserIds: " + userIds;
|
||||
out += "\nsignatureOnly: " + signatureOnly;
|
||||
out += "\nkeyId: " + OpenPgpUtils.convertKeyIdToHex(keyId);
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
@ -1,306 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class OpenPgpApi {
|
||||
|
||||
public static final String TAG = "OpenPgp API";
|
||||
|
||||
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
|
||||
|
||||
/**
|
||||
* Version history
|
||||
* ---------------
|
||||
* <p/>
|
||||
* 3:
|
||||
* - first public stable version
|
||||
* <p/>
|
||||
* 4:
|
||||
* - No changes to existing methods -> backward compatible
|
||||
* - Introduction of ACTION_DECRYPT_METADATA, RESULT_METADATA, EXTRA_ORIGINAL_FILENAME, and OpenPgpMetadata parcel
|
||||
* - Introduction of internal NFC extras: EXTRA_NFC_SIGNED_HASH, EXTRA_NFC_SIG_CREATION_TIMESTAMP
|
||||
* 5:
|
||||
* - OpenPgpSignatureResult: new consts SIGNATURE_KEY_REVOKED and SIGNATURE_KEY_EXPIRED
|
||||
* - OpenPgpSignatureResult: ArrayList<String> userIds
|
||||
*/
|
||||
public static final int API_VERSION = 5;
|
||||
|
||||
/**
|
||||
* General extras
|
||||
* --------------
|
||||
*
|
||||
* required extras:
|
||||
* int EXTRA_API_VERSION (always required)
|
||||
*
|
||||
* returned extras:
|
||||
* int RESULT_CODE (RESULT_CODE_ERROR, RESULT_CODE_SUCCESS or RESULT_CODE_USER_INTERACTION_REQUIRED)
|
||||
* OpenPgpError RESULT_ERROR (if RESULT_CODE == RESULT_CODE_ERROR)
|
||||
* PendingIntent RESULT_INTENT (if RESULT_CODE == RESULT_CODE_USER_INTERACTION_REQUIRED)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sign only
|
||||
* <p/>
|
||||
* optional extras:
|
||||
* boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output)
|
||||
* String EXTRA_PASSPHRASE (key passphrase)
|
||||
*/
|
||||
public static final String ACTION_SIGN = "org.openintents.openpgp.action.SIGN";
|
||||
|
||||
/**
|
||||
* Encrypt
|
||||
* <p/>
|
||||
* required extras:
|
||||
* String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT)
|
||||
* or
|
||||
* long[] EXTRA_KEY_IDS
|
||||
* <p/>
|
||||
* optional extras:
|
||||
* boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output)
|
||||
* String EXTRA_PASSPHRASE (key passphrase)
|
||||
* String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata)
|
||||
*/
|
||||
public static final String ACTION_ENCRYPT = "org.openintents.openpgp.action.ENCRYPT";
|
||||
|
||||
/**
|
||||
* Sign and encrypt
|
||||
* <p/>
|
||||
* required extras:
|
||||
* String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT)
|
||||
* or
|
||||
* long[] EXTRA_KEY_IDS
|
||||
* <p/>
|
||||
* optional extras:
|
||||
* boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output)
|
||||
* String EXTRA_PASSPHRASE (key passphrase)
|
||||
* String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata)
|
||||
*/
|
||||
public static final String ACTION_SIGN_AND_ENCRYPT = "org.openintents.openpgp.action.SIGN_AND_ENCRYPT";
|
||||
|
||||
/**
|
||||
* Decrypts and verifies given input stream. This methods handles encrypted-only, signed-and-encrypted,
|
||||
* and also signed-only input.
|
||||
* <p/>
|
||||
* If OpenPgpSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_MISSING
|
||||
* in addition a PendingIntent is returned via RESULT_INTENT to download missing keys.
|
||||
* <p/>
|
||||
* optional extras:
|
||||
* boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output)
|
||||
* <p/>
|
||||
* returned extras:
|
||||
* OpenPgpSignatureResult RESULT_SIGNATURE
|
||||
* OpenPgpDecryptMetadata RESULT_METADATA
|
||||
*/
|
||||
public static final String ACTION_DECRYPT_VERIFY = "org.openintents.openpgp.action.DECRYPT_VERIFY";
|
||||
|
||||
/**
|
||||
* Decrypts the header of an encrypted file to retrieve metadata such as original filename.
|
||||
* <p/>
|
||||
* This does not decrypt the actual content of the file.
|
||||
* <p/>
|
||||
* returned extras:
|
||||
* OpenPgpDecryptMetadata RESULT_METADATA
|
||||
*/
|
||||
public static final String ACTION_DECRYPT_METADATA = "org.openintents.openpgp.action.DECRYPT_METADATA";
|
||||
|
||||
/**
|
||||
* Get key ids based on given user ids (=emails)
|
||||
* <p/>
|
||||
* required extras:
|
||||
* String[] EXTRA_USER_IDS
|
||||
* <p/>
|
||||
* returned extras:
|
||||
* long[] RESULT_KEY_IDS
|
||||
*/
|
||||
public static final String ACTION_GET_KEY_IDS = "org.openintents.openpgp.action.GET_KEY_IDS";
|
||||
|
||||
/**
|
||||
* This action returns RESULT_CODE_SUCCESS if the OpenPGP Provider already has the key
|
||||
* corresponding to the given key id in its database.
|
||||
* <p/>
|
||||
* It returns RESULT_CODE_USER_INTERACTION_REQUIRED if the Provider does not have the key.
|
||||
* The PendingIntent from RESULT_INTENT can be used to retrieve those from a keyserver.
|
||||
* <p/>
|
||||
* required extras:
|
||||
* long EXTRA_KEY_ID
|
||||
*/
|
||||
public static final String ACTION_GET_KEY = "org.openintents.openpgp.action.GET_KEY";
|
||||
|
||||
/* Intent extras */
|
||||
public static final String EXTRA_API_VERSION = "api_version";
|
||||
|
||||
public static final String EXTRA_ACCOUNT_NAME = "account_name";
|
||||
|
||||
// SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
|
||||
// request ASCII Armor for output
|
||||
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
|
||||
public static final String EXTRA_REQUEST_ASCII_ARMOR = "ascii_armor";
|
||||
|
||||
// ENCRYPT, SIGN_AND_ENCRYPT
|
||||
public static final String EXTRA_USER_IDS = "user_ids";
|
||||
public static final String EXTRA_KEY_IDS = "key_ids";
|
||||
// optional extras:
|
||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_ORIGINAL_FILENAME = "original_filename";
|
||||
|
||||
// internal NFC states
|
||||
public static final String EXTRA_NFC_SIGNED_HASH = "nfc_signed_hash";
|
||||
public static final String EXTRA_NFC_SIG_CREATION_TIMESTAMP = "nfc_sig_creation_timestamp";
|
||||
public static final String EXTRA_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key";
|
||||
|
||||
// GET_KEY
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String RESULT_KEY_IDS = "key_ids";
|
||||
|
||||
/* Service Intent returns */
|
||||
public static final String RESULT_CODE = "result_code";
|
||||
|
||||
// get actual error object from RESULT_ERROR
|
||||
public static final int RESULT_CODE_ERROR = 0;
|
||||
// success!
|
||||
public static final int RESULT_CODE_SUCCESS = 1;
|
||||
// get PendingIntent from RESULT_INTENT, start PendingIntent with startIntentSenderForResult,
|
||||
// and execute service method again in onActivityResult
|
||||
public static final int RESULT_CODE_USER_INTERACTION_REQUIRED = 2;
|
||||
|
||||
public static final String RESULT_ERROR = "error";
|
||||
public static final String RESULT_INTENT = "intent";
|
||||
|
||||
// DECRYPT_VERIFY
|
||||
public static final String RESULT_SIGNATURE = "signature";
|
||||
public static final String RESULT_METADATA = "metadata";
|
||||
|
||||
IOpenPgpService mService;
|
||||
Context mContext;
|
||||
|
||||
public OpenPgpApi(Context context, IOpenPgpService service) {
|
||||
this.mContext = context;
|
||||
this.mService = service;
|
||||
}
|
||||
|
||||
public interface IOpenPgpCallback {
|
||||
void onReturn(final Intent result);
|
||||
}
|
||||
|
||||
private class OpenPgpAsyncTask extends AsyncTask<Void, Integer, Intent> {
|
||||
Intent data;
|
||||
InputStream is;
|
||||
OutputStream os;
|
||||
IOpenPgpCallback callback;
|
||||
|
||||
private OpenPgpAsyncTask(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) {
|
||||
this.data = data;
|
||||
this.is = is;
|
||||
this.os = os;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent doInBackground(Void... unused) {
|
||||
return executeApi(data, is, os);
|
||||
}
|
||||
|
||||
protected void onPostExecute(Intent result) {
|
||||
callback.onReturn(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public void executeApiAsync(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) {
|
||||
OpenPgpAsyncTask task = new OpenPgpAsyncTask(data, is, os, callback);
|
||||
|
||||
// don't serialize async tasks!
|
||||
// http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
|
||||
} else {
|
||||
task.execute((Void[]) null);
|
||||
}
|
||||
}
|
||||
|
||||
public Intent executeApi(Intent data, InputStream is, OutputStream os) {
|
||||
try {
|
||||
data.putExtra(EXTRA_API_VERSION, OpenPgpApi.API_VERSION);
|
||||
|
||||
Intent result;
|
||||
|
||||
// pipe the input and output
|
||||
ParcelFileDescriptor input = null;
|
||||
if (is != null) {
|
||||
input = ParcelFileDescriptorUtil.pipeFrom(is,
|
||||
new ParcelFileDescriptorUtil.IThreadListener() {
|
||||
|
||||
@Override
|
||||
public void onThreadFinished(Thread thread) {
|
||||
//Log.d(OpenPgpApi.TAG, "Copy to service finished");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
ParcelFileDescriptor output = null;
|
||||
if (os != null) {
|
||||
output = ParcelFileDescriptorUtil.pipeTo(os,
|
||||
new ParcelFileDescriptorUtil.IThreadListener() {
|
||||
|
||||
@Override
|
||||
public void onThreadFinished(Thread thread) {
|
||||
//Log.d(OpenPgpApi.TAG, "Service finished writing!");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// blocks until result is ready
|
||||
result = mService.execute(data, input, output);
|
||||
// close() is required to halt the TransferThread
|
||||
if (output != null) {
|
||||
output.close();
|
||||
}
|
||||
// TODO: close input?
|
||||
|
||||
// set class loader to current context to allow unparcelling
|
||||
// of OpenPgpError and OpenPgpSignatureResult
|
||||
// http://stackoverflow.com/a/3806769
|
||||
result.setExtrasClassLoader(mContext.getClassLoader());
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
Log.e(OpenPgpApi.TAG, "Exception in executeApi call", e);
|
||||
Intent result = new Intent();
|
||||
result.putExtra(RESULT_CODE, RESULT_CODE_ERROR);
|
||||
result.putExtra(RESULT_ERROR,
|
||||
new OpenPgpError(OpenPgpError.CLIENT_SIDE_ERROR, e.getMessage()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,257 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp.util;
|
||||
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.TextView;
|
||||
import org.openintents.openpgp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Does not extend ListPreference, but is very similar to it!
|
||||
* http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/preference/ListPreference.java/?v=source
|
||||
*/
|
||||
public class OpenPgpListPreference extends DialogPreference {
|
||||
private static final String OPENKEYCHAIN_PACKAGE = "org.sufficientlysecure.keychain";
|
||||
private static final String MARKET_INTENT_URI_BASE = "market://details?id=%s";
|
||||
private static final Intent MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse(
|
||||
String.format(MARKET_INTENT_URI_BASE, OPENKEYCHAIN_PACKAGE)));
|
||||
|
||||
private ArrayList<OpenPgpProviderEntry> mLegacyList = new ArrayList<OpenPgpProviderEntry>();
|
||||
private ArrayList<OpenPgpProviderEntry> mList = new ArrayList<OpenPgpProviderEntry>();
|
||||
|
||||
private String mSelectedPackage;
|
||||
|
||||
public OpenPgpListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public OpenPgpListPreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method to add new entries for legacy applications
|
||||
*
|
||||
* @param packageName
|
||||
* @param simpleName
|
||||
* @param icon
|
||||
*/
|
||||
public void addLegacyProvider(int position, String packageName, String simpleName, Drawable icon) {
|
||||
mLegacyList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(Builder builder) {
|
||||
mList.clear();
|
||||
|
||||
// add "none"-entry
|
||||
mList.add(0, new OpenPgpProviderEntry("",
|
||||
getContext().getString(R.string.openpgp_list_preference_none),
|
||||
getContext().getResources().getDrawable(R.drawable.ic_action_cancel_launchersize)));
|
||||
|
||||
// add all additional (legacy) providers
|
||||
mList.addAll(mLegacyList);
|
||||
|
||||
// search for OpenPGP providers...
|
||||
ArrayList<OpenPgpProviderEntry> providerList = new ArrayList<OpenPgpProviderEntry>();
|
||||
Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT);
|
||||
List<ResolveInfo> resInfo = getContext().getPackageManager().queryIntentServices(intent, 0);
|
||||
if (!resInfo.isEmpty()) {
|
||||
for (ResolveInfo resolveInfo : resInfo) {
|
||||
if (resolveInfo.serviceInfo == null)
|
||||
continue;
|
||||
|
||||
String packageName = resolveInfo.serviceInfo.packageName;
|
||||
String simpleName = String.valueOf(resolveInfo.serviceInfo.loadLabel(getContext()
|
||||
.getPackageManager()));
|
||||
Drawable icon = resolveInfo.serviceInfo.loadIcon(getContext().getPackageManager());
|
||||
|
||||
providerList.add(new OpenPgpProviderEntry(packageName, simpleName, icon));
|
||||
}
|
||||
}
|
||||
|
||||
if (providerList.isEmpty()) {
|
||||
// add install links if provider list is empty
|
||||
resInfo = getContext().getPackageManager().queryIntentActivities
|
||||
(MARKET_INTENT, 0);
|
||||
for (ResolveInfo resolveInfo : resInfo) {
|
||||
Intent marketIntent = new Intent(MARKET_INTENT);
|
||||
marketIntent.setPackage(resolveInfo.activityInfo.packageName);
|
||||
Drawable icon = resolveInfo.activityInfo.loadIcon(getContext().getPackageManager());
|
||||
String marketName = String.valueOf(resolveInfo.activityInfo.applicationInfo
|
||||
.loadLabel(getContext().getPackageManager()));
|
||||
String simpleName = String.format(getContext().getString(R.string
|
||||
.openpgp_install_openkeychain_via), marketName);
|
||||
mList.add(new OpenPgpProviderEntry(OPENKEYCHAIN_PACKAGE, simpleName,
|
||||
icon, marketIntent));
|
||||
}
|
||||
} else {
|
||||
// add provider
|
||||
mList.addAll(providerList);
|
||||
}
|
||||
|
||||
// Init ArrayAdapter with OpenPGP Providers
|
||||
ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(),
|
||||
android.R.layout.select_dialog_singlechoice, android.R.id.text1, mList) {
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
// User super class to create the View
|
||||
View v = super.getView(position, convertView, parent);
|
||||
TextView tv = (TextView) v.findViewById(android.R.id.text1);
|
||||
|
||||
// Put the image on the TextView
|
||||
tv.setCompoundDrawablesWithIntrinsicBounds(mList.get(position).icon, null,
|
||||
null, null);
|
||||
|
||||
// Add margin between image and text (support various screen densities)
|
||||
int dp10 = (int) (10 * getContext().getResources().getDisplayMetrics().density + 0.5f);
|
||||
tv.setCompoundDrawablePadding(dp10);
|
||||
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
builder.setSingleChoiceItems(adapter, getIndexOfProviderList(getValue()),
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
OpenPgpProviderEntry entry = mList.get(which);
|
||||
|
||||
if (entry.intent != null) {
|
||||
/*
|
||||
* Intents are called as activity
|
||||
*
|
||||
* Current approach is to assume the user installed the app.
|
||||
* If he does not, the selected package is not valid.
|
||||
*
|
||||
* However applications should always consider this could happen,
|
||||
* as the user might remove the currently used OpenPGP app.
|
||||
*/
|
||||
getContext().startActivity(entry.intent);
|
||||
}
|
||||
|
||||
mSelectedPackage = entry.packageName;
|
||||
|
||||
/*
|
||||
* Clicking on an item simulates the positive button click, and dismisses
|
||||
* the dialog.
|
||||
*/
|
||||
OpenPgpListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
|
||||
* dialog instead of the user having to press 'Ok'.
|
||||
*/
|
||||
builder.setPositiveButton(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
|
||||
if (positiveResult && (mSelectedPackage != null)) {
|
||||
if (callChangeListener(mSelectedPackage)) {
|
||||
setValue(mSelectedPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getIndexOfProviderList(String packageName) {
|
||||
for (OpenPgpProviderEntry app : mList) {
|
||||
if (app.packageName.equals(packageName)) {
|
||||
return mList.indexOf(app);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void setValue(String packageName) {
|
||||
mSelectedPackage = packageName;
|
||||
persistString(packageName);
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return mSelectedPackage;
|
||||
}
|
||||
|
||||
public String getEntry() {
|
||||
return getEntryByValue(mSelectedPackage);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
||||
return a.getString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
|
||||
setValue(restoreValue ? getPersistedString(mSelectedPackage) : (String) defaultValue);
|
||||
}
|
||||
|
||||
public String getEntryByValue(String packageName) {
|
||||
for (OpenPgpProviderEntry app : mList) {
|
||||
if (app.packageName.equals(packageName)) {
|
||||
return app.simpleName;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class OpenPgpProviderEntry {
|
||||
private String packageName;
|
||||
private String simpleName;
|
||||
private Drawable icon;
|
||||
private Intent intent;
|
||||
|
||||
public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon) {
|
||||
this.packageName = packageName;
|
||||
this.simpleName = simpleName;
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon, Intent intent) {
|
||||
this(packageName, simpleName, icon);
|
||||
this.intent = intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return simpleName;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp.util;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
|
||||
public class OpenPgpServiceConnection {
|
||||
|
||||
// callback interface
|
||||
public interface OnBound {
|
||||
public void onBound(IOpenPgpService service);
|
||||
|
||||
public void onError(Exception e);
|
||||
}
|
||||
|
||||
private Context mApplicationContext;
|
||||
|
||||
private IOpenPgpService mService;
|
||||
private String mProviderPackageName;
|
||||
|
||||
private OnBound mOnBoundListener;
|
||||
|
||||
/**
|
||||
* Create new connection
|
||||
*
|
||||
* @param context
|
||||
* @param providerPackageName specify package name of OpenPGP provider,
|
||||
* e.g., "org.sufficientlysecure.keychain"
|
||||
*/
|
||||
public OpenPgpServiceConnection(Context context, String providerPackageName) {
|
||||
this.mApplicationContext = context.getApplicationContext();
|
||||
this.mProviderPackageName = providerPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new connection with callback
|
||||
*
|
||||
* @param context
|
||||
* @param providerPackageName specify package name of OpenPGP provider,
|
||||
* e.g., "org.sufficientlysecure.keychain"
|
||||
* @param onBoundListener callback, executed when connection to service has been established
|
||||
*/
|
||||
public OpenPgpServiceConnection(Context context, String providerPackageName,
|
||||
OnBound onBoundListener) {
|
||||
this(context, providerPackageName);
|
||||
this.mOnBoundListener = onBoundListener;
|
||||
}
|
||||
|
||||
public IOpenPgpService getService() {
|
||||
return mService;
|
||||
}
|
||||
|
||||
public boolean isBound() {
|
||||
return (mService != null);
|
||||
}
|
||||
|
||||
private ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mService = IOpenPgpService.Stub.asInterface(service);
|
||||
if (mOnBoundListener != null) {
|
||||
mOnBoundListener.onBound(mService);
|
||||
}
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mService = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If not already bound, bind to service!
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public void bindToService() {
|
||||
// if not already bound...
|
||||
if (mService == null) {
|
||||
try {
|
||||
Intent serviceIntent = new Intent(OpenPgpApi.SERVICE_INTENT);
|
||||
// NOTE: setPackage is very important to restrict the intent to this provider only!
|
||||
serviceIntent.setPackage(mProviderPackageName);
|
||||
boolean connect = mApplicationContext.bindService(serviceIntent, mServiceConnection,
|
||||
Context.BIND_AUTO_CREATE);
|
||||
if (!connect) {
|
||||
throw new Exception("bindService() returned false!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (mOnBoundListener != null) {
|
||||
mOnBoundListener.onError(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// already bound, but also inform client about it with callback
|
||||
if (mOnBoundListener != null) {
|
||||
mOnBoundListener.onBound(mService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unbindFromService() {
|
||||
mApplicationContext.unbindService(mServiceConnection);
|
||||
}
|
||||
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
|
||||
public class OpenPgpUtils {
|
||||
|
||||
public static final Pattern PGP_MESSAGE = Pattern.compile(
|
||||
".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
|
||||
Pattern.DOTALL);
|
||||
|
||||
public static final Pattern PGP_SIGNED_MESSAGE = Pattern.compile(
|
||||
".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
|
||||
Pattern.DOTALL);
|
||||
|
||||
public static final int PARSE_RESULT_NO_PGP = -1;
|
||||
public static final int PARSE_RESULT_MESSAGE = 0;
|
||||
public static final int PARSE_RESULT_SIGNED_MESSAGE = 1;
|
||||
|
||||
public static int parseMessage(String message) {
|
||||
Matcher matcherSigned = PGP_SIGNED_MESSAGE.matcher(message);
|
||||
Matcher matcherMessage = PGP_MESSAGE.matcher(message);
|
||||
|
||||
if (matcherMessage.matches()) {
|
||||
return PARSE_RESULT_MESSAGE;
|
||||
} else if (matcherSigned.matches()) {
|
||||
return PARSE_RESULT_SIGNED_MESSAGE;
|
||||
} else {
|
||||
return PARSE_RESULT_NO_PGP;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isAvailable(Context context) {
|
||||
Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT);
|
||||
List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0);
|
||||
if (!resInfo.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertKeyIdToHex(long keyId) {
|
||||
return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId);
|
||||
}
|
||||
|
||||
private static String convertKeyIdToHex32bit(long keyId) {
|
||||
String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.ENGLISH);
|
||||
while (hexString.length() < 8) {
|
||||
hexString = "0" + hexString;
|
||||
}
|
||||
return hexString;
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 2013 Florian Schmaus <flo@geekplace.eu>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp.util;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Partially based on <a href="http://stackoverflow.com/questions/18212152/">Stackoverflow: Transfer InputStream to another Service (across process boundaries)</a>
|
||||
**/
|
||||
public class ParcelFileDescriptorUtil {
|
||||
|
||||
public interface IThreadListener {
|
||||
void onThreadFinished(final Thread thread);
|
||||
}
|
||||
|
||||
public static ParcelFileDescriptor pipeFrom(InputStream inputStream, IThreadListener listener)
|
||||
throws IOException {
|
||||
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
ParcelFileDescriptor readSide = pipe[0];
|
||||
ParcelFileDescriptor writeSide = pipe[1];
|
||||
|
||||
// start the transfer thread
|
||||
new TransferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writeSide),
|
||||
listener)
|
||||
.start();
|
||||
|
||||
return readSide;
|
||||
}
|
||||
|
||||
public static ParcelFileDescriptor pipeTo(OutputStream outputStream, IThreadListener listener)
|
||||
throws IOException {
|
||||
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
ParcelFileDescriptor readSide = pipe[0];
|
||||
ParcelFileDescriptor writeSide = pipe[1];
|
||||
|
||||
// start the transfer thread
|
||||
new TransferThread(new ParcelFileDescriptor.AutoCloseInputStream(readSide), outputStream,
|
||||
listener)
|
||||
.start();
|
||||
|
||||
return writeSide;
|
||||
}
|
||||
|
||||
static class TransferThread extends Thread {
|
||||
final InputStream mIn;
|
||||
final OutputStream mOut;
|
||||
final IThreadListener mListener;
|
||||
|
||||
TransferThread(InputStream in, OutputStream out, IThreadListener listener) {
|
||||
super("ParcelFileDescriptor Transfer Thread");
|
||||
mIn = in;
|
||||
mOut = out;
|
||||
mListener = listener;
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
|
||||
try {
|
||||
while ((len = mIn.read(buf)) > 0) {
|
||||
mOut.write(buf, 0, len);
|
||||
}
|
||||
mOut.flush(); // just to be safe
|
||||
} catch (IOException e) {
|
||||
//Log.e(OpenPgpApi.TAG, "TransferThread" + getId() + ": writing failed", e);
|
||||
} finally {
|
||||
try {
|
||||
mIn.close();
|
||||
} catch (IOException e) {
|
||||
//Log.e(OpenPgpApi.TAG, "TransferThread" + getId(), e);
|
||||
}
|
||||
try {
|
||||
mOut.close();
|
||||
} catch (IOException e) {
|
||||
//Log.e(OpenPgpApi.TAG, "TransferThread" + getId(), e);
|
||||
}
|
||||
}
|
||||
if (mListener != null) {
|
||||
//Log.d(OpenPgpApi.TAG, "TransferThread " + getId() + " finished!");
|
||||
mListener.onThreadFinished(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
@ -1,27 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /home/sam/android-sdk-linux/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
-keep class * extends java.util.ListResourceBundle {
|
||||
protected Object[][] getContents();
|
||||
}
|
||||
|
||||
-keepnames class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
BIN
screenshots.png
Before Width: | Height: | Size: 1011 KiB After Width: | Height: | Size: 195 KiB |
@ -1,4 +1,3 @@
|
||||
include ':libs:MemorizingTrustManager'
|
||||
include ':libs:openpgp-api-lib'
|
||||
|
||||
rootProject.name = 'Conversations'
|
||||
|
@ -0,0 +1,28 @@
|
||||
package eu.siacs.conversations.services;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
|
||||
public class PushManagementService {
|
||||
|
||||
protected final XmppConnectionService mXmppConnectionService;
|
||||
|
||||
public PushManagementService(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public void registerPushTokenOnServer(Account account) {
|
||||
//stub implementation. only affects playstore flavor
|
||||
}
|
||||
|
||||
public boolean available(Account account) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isStub() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean availableAndUseful(Account account) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
package="eu.siacs.conversations"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="eu.siacs.conversations">
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
@ -14,22 +13,34 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_PHONE_STATE"
|
||||
tools:node="remove" />
|
||||
|
||||
<uses-sdk tools:overrideLibrary="net.ypresto.androidtranscoder" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:networkSecurityConfig="@xml/network_security_configuration"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/ConversationsTheme"
|
||||
tools:replace="android:label" >
|
||||
tools:replace="android:label">
|
||||
|
||||
<meta-data android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc" />
|
||||
|
||||
<service android:name=".services.XmppConnectionService" />
|
||||
|
||||
<receiver android:name=".services.EventReceiver" >
|
||||
<receiver android:name=".services.EventReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
|
||||
<action android:name="android.media.RINGER_MODE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
@ -37,7 +48,9 @@
|
||||
android:name=".ui.ConversationActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="stateHidden" >
|
||||
android:minWidth="300dp"
|
||||
android:minHeight="300dp"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@ -46,8 +59,8 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.StartConversationActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/title_activity_start_conversation" >
|
||||
android:label="@string/title_activity_start_conversation"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SENDTO" />
|
||||
|
||||
@ -71,7 +84,33 @@
|
||||
|
||||
<data android:scheme="xmpp" />
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="conversations.im" />
|
||||
<data android:pathPrefix="/i/" />
|
||||
<data android:pathPrefix="/j/" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.WelcomeActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"/>
|
||||
<activity
|
||||
android:name=".ui.MagicCreateActivity"
|
||||
android:label="@string/create_account"
|
||||
android:launchMode="singleTask"/>
|
||||
<activity
|
||||
android:name=".ui.SetPresenceActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/change_presence"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.SettingsActivity"
|
||||
android:label="@string/title_activity_settings" />
|
||||
@ -81,15 +120,16 @@
|
||||
<activity
|
||||
android:name=".ui.BlocklistActivity"
|
||||
android:label="@string/title_activity_block_list" />
|
||||
<activity
|
||||
android:name=".ui.ChangePasswordActivity"
|
||||
android:label="@string/change_password_on_server" />
|
||||
<activity
|
||||
android:name=".ui.ChangePasswordActivity"
|
||||
android:label="@string/change_password_on_server" />
|
||||
<activity
|
||||
android:name=".ui.ManageAccountActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/title_activity_manage_accounts" />
|
||||
android:label="@string/title_activity_manage_accounts"
|
||||
android:launchMode="singleTask" />
|
||||
<activity
|
||||
android:name=".ui.EditAccountActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.ConferenceDetailsActivity"
|
||||
@ -109,7 +149,7 @@
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".ui.ShareWithActivity"
|
||||
android:label="@string/app_name" >
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
@ -131,23 +171,54 @@
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.service.chooser.chooser_target_service"
|
||||
android:value=".services.ContactChooserTargetService" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.TrustKeysActivity"
|
||||
android:label="@string/trust_omemo_fingerprints"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"/>
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
<activity
|
||||
android:name="de.duenndns.ssl.MemorizingActivity"
|
||||
android:theme="@style/ConversationsTheme"
|
||||
tools:replace="android:theme"/>
|
||||
tools:replace="android:theme" />
|
||||
<activity
|
||||
android:name=".ui.AboutActivity"
|
||||
android:label="@string/title_activity_about"
|
||||
android:parentActivityName=".ui.SettingsActivity" >
|
||||
android:parentActivityName=".ui.SettingsActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="eu.siacs.conversations.ui.SettingsActivity" />
|
||||
</activity>
|
||||
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
|
||||
|
||||
<service android:name=".services.ExportLogsService" />
|
||||
<service
|
||||
android:name=".services.ContactChooserTargetService"
|
||||
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.chooser.ChooserTargetService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.files"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
<provider
|
||||
android:authorities="${applicationId}.barcodes"
|
||||
android:name=".services.BarcodeProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"/>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -6,19 +6,59 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||
|
||||
public final class Config {
|
||||
|
||||
|
||||
private static final int UNENCRYPTED = 1;
|
||||
private static final int OPENPGP = 2;
|
||||
private static final int OTR = 4;
|
||||
private static final int OMEMO = 8;
|
||||
|
||||
private static final int ENCRYPTION_MASK = UNENCRYPTED | OPENPGP | OTR | OMEMO;
|
||||
|
||||
public static boolean supportUnencrypted() {
|
||||
return (ENCRYPTION_MASK & UNENCRYPTED) != 0;
|
||||
}
|
||||
|
||||
public static boolean supportOpenPgp() {
|
||||
return (ENCRYPTION_MASK & OPENPGP) != 0;
|
||||
}
|
||||
|
||||
public static boolean supportOtr() {
|
||||
return (ENCRYPTION_MASK & OTR) != 0;
|
||||
}
|
||||
|
||||
public static boolean supportOmemo() {
|
||||
return (ENCRYPTION_MASK & OMEMO) != 0;
|
||||
}
|
||||
|
||||
public static boolean multipleEncryptionChoices() {
|
||||
return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0;
|
||||
}
|
||||
|
||||
public static final String LOGTAG = "conversations";
|
||||
|
||||
public static final String BUG_REPORTS = "bugs@conversations.im";
|
||||
|
||||
|
||||
public static final String DOMAIN_LOCK = null; //only allow account creation for this domain
|
||||
public static final String MAGIC_CREATE_DOMAIN = "conversations.im";
|
||||
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
|
||||
public static final boolean HIDE_PGP_IN_UI = false; //some more consumer focused clients might want to disable OpenPGP
|
||||
|
||||
public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true
|
||||
public static final boolean FORCE_ORBOT = false; // always use TOR
|
||||
public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false;
|
||||
public static final boolean SHOW_CONNECTED_ACCOUNTS = false; //show number of connected accounts in foreground notification
|
||||
public static final boolean SHOW_DISABLE_FOREGROUND = false; //if set to true the foreground notification has a button to disable it
|
||||
|
||||
public static final boolean ALWAYS_NOTIFY_BY_DEFAULT = false;
|
||||
|
||||
public static final int PING_MAX_INTERVAL = 300;
|
||||
public static final int IDLE_PING_INTERVAL = 600; //540 is minimum according to docs;
|
||||
public static final int PING_MIN_INTERVAL = 30;
|
||||
public static final int PING_TIMEOUT = 10;
|
||||
public static final int LOW_PING_TIMEOUT = 1; // used after push received
|
||||
public static final int PING_TIMEOUT = 15;
|
||||
public static final int SOCKET_TIMEOUT = 15;
|
||||
public static final int CONNECT_TIMEOUT = 90;
|
||||
public static final int CARBON_GRACE_PERIOD = 90;
|
||||
public static final int CONNECT_DISCO_TIMEOUT = 20;
|
||||
public static final int MINI_GRACE_PERIOD = 750;
|
||||
|
||||
public static final int AVATAR_SIZE = 192;
|
||||
@ -27,6 +67,7 @@ public final class Config {
|
||||
public static final int IMAGE_SIZE = 1920;
|
||||
public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG;
|
||||
public static final int IMAGE_QUALITY = 75;
|
||||
public static final int IMAGE_MAX_SIZE = 524288; //512KiB
|
||||
|
||||
public static final int MESSAGE_MERGE_WINDOW = 20;
|
||||
|
||||
@ -35,24 +76,45 @@ public final class Config {
|
||||
|
||||
public static final int REFRESH_UI_INTERVAL = 500;
|
||||
|
||||
public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb
|
||||
public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096;
|
||||
public static final int MAX_STORAGE_MESSAGE_CHARS = 1024 * 1024 * 1024;
|
||||
|
||||
public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
public static final long OMEMO_AUTO_EXPIRY = 7 * MILLISECONDS_IN_DAY;
|
||||
public static final boolean REMOVE_BROKEN_DEVICES = false;
|
||||
public static final boolean OMEMO_PADDING = false;
|
||||
public static boolean PUT_AUTH_TAG_INTO_KEY = true;
|
||||
|
||||
|
||||
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
|
||||
public static final boolean DISABLE_HTTP_UPLOAD = false;
|
||||
public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance
|
||||
public static final boolean EXTENDED_SM_LOGGING = true; // log stanza counts
|
||||
public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts
|
||||
public static final boolean BACKGROUND_STANZA_LOGGING = false; //log all stanzas that were received while the app is in background
|
||||
public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption
|
||||
|
||||
public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false;
|
||||
|
||||
public static final boolean REPORT_WRONG_FILESIZE_IN_OTR_JINGLE = true;
|
||||
public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys
|
||||
|
||||
public static final boolean SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON = false;
|
||||
public static final boolean ONLY_INTERNAL_STORAGE = false; //use internal storage instead of sdcard to save attachments
|
||||
|
||||
public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2;
|
||||
public static final int MAM_MAX_MESSAGES = 500;
|
||||
public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
|
||||
|
||||
public static final boolean PARSE_REAL_JID_FROM_MUC_MAM = false; //dangerous if server doesn’t filter
|
||||
|
||||
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5;
|
||||
public static final int MAM_MAX_MESSAGES = 750;
|
||||
|
||||
public static final long FREQUENT_RESTARTS_DETECTION_WINDOW = 12 * 60 * 60 * 1000; // 10 hours
|
||||
public static final long FREQUENT_RESTARTS_THRESHOLD = 0; // previous value was 16;
|
||||
|
||||
public static final ChatState DEFAULT_CHATSTATE = ChatState.ACTIVE;
|
||||
public static final int TYPING_TIMEOUT = 8;
|
||||
|
||||
public static final int EXPIRY_INTERVAL = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
public static final String ENABLED_CIPHERS[] = {
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384",
|
||||
@ -91,6 +153,5 @@ public final class Config {
|
||||
};
|
||||
|
||||
private Config() {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import java.security.spec.InvalidKeySpecException;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.generator.MessageGenerator;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
@ -52,28 +53,30 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
private KeyPair loadKey(JSONObject keys) {
|
||||
private KeyPair loadKey(final JSONObject keys) {
|
||||
if (keys == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BigInteger x = new BigInteger(keys.getString("otr_x"), 16);
|
||||
BigInteger y = new BigInteger(keys.getString("otr_y"), 16);
|
||||
BigInteger p = new BigInteger(keys.getString("otr_p"), 16);
|
||||
BigInteger q = new BigInteger(keys.getString("otr_q"), 16);
|
||||
BigInteger g = new BigInteger(keys.getString("otr_g"), 16);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
|
||||
DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g);
|
||||
DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
|
||||
PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
} catch (InvalidKeySpecException e) {
|
||||
return null;
|
||||
synchronized (keys) {
|
||||
try {
|
||||
BigInteger x = new BigInteger(keys.getString("otr_x"), 16);
|
||||
BigInteger y = new BigInteger(keys.getString("otr_y"), 16);
|
||||
BigInteger p = new BigInteger(keys.getString("otr_p"), 16);
|
||||
BigInteger q = new BigInteger(keys.getString("otr_q"), 16);
|
||||
BigInteger g = new BigInteger(keys.getString("otr_g"), 16);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
|
||||
DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g);
|
||||
DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
|
||||
PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
} catch (InvalidKeySpecException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +124,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
|
||||
|
||||
@Override
|
||||
public String getFallbackMessage(SessionID arg0) {
|
||||
return "I would like to start a private (OTR encrypted) conversation but your client doesn’t seem to support that";
|
||||
return MessageGenerator.OTR_FALLBACK_MESSAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -179,10 +182,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
|
||||
packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID());
|
||||
}
|
||||
packet.setBody(body);
|
||||
packet.addChild("private", "urn:xmpp:carbons:2");
|
||||
packet.addChild("no-copy", "urn:xmpp:hints");
|
||||
packet.addChild("no-permanent-store", "urn:xmpp:hints");
|
||||
packet.addChild("no-permanent-storage", "urn:xmpp:hints");
|
||||
MessageGenerator.addMessageHints(packet);
|
||||
try {
|
||||
Jid jid = Jid.fromSessionID(session);
|
||||
Conversation conversation = mXmppConnectionService.find(account,jid);
|
||||
@ -194,8 +194,9 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
|
||||
} catch (final InvalidJidException ignored) {
|
||||
|
||||
}
|
||||
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
packet.addChild("encryption","urn:xmpp:eme:0")
|
||||
.setAttribute("namespace","urn:xmpp:otr:0");
|
||||
account.getXmppConnection().sendMessagePacket(packet);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,240 @@
|
||||
package eu.siacs.conversations.crypto;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
|
||||
public class PgpDecryptionService {
|
||||
|
||||
private final XmppConnectionService mXmppConnectionService;
|
||||
private OpenPgpApi openPgpApi = null;
|
||||
|
||||
protected final ArrayDeque<Message> messages = new ArrayDeque();
|
||||
protected final HashSet<Message> pendingNotifications = new HashSet<>();
|
||||
Message currentMessage;
|
||||
private PendingIntent pendingIntent;
|
||||
|
||||
|
||||
public PgpDecryptionService(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public synchronized boolean decrypt(final Message message, boolean notify) {
|
||||
messages.add(message);
|
||||
if (notify && pendingIntent == null) {
|
||||
pendingNotifications.add(message);
|
||||
continueDecryption();
|
||||
return false;
|
||||
} else {
|
||||
continueDecryption();
|
||||
return notify;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void decrypt(final List<Message> list) {
|
||||
for(Message message : list) {
|
||||
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
continueDecryption();
|
||||
}
|
||||
|
||||
public synchronized void discard(List<Message> discards) {
|
||||
this.messages.removeAll(discards);
|
||||
this.pendingNotifications.removeAll(discards);
|
||||
}
|
||||
|
||||
public synchronized void discard(Message message) {
|
||||
this.messages.remove(message);
|
||||
this.pendingNotifications.remove(message);
|
||||
}
|
||||
|
||||
public void giveUpCurrentDecryption(){
|
||||
Message message;
|
||||
synchronized (this) {
|
||||
if(currentMessage != null) {
|
||||
return;
|
||||
}
|
||||
message = messages.peekFirst();
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
discard(message);
|
||||
}
|
||||
synchronized (message){
|
||||
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
|
||||
}
|
||||
}
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
continueDecryption(true);
|
||||
}
|
||||
|
||||
protected synchronized void decryptNext() {
|
||||
if (pendingIntent == null
|
||||
&& getOpenPgpApi() != null
|
||||
&& (currentMessage = messages.poll()) != null) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executeApi(currentMessage);
|
||||
decryptNext();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void continueDecryption(boolean resetPending) {
|
||||
if (resetPending) {
|
||||
this.pendingIntent = null;
|
||||
}
|
||||
continueDecryption();
|
||||
}
|
||||
|
||||
public synchronized void continueDecryption() {
|
||||
if (currentMessage == null) {
|
||||
decryptNext();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized OpenPgpApi getOpenPgpApi() {
|
||||
if (openPgpApi == null) {
|
||||
this.openPgpApi = mXmppConnectionService.getOpenPgpApi();
|
||||
}
|
||||
return this.openPgpApi;
|
||||
}
|
||||
|
||||
private void executeApi(Message message) {
|
||||
synchronized (message) {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
if (message.getType() == Message.TYPE_TEXT) {
|
||||
InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
|
||||
final OutputStream os = new ByteArrayOutputStream();
|
||||
Intent result = getOpenPgpApi().executeApi(params, is, os);
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
try {
|
||||
os.flush();
|
||||
final String body = os.toString();
|
||||
if (body == null) {
|
||||
throw new IOException("body was null");
|
||||
}
|
||||
message.setBody(body);
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||
final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
|
||||
if (message.trusted()
|
||||
&& message.treatAsDownloadable()
|
||||
&& manager.getAutoAcceptFileSize() > 0) {
|
||||
manager.createNewDownloadConnection(message);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
|
||||
}
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
synchronized (PgpDecryptionService.this) {
|
||||
PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
messages.addFirst(message);
|
||||
currentMessage = null;
|
||||
storePendingIntent(pendingIntent);
|
||||
}
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
break;
|
||||
}
|
||||
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||
try {
|
||||
final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||
final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
|
||||
outputFile.getParentFile().mkdirs();
|
||||
outputFile.createNewFile();
|
||||
InputStream is = new FileInputStream(inputFile);
|
||||
OutputStream os = new FileOutputStream(outputFile);
|
||||
Intent result = getOpenPgpApi().executeApi(params, is, os);
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
URL url = message.getFileParams().url;
|
||||
mXmppConnectionService.getFileBackend().updateFileParams(message, url);
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||
inputFile.delete();
|
||||
mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
synchronized (PgpDecryptionService.this) {
|
||||
PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
messages.addFirst(message);
|
||||
currentMessage = null;
|
||||
storePendingIntent(pendingIntent);
|
||||
}
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
break;
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyIfPending(message);
|
||||
}
|
||||
|
||||
private synchronized void notifyIfPending(Message message) {
|
||||
if (pendingNotifications.remove(message)) {
|
||||
mXmppConnectionService.getNotificationService().push(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void storePendingIntent(PendingIntent pendingIntent) {
|
||||
this.pendingIntent = pendingIntent;
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
|
||||
public synchronized boolean hasPendingIntent(Conversation conversation) {
|
||||
if (pendingIntent == null) {
|
||||
return false;
|
||||
} else {
|
||||
for(Message message : messages) {
|
||||
if (message.getConversation() == conversation) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public PendingIntent getPendingIntent() {
|
||||
return pendingIntent;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return getOpenPgpApi() != null;
|
||||
}
|
||||
}
|
@ -2,8 +2,9 @@ package eu.siacs.conversations.crypto;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
|
||||
@ -15,15 +16,15 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||
import eu.siacs.conversations.persistance.FileBackend;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.ui.UiCallback;
|
||||
|
||||
@ -36,113 +37,19 @@ public class PgpEngine {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public void decrypt(final Message message,
|
||||
final UiCallback<Message> callback) {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
|
||||
.getConversation().getAccount().getJid().toBareJid().toString());
|
||||
if (message.getType() == Message.TYPE_TEXT) {
|
||||
InputStream is = new ByteArrayInputStream(message.getBody()
|
||||
.getBytes());
|
||||
final OutputStream os = new ByteArrayOutputStream();
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
try {
|
||||
os.flush();
|
||||
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||
message.setBody(os.toString());
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||
final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
|
||||
if (message.trusted()
|
||||
&& message.treatAsDownloadable() != Message.Decision.NEVER
|
||||
&& manager.getAutoAcceptFileSize() > 0) {
|
||||
manager.createNewDownloadConnection(message);
|
||||
}
|
||||
callback.success(message);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried((PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
message);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||
try {
|
||||
final DownloadableFile inputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getFile(message, false);
|
||||
final DownloadableFile outputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getFile(message, true);
|
||||
outputFile.getParentFile().mkdirs();
|
||||
outputFile.createNewFile();
|
||||
InputStream is = new FileInputStream(inputFile);
|
||||
OutputStream os = new FileOutputStream(outputFile);
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
URL url = message.getFileParams().url;
|
||||
mXmppConnectionService.getFileBackend().updateFileParams(message,url);
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||
PgpEngine.this.mXmppConnectionService
|
||||
.updateMessage(message);
|
||||
inputFile.delete();
|
||||
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||
intent.setData(Uri.fromFile(outputFile));
|
||||
mXmppConnectionService.sendBroadcast(intent);
|
||||
callback.success(message);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried(
|
||||
(PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
message);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (final IOException e) {
|
||||
callback.error(R.string.error_decrypting_file, message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void encrypt(final Message message,
|
||||
final UiCallback<Message> callback) {
|
||||
|
||||
public void encrypt(final Message message, final UiCallback<Message> callback) {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
||||
if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
|
||||
long[] keys = { message.getConversation().getContact()
|
||||
.getPgpKeyId() };
|
||||
final Conversation conversation = message.getConversation();
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
long[] keys = {
|
||||
conversation.getContact().getPgpKeyId(),
|
||||
conversation.getAccount().getPgpId()
|
||||
};
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
|
||||
} else {
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation()
|
||||
.getMucOptions().getPgpKeyIds());
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
|
||||
}
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
|
||||
.getConversation().getAccount().getJid().toBareJid().toString());
|
||||
|
||||
if (!message.needsUploading()) {
|
||||
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
@ -184,6 +91,7 @@ public class PgpEngine {
|
||||
message);
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
break;
|
||||
}
|
||||
@ -197,8 +105,8 @@ public class PgpEngine {
|
||||
.getFileBackend().getFile(message, false);
|
||||
outputFile.getParentFile().mkdirs();
|
||||
outputFile.createNewFile();
|
||||
InputStream is = new FileInputStream(inputFile);
|
||||
OutputStream os = new FileOutputStream(outputFile);
|
||||
final InputStream is = new FileInputStream(inputFile);
|
||||
final OutputStream os = new FileOutputStream(outputFile);
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
@ -206,6 +114,12 @@ public class PgpEngine {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
try {
|
||||
os.flush();
|
||||
} catch (IOException ignored) {
|
||||
//ignored
|
||||
}
|
||||
FileBackend.close(os);
|
||||
callback.success(message);
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
@ -215,6 +129,7 @@ public class PgpEngine {
|
||||
message);
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
break;
|
||||
}
|
||||
@ -248,7 +163,6 @@ public class PgpEngine {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
|
||||
InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
Intent result = api.executeApi(params, is, os);
|
||||
@ -265,19 +179,48 @@ public class PgpEngine {
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
return 0;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void chooseKey(final Account account, final UiCallback<Account> callback) {
|
||||
Intent p = new Intent();
|
||||
p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
|
||||
api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
callback.success(account);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried((PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
account);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
|
||||
callback.error(R.string.openpgp_error, account);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void generateSignature(final Account account, String status,
|
||||
final UiCallback<Account> callback) {
|
||||
if (account.getPgpId() == 0) {
|
||||
return;
|
||||
}
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
|
||||
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
params.setAction(OpenPgpApi.ACTION_SIGN);
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
|
||||
params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
|
||||
InputStream is = new ByteArrayInputStream(status.getBytes());
|
||||
final OutputStream os = new ByteArrayOutputStream();
|
||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": signing status message \""+status+"\"");
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
@ -307,7 +250,7 @@ public class PgpEngine {
|
||||
callback.error(R.string.openpgp_error, account);
|
||||
return;
|
||||
}
|
||||
account.setKey("pgp_signature", signatureBuilder.toString());
|
||||
account.setPgpSignature(signatureBuilder.toString());
|
||||
callback.success(account);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
@ -316,7 +259,13 @@ public class PgpEngine {
|
||||
account);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
callback.error(R.string.openpgp_error, account);
|
||||
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
||||
if (error != null && "signing subkey not found!".equals(error.getMessage())) {
|
||||
callback.error(0,account);
|
||||
} else {
|
||||
logError(account, error);
|
||||
callback.error(R.string.unable_to_connect_to_keychain, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -326,8 +275,6 @@ public class PgpEngine {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_GET_KEY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
|
||||
.getJid().toBareJid().toString());
|
||||
api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
@ -342,30 +289,27 @@ public class PgpEngine {
|
||||
contact);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
logError(contact.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
|
||||
callback.error(R.string.openpgp_error, contact);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public PendingIntent getIntentForKey(Contact contact) {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_GET_KEY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
|
||||
.getJid().toBareJid().toString());
|
||||
Intent result = api.executeApi(params, null, null);
|
||||
return (PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
private static void logError(Account account, OpenPgpError error) {
|
||||
if (error != null) {
|
||||
Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": OpenKeychain error '"+error.getMessage()+"' code="+error.getErrorId());
|
||||
} else {
|
||||
Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": OpenKeychain error with no message");
|
||||
}
|
||||
}
|
||||
|
||||
public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
|
||||
|
||||
public PendingIntent getIntentForKey(long pgpKeyId) {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_GET_KEY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
|
||||
Intent result = api.executeApi(params, null, null);
|
||||
return (PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,167 @@
|
||||
package eu.siacs.conversations.crypto;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.DERIA5String;
|
||||
import org.bouncycastle.asn1.DERTaggedObject;
|
||||
import org.bouncycastle.asn1.DERUTF8String;
|
||||
import org.bouncycastle.asn1.DLSequence;
|
||||
import org.bouncycastle.asn1.x500.RDN;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import de.duenndns.ssl.DomainHostnameVerifier;
|
||||
|
||||
public class XmppDomainVerifier implements DomainHostnameVerifier {
|
||||
|
||||
private static final String LOGTAG = "XmppDomainVerifier";
|
||||
|
||||
private static final String SRV_NAME = "1.3.6.1.5.5.7.8.7";
|
||||
private static final String XMPP_ADDR = "1.3.6.1.5.5.7.8.5";
|
||||
|
||||
@Override
|
||||
public boolean verify(String domain, String hostname, SSLSession sslSession) {
|
||||
try {
|
||||
Certificate[] chain = sslSession.getPeerCertificates();
|
||||
if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) {
|
||||
return false;
|
||||
}
|
||||
X509Certificate certificate = (X509Certificate) chain[0];
|
||||
final List<String> commonNames = getCommonNames(certificate);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isSelfSigned(certificate)) {
|
||||
if (commonNames.size() == 1 && matchDomain(domain,commonNames)) {
|
||||
Log.d(LOGTAG,"accepted CN in self signed cert as work around for "+domain);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames();
|
||||
List<String> xmppAddrs = new ArrayList<>();
|
||||
List<String> srvNames = new ArrayList<>();
|
||||
List<String> domains = new ArrayList<>();
|
||||
if (alternativeNames != null) {
|
||||
for (List<?> san : alternativeNames) {
|
||||
Integer type = (Integer) san.get(0);
|
||||
if (type == 0) {
|
||||
Pair<String, String> otherName = parseOtherName((byte[]) san.get(1));
|
||||
if (otherName != null) {
|
||||
switch (otherName.first) {
|
||||
case SRV_NAME:
|
||||
srvNames.add(otherName.second);
|
||||
break;
|
||||
case XMPP_ADDR:
|
||||
xmppAddrs.add(otherName.second);
|
||||
break;
|
||||
default:
|
||||
Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second);
|
||||
}
|
||||
}
|
||||
} else if (type == 2) {
|
||||
Object value = san.get(1);
|
||||
if (value instanceof String) {
|
||||
domains.add((String) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) {
|
||||
domains.addAll(commonNames);
|
||||
}
|
||||
Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains);
|
||||
if (hostname != null) {
|
||||
Log.d(LOGTAG,"also trying to verify hostname "+hostname);
|
||||
}
|
||||
return xmppAddrs.contains(domain)
|
||||
|| srvNames.contains("_xmpp-client." + domain)
|
||||
|| matchDomain(domain, domains)
|
||||
|| (hostname != null && matchDomain(hostname,domains));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> getCommonNames(X509Certificate certificate) {
|
||||
List<String> domains = new ArrayList<>();
|
||||
try {
|
||||
X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
|
||||
RDN[] rdns = x500name.getRDNs(BCStyle.CN);
|
||||
for (int i = 0; i < rdns.length; ++i) {
|
||||
domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue()));
|
||||
}
|
||||
return domains;
|
||||
} catch (CertificateEncodingException e) {
|
||||
return domains;
|
||||
}
|
||||
}
|
||||
|
||||
private static Pair<String, String> parseOtherName(byte[] otherName) {
|
||||
try {
|
||||
ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName);
|
||||
if (asn1Primitive instanceof DERTaggedObject) {
|
||||
ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject();
|
||||
if (inner instanceof DLSequence) {
|
||||
DLSequence sequence = (DLSequence) inner;
|
||||
if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) {
|
||||
String oid = sequence.getObjectAt(0).toString();
|
||||
ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject();
|
||||
if (value instanceof DERUTF8String) {
|
||||
return new Pair<>(oid, ((DERUTF8String) value).getString());
|
||||
} else if (value instanceof DERIA5String) {
|
||||
return new Pair<>(oid, ((DERIA5String) value).getString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean matchDomain(String needle, List<String> haystack) {
|
||||
for (String entry : haystack) {
|
||||
if (entry.startsWith("*.")) {
|
||||
int i = needle.indexOf('.');
|
||||
Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1));
|
||||
if (i != -1 && needle.substring(i).equals(entry.substring(1))) {
|
||||
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (entry.equals(needle)) {
|
||||
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSelfSigned(X509Certificate certificate) {
|
||||
try {
|
||||
certificate.verify(certificate.getPublicKey());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String domain, SSLSession sslSession) {
|
||||
return verify(domain,null,sslSession);
|
||||
}
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
package eu.siacs.conversations.crypto.axolotl;
|
||||
|
||||
public class CryptoFailedException extends Exception {
|
||||
|
||||
public CryptoFailedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public CryptoFailedException(Exception e){
|
||||
super(e);
|
||||
}
|
||||
|
@ -0,0 +1,180 @@
|
||||
package eu.siacs.conversations.crypto.axolotl;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
public class FingerprintStatus implements Comparable<FingerprintStatus> {
|
||||
|
||||
private static final long DO_NOT_OVERWRITE = -1;
|
||||
|
||||
private Trust trust = Trust.UNTRUSTED;
|
||||
private boolean active = false;
|
||||
private long lastActivation = DO_NOT_OVERWRITE;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
FingerprintStatus that = (FingerprintStatus) o;
|
||||
|
||||
return active == that.active && trust == that.trust;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = trust.hashCode();
|
||||
result = 31 * result + (active ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private FingerprintStatus() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
public ContentValues toContentValues() {
|
||||
final ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(SQLiteAxolotlStore.TRUST,trust.toString());
|
||||
contentValues.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0);
|
||||
if (lastActivation != DO_NOT_OVERWRITE) {
|
||||
contentValues.put(SQLiteAxolotlStore.LAST_ACTIVATION,lastActivation);
|
||||
}
|
||||
return contentValues;
|
||||
}
|
||||
|
||||
public static FingerprintStatus fromCursor(Cursor cursor) {
|
||||
final FingerprintStatus status = new FingerprintStatus();
|
||||
try {
|
||||
status.trust = Trust.valueOf(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.TRUST)));
|
||||
} catch(IllegalArgumentException e) {
|
||||
status.trust = Trust.UNTRUSTED;
|
||||
}
|
||||
status.active = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.ACTIVE)) > 0;
|
||||
status.lastActivation = cursor.getLong(cursor.getColumnIndex(SQLiteAxolotlStore.LAST_ACTIVATION));
|
||||
return status;
|
||||
}
|
||||
|
||||
public static FingerprintStatus createActiveUndecided() {
|
||||
final FingerprintStatus status = new FingerprintStatus();
|
||||
status.trust = Trust.UNDECIDED;
|
||||
status.active = true;
|
||||
status.lastActivation = System.currentTimeMillis();
|
||||
return status;
|
||||
}
|
||||
|
||||
public static FingerprintStatus createActiveTrusted() {
|
||||
final FingerprintStatus status = new FingerprintStatus();
|
||||
status.trust = Trust.TRUSTED;
|
||||
status.active = true;
|
||||
status.lastActivation = System.currentTimeMillis();
|
||||
return status;
|
||||
}
|
||||
|
||||
public static FingerprintStatus createActiveVerified(boolean x509) {
|
||||
final FingerprintStatus status = new FingerprintStatus();
|
||||
status.trust = x509 ? Trust.VERIFIED_X509 : Trust.VERIFIED;
|
||||
status.active = true;
|
||||
return status;
|
||||
}
|
||||
|
||||
public static FingerprintStatus createActive(boolean trusted) {
|
||||
final FingerprintStatus status = new FingerprintStatus();
|
||||
status.trust = trusted ? Trust.TRUSTED : Trust.UNTRUSTED;
|
||||
status.active = true;
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isTrustedAndActive() {
|
||||
return active && isTrusted();
|
||||
}
|
||||
|
||||
public boolean isTrusted() {
|
||||
return trust == Trust.TRUSTED || isVerified();
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return trust == Trust.VERIFIED || trust == Trust.VERIFIED_X509;
|
||||
}
|
||||
|
||||
public boolean isCompromised() {
|
||||
return trust == Trust.COMPROMISED;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public FingerprintStatus toActive() {
|
||||
FingerprintStatus status = new FingerprintStatus();
|
||||
status.trust = trust;
|
||||
if (!status.active) {
|
||||
status.lastActivation = System.currentTimeMillis();
|
||||
}
|
||||
status.active = true;
|
||||
return status;
|
||||
}
|
||||
|
||||
public FingerprintStatus toInactive() {
|
||||
FingerprintStatus status = new FingerprintStatus();
|
||||
status.trust = trust;
|
||||
status.active = false;
|
||||
return status;
|
||||
}
|
||||
|
||||
public Trust getTrust() {
|
||||
return trust;
|
||||
}
|
||||
|
||||
public FingerprintStatus toVerified() {
|
||||
FingerprintStatus status = new FingerprintStatus();
|
||||
status.active = active;
|
||||
status.trust = Trust.VERIFIED;
|
||||
return status;
|
||||
}
|
||||
|
||||
public FingerprintStatus toUntrusted() {
|
||||
FingerprintStatus status = new FingerprintStatus();
|
||||
status.active = active;
|
||||
status.trust = Trust.UNTRUSTED;
|
||||
return status;
|
||||
}
|
||||
|
||||
public static FingerprintStatus createInactiveVerified() {
|
||||
final FingerprintStatus status = new FingerprintStatus();
|
||||
status.trust = Trust.VERIFIED;
|
||||
status.active = false;
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(FingerprintStatus o) {
|
||||
if (active == o.active) {
|
||||
if (lastActivation > o.lastActivation) {
|
||||
return -1;
|
||||
} else if (lastActivation < o.lastActivation) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else if (active){
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public long getLastActivation() {
|
||||
return lastActivation;
|
||||
}
|
||||
|
||||
public enum Trust {
|
||||
COMPROMISED,
|
||||
UNDECIDED,
|
||||
UNTRUSTED,
|
||||
TRUSTED,
|
||||
VERIFIED,
|
||||
VERIFIED_X509
|
||||
}
|
||||
|
||||
}
|
@ -3,26 +3,28 @@ package eu.siacs.conversations.crypto.axolotl;
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
|
||||
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyIdException;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.state.SessionRecord;
|
||||
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.util.KeyHelper;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.ecc.Curve;
|
||||
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.util.KeyHelper;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
|
||||
public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
public class SQLiteAxolotlStore implements SignalProtocolStore {
|
||||
|
||||
public static final String PREKEY_TABLENAME = "prekeys";
|
||||
public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
|
||||
@ -34,8 +36,12 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
public static final String KEY = "key";
|
||||
public static final String FINGERPRINT = "fingerprint";
|
||||
public static final String NAME = "name";
|
||||
public static final String TRUSTED = "trusted";
|
||||
public static final String TRUSTED = "trusted"; //no longer used
|
||||
public static final String TRUST = "trust";
|
||||
public static final String ACTIVE = "active";
|
||||
public static final String LAST_ACTIVATION = "last_activation";
|
||||
public static final String OWN = "ownkey";
|
||||
public static final String CERTIFICATE = "certificate";
|
||||
|
||||
public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
|
||||
public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
|
||||
@ -49,11 +55,11 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
private int localRegistrationId;
|
||||
private int currentPreKeyId = 0;
|
||||
|
||||
private final LruCache<String, XmppAxolotlSession.Trust> trustCache =
|
||||
new LruCache<String, XmppAxolotlSession.Trust>(NUM_TRUSTS_TO_CACHE) {
|
||||
private final LruCache<String, FingerprintStatus> trustCache =
|
||||
new LruCache<String, FingerprintStatus>(NUM_TRUSTS_TO_CACHE) {
|
||||
@Override
|
||||
protected XmppAxolotlSession.Trust create(String fingerprint) {
|
||||
return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint);
|
||||
protected FingerprintStatus create(String fingerprint) {
|
||||
return mXmppConnectionService.databaseBackend.getFingerprintStatus(account, fingerprint);
|
||||
}
|
||||
};
|
||||
|
||||
@ -74,9 +80,6 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
this.mXmppConnectionService = service;
|
||||
this.localRegistrationId = loadRegistrationId();
|
||||
this.currentPreKeyId = loadCurrentPreKeyId();
|
||||
for (SignedPreKeyRecord record : loadSignedPreKeys()) {
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentPreKeyId() {
|
||||
@ -88,18 +91,18 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
// --------------------------------------
|
||||
|
||||
private IdentityKeyPair loadIdentityKeyPair() {
|
||||
String ownName = account.getJid().toBareJid().toString();
|
||||
IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account,
|
||||
ownName);
|
||||
synchronized (mXmppConnectionService) {
|
||||
IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account);
|
||||
|
||||
if (ownKey != null) {
|
||||
if (ownKey != null) {
|
||||
return ownKey;
|
||||
} else {
|
||||
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
|
||||
ownKey = generateIdentityKeyPair();
|
||||
mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey);
|
||||
}
|
||||
return ownKey;
|
||||
} else {
|
||||
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl key for account " + ownName);
|
||||
ownKey = generateIdentityKeyPair();
|
||||
mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey);
|
||||
}
|
||||
return ownKey;
|
||||
}
|
||||
|
||||
private int loadRegistrationId() {
|
||||
@ -125,15 +128,15 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
}
|
||||
|
||||
private int loadCurrentPreKeyId() {
|
||||
String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
|
||||
int reg_id;
|
||||
if (regIdString != null) {
|
||||
reg_id = Integer.valueOf(regIdString);
|
||||
String prekeyIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
|
||||
int prekey_id;
|
||||
if (prekeyIdString != null) {
|
||||
prekey_id = Integer.valueOf(prekeyIdString);
|
||||
} else {
|
||||
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
|
||||
reg_id = 0;
|
||||
prekey_id = 0;
|
||||
}
|
||||
return reg_id;
|
||||
return prekey_id;
|
||||
}
|
||||
|
||||
public void regenerate() {
|
||||
@ -177,14 +180,29 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
* <p/>
|
||||
* Store a remote client's identity key as trusted.
|
||||
*
|
||||
* @param name The name of the remote client.
|
||||
* @param address The address of the remote client.
|
||||
* @param identityKey The remote client's identity key.
|
||||
* @return true on success
|
||||
*/
|
||||
@Override
|
||||
public void saveIdentity(String name, IdentityKey identityKey) {
|
||||
if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) {
|
||||
mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey);
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, address.getName()).contains(identityKey)) {
|
||||
String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
|
||||
FingerprintStatus status = getFingerprintStatus(fingerprint);
|
||||
if (status == null) {
|
||||
if (mXmppConnectionService.blindTrustBeforeVerification() && !account.getAxolotlService().hasVerifiedKeys(address.getName())) {
|
||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": blindly trusted "+fingerprint+" of "+address.getName());
|
||||
status = FingerprintStatus.createActiveTrusted();
|
||||
} else {
|
||||
status = FingerprintStatus.createActiveUndecided();
|
||||
}
|
||||
} else {
|
||||
status = status.toActive();
|
||||
}
|
||||
mXmppConnectionService.databaseBackend.storeIdentityKey(account, address.getName(), identityKey, status);
|
||||
trustCache.remove(fingerprint);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,26 +215,33 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
* store. Only if it mismatches an entry in the local store is it considered
|
||||
* 'untrusted.'
|
||||
*
|
||||
* @param name The name of the remote client.
|
||||
* @param identityKey The identity key to verify.
|
||||
* @return true if trusted, false if untrusted.
|
||||
*/
|
||||
@Override
|
||||
public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
|
||||
public FingerprintStatus getFingerprintStatus(String fingerprint) {
|
||||
return (fingerprint == null)? null : trustCache.get(fingerprint);
|
||||
}
|
||||
|
||||
public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
|
||||
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust);
|
||||
public void setFingerprintStatus(String fingerprint, FingerprintStatus status) {
|
||||
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, status);
|
||||
trustCache.remove(fingerprint);
|
||||
}
|
||||
|
||||
public Set<IdentityKey> getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) {
|
||||
return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust);
|
||||
public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) {
|
||||
mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate);
|
||||
}
|
||||
|
||||
public X509Certificate getFingerprintCertificate(String fingerprint) {
|
||||
return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint);
|
||||
}
|
||||
|
||||
public Set<IdentityKey> getContactKeysWithTrust(String bareJid, FingerprintStatus status) {
|
||||
return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, status);
|
||||
}
|
||||
|
||||
public long getContactNumTrustedKeys(String bareJid) {
|
||||
@ -241,7 +266,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
* a new SessionRecord if one does not currently exist.
|
||||
*/
|
||||
@Override
|
||||
public SessionRecord loadSession(AxolotlAddress address) {
|
||||
public SessionRecord loadSession(SignalProtocolAddress address) {
|
||||
SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
|
||||
return (session != null) ? session : new SessionRecord();
|
||||
}
|
||||
@ -255,9 +280,13 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
@Override
|
||||
public List<Integer> getSubDeviceSessions(String name) {
|
||||
return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
|
||||
new AxolotlAddress(name, 0));
|
||||
new SignalProtocolAddress(name, 0));
|
||||
}
|
||||
|
||||
|
||||
public List<String> getKnownAddresses() {
|
||||
return mXmppConnectionService.databaseBackend.getKnownSignalAddresses(account);
|
||||
}
|
||||
/**
|
||||
* Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
|
||||
*
|
||||
@ -265,7 +294,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
* @param record the current SessionRecord for the remote client.
|
||||
*/
|
||||
@Override
|
||||
public void storeSession(AxolotlAddress address, SessionRecord record) {
|
||||
public void storeSession(SignalProtocolAddress address, SessionRecord record) {
|
||||
mXmppConnectionService.databaseBackend.storeSession(account, address, record);
|
||||
}
|
||||
|
||||
@ -276,7 +305,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
* @return true if a {@link SessionRecord} exists, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean containsSession(AxolotlAddress address) {
|
||||
public boolean containsSession(SignalProtocolAddress address) {
|
||||
return mXmppConnectionService.databaseBackend.containsSession(account, address);
|
||||
}
|
||||
|
||||
@ -286,7 +315,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
* @param address the address of the remote client.
|
||||
*/
|
||||
@Override
|
||||
public void deleteSession(AxolotlAddress address) {
|
||||
public void deleteSession(SignalProtocolAddress address) {
|
||||
mXmppConnectionService.databaseBackend.deleteSession(account, address);
|
||||
}
|
||||
|
||||
@ -297,7 +326,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
*/
|
||||
@Override
|
||||
public void deleteAllSessions(String name) {
|
||||
AxolotlAddress address = new AxolotlAddress(name, 0);
|
||||
SignalProtocolAddress address = new SignalProtocolAddress(name, 0);
|
||||
mXmppConnectionService.databaseBackend.deleteAllSessions(account,
|
||||
address);
|
||||
}
|
||||
@ -389,6 +418,10 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
|
||||
}
|
||||
|
||||
public int getSignedPreKeysCount() {
|
||||
return mXmppConnectionService.databaseBackend.getSignedPreKeysCount(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a local SignedPreKeyRecord.
|
||||
*
|
||||
@ -418,4 +451,8 @@ public class SQLiteAxolotlStore implements AxolotlStore {
|
||||
public void removeSignedPreKey(int signedPreKeyId) {
|
||||
mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
|
||||
}
|
||||
|
||||
public void preVerifyFingerprint(Account account, String name, String fingerprint) {
|
||||
mXmppConnectionService.databaseBackend.storePreVerification(account,name,fingerprint,FingerprintStatus.createInactiveVerified());
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package eu.siacs.conversations.crypto.axolotl;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -40,8 +41,9 @@ public class XmppAxolotlMessage {
|
||||
|
||||
private byte[] innerKey;
|
||||
private byte[] ciphertext = null;
|
||||
private byte[] authtagPlusInnerKey = null;
|
||||
private byte[] iv = null;
|
||||
private final Map<Integer, byte[]> keys;
|
||||
private final Map<Integer, XmppAxolotlSession.AxolotlKey> keys;
|
||||
private final Jid from;
|
||||
private final int sourceDeviceId;
|
||||
|
||||
@ -91,7 +93,11 @@ public class XmppAxolotlMessage {
|
||||
private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException {
|
||||
this.from = from;
|
||||
Element header = axolotlMessage.findChild(HEADER);
|
||||
this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID));
|
||||
try {
|
||||
this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("invalid source id");
|
||||
}
|
||||
List<Element> keyElements = header.getChildren();
|
||||
this.keys = new HashMap<>(keyElements.size());
|
||||
for (Element keyElement : keyElements) {
|
||||
@ -99,17 +105,18 @@ public class XmppAxolotlMessage {
|
||||
case KEYTAG:
|
||||
try {
|
||||
Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID));
|
||||
byte[] key = Base64.decode(keyElement.getContent(), Base64.DEFAULT);
|
||||
this.keys.put(recipientId, key);
|
||||
byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT);
|
||||
boolean isPreKey =keyElement.getAttributeAsBoolean("prekey");
|
||||
this.keys.put(recipientId, new XmppAxolotlSession.AxolotlKey(key,isPreKey));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
throw new IllegalArgumentException("invalid remote id");
|
||||
}
|
||||
break;
|
||||
case IVTAG:
|
||||
if (this.iv != null) {
|
||||
throw new IllegalArgumentException("Duplicate iv entry");
|
||||
}
|
||||
iv = Base64.decode(keyElement.getContent(), Base64.DEFAULT);
|
||||
iv = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT);
|
||||
break;
|
||||
default:
|
||||
Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString());
|
||||
@ -118,7 +125,7 @@ public class XmppAxolotlMessage {
|
||||
}
|
||||
Element payloadElement = axolotlMessage.findChild(PAYLOAD);
|
||||
if (payloadElement != null) {
|
||||
ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT);
|
||||
ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,8 +165,15 @@ public class XmppAxolotlMessage {
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
|
||||
this.innerKey = secretKey.getEncoded();
|
||||
this.ciphertext = cipher.doFinal(plaintext.getBytes());
|
||||
this.ciphertext = cipher.doFinal(Config.OMEMO_PADDING ? getPaddedBytes(plaintext) : plaintext.getBytes());
|
||||
if (Config.PUT_AUTH_TAG_INTO_KEY && this.ciphertext != null) {
|
||||
this.authtagPlusInnerKey = new byte[16+16];
|
||||
byte[] ciphertext = new byte[this.ciphertext.length - 16];
|
||||
System.arraycopy(this.ciphertext,0,ciphertext,0,ciphertext.length);
|
||||
System.arraycopy(this.ciphertext,ciphertext.length,authtagPlusInnerKey,16,16);
|
||||
System.arraycopy(this.innerKey,0,authtagPlusInnerKey,0,this.innerKey.length);
|
||||
this.ciphertext = ciphertext;
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||
| IllegalBlockSizeException | BadPaddingException | NoSuchProviderException
|
||||
| InvalidAlgorithmParameterException e) {
|
||||
@ -167,6 +181,22 @@ public class XmppAxolotlMessage {
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getPaddedBytes(String plaintext) {
|
||||
int plainLength = plaintext.getBytes().length;
|
||||
int pad = Math.max(64,(plainLength / 32 + 1) * 32) - plainLength;
|
||||
SecureRandom random = new SecureRandom();
|
||||
int left = random.nextInt(pad);
|
||||
int right = pad - left;
|
||||
StringBuilder builder = new StringBuilder(plaintext);
|
||||
for(int i = 0; i < left; ++i) {
|
||||
builder.insert(0,random.nextBoolean() ? "\t" : " ");
|
||||
}
|
||||
for(int i = 0; i < right; ++i) {
|
||||
builder.append(random.nextBoolean() ? "\t" : " ");
|
||||
}
|
||||
return builder.toString().getBytes();
|
||||
}
|
||||
|
||||
public Jid getFrom() {
|
||||
return this.from;
|
||||
}
|
||||
@ -180,7 +210,12 @@ public class XmppAxolotlMessage {
|
||||
}
|
||||
|
||||
public void addDevice(XmppAxolotlSession session) {
|
||||
byte[] key = session.processSending(innerKey);
|
||||
XmppAxolotlSession.AxolotlKey key;
|
||||
if (authtagPlusInnerKey != null) {
|
||||
key = session.processSending(authtagPlusInnerKey);
|
||||
} else {
|
||||
key = session.processSending(innerKey);
|
||||
}
|
||||
if (key != null) {
|
||||
keys.put(session.getRemoteAddress().getDeviceId(), key);
|
||||
}
|
||||
@ -198,30 +233,33 @@ public class XmppAxolotlMessage {
|
||||
Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX);
|
||||
Element headerElement = encryptionElement.addChild(HEADER);
|
||||
headerElement.setAttribute(SOURCEID, sourceDeviceId);
|
||||
for (Map.Entry<Integer, byte[]> keyEntry : keys.entrySet()) {
|
||||
for (Map.Entry<Integer, XmppAxolotlSession.AxolotlKey> keyEntry : keys.entrySet()) {
|
||||
Element keyElement = new Element(KEYTAG);
|
||||
keyElement.setAttribute(REMOTEID, keyEntry.getKey());
|
||||
keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.DEFAULT));
|
||||
if (keyEntry.getValue().prekey) {
|
||||
keyElement.setAttribute("prekey","true");
|
||||
}
|
||||
keyElement.setContent(Base64.encodeToString(keyEntry.getValue().key, Base64.NO_WRAP));
|
||||
headerElement.addChild(keyElement);
|
||||
}
|
||||
headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT));
|
||||
headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP));
|
||||
if (ciphertext != null) {
|
||||
Element payload = encryptionElement.addChild(PAYLOAD);
|
||||
payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT));
|
||||
payload.setContent(Base64.encodeToString(ciphertext, Base64.NO_WRAP));
|
||||
}
|
||||
return encryptionElement;
|
||||
}
|
||||
|
||||
private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) {
|
||||
byte[] encryptedKey = keys.get(sourceDeviceId);
|
||||
return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null;
|
||||
private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
|
||||
XmppAxolotlSession.AxolotlKey encryptedKey = keys.get(sourceDeviceId);
|
||||
if (encryptedKey == null) {
|
||||
throw new CryptoFailedException("Message was not encrypted for this device");
|
||||
}
|
||||
return session.processReceiving(encryptedKey);
|
||||
}
|
||||
|
||||
public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) {
|
||||
byte[] key = unpackKey(session, sourceDeviceId);
|
||||
return (key != null)
|
||||
? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV())
|
||||
: null;
|
||||
public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
|
||||
return new XmppAxolotlKeyTransportMessage(session.getFingerprint(), unpackKey(session, sourceDeviceId), getIV());
|
||||
}
|
||||
|
||||
public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
|
||||
@ -229,6 +267,19 @@ public class XmppAxolotlMessage {
|
||||
byte[] key = unpackKey(session, sourceDeviceId);
|
||||
if (key != null) {
|
||||
try {
|
||||
|
||||
if (key.length >= 32) {
|
||||
int authtaglength = key.length - 16;
|
||||
Log.d(Config.LOGTAG,"found auth tag as part of omemo key");
|
||||
byte[] newCipherText = new byte[key.length - 16 + ciphertext.length];
|
||||
byte[] newKey = new byte[16];
|
||||
System.arraycopy(ciphertext, 0, newCipherText, 0, ciphertext.length);
|
||||
System.arraycopy(key, 16, newCipherText, ciphertext.length, authtaglength);
|
||||
System.arraycopy(key,0,newKey,0,newKey.length);
|
||||
ciphertext = newCipherText;
|
||||
key = newKey;
|
||||
}
|
||||
|
||||
Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
@ -236,7 +287,7 @@ public class XmppAxolotlMessage {
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||
|
||||
String plaintext = new String(cipher.doFinal(ciphertext));
|
||||
plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint());
|
||||
plaintextMessage = new XmppAxolotlPlaintextMessage(Config.OMEMO_PADDING ? plaintext.trim() : plaintext, session.getFingerprint());
|
||||
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException
|
||||
|
@ -2,99 +2,41 @@ package eu.siacs.conversations.crypto.axolotl;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyIdException;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.libaxolotl.InvalidVersionException;
|
||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||
import org.whispersystems.libaxolotl.NoSessionException;
|
||||
import org.whispersystems.libaxolotl.SessionCipher;
|
||||
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.DuplicateMessageException;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.InvalidVersionException;
|
||||
import org.whispersystems.libsignal.LegacyMessageException;
|
||||
import org.whispersystems.libsignal.NoSessionException;
|
||||
import org.whispersystems.libsignal.SessionCipher;
|
||||
import org.whispersystems.libsignal.UntrustedIdentityException;
|
||||
import org.whispersystems.libsignal.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
|
||||
import org.whispersystems.libsignal.protocol.SignalMessage;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
|
||||
public class XmppAxolotlSession {
|
||||
public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
|
||||
private final SessionCipher cipher;
|
||||
private final SQLiteAxolotlStore sqLiteAxolotlStore;
|
||||
private final AxolotlAddress remoteAddress;
|
||||
private final SignalProtocolAddress remoteAddress;
|
||||
private final Account account;
|
||||
private String fingerprint = null;
|
||||
private IdentityKey identityKey;
|
||||
private Integer preKeyId = null;
|
||||
private boolean fresh = true;
|
||||
|
||||
public enum Trust {
|
||||
UNDECIDED(0),
|
||||
TRUSTED(1),
|
||||
UNTRUSTED(2),
|
||||
COMPROMISED(3),
|
||||
INACTIVE_TRUSTED(4),
|
||||
INACTIVE_UNDECIDED(5),
|
||||
INACTIVE_UNTRUSTED(6);
|
||||
|
||||
private static final Map<Integer, Trust> trustsByValue = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Trust trust : Trust.values()) {
|
||||
trustsByValue.put(trust.getCode(), trust);
|
||||
}
|
||||
}
|
||||
|
||||
private final int code;
|
||||
|
||||
Trust(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
switch (this) {
|
||||
case UNDECIDED:
|
||||
return "Trust undecided " + getCode();
|
||||
case TRUSTED:
|
||||
return "Trusted " + getCode();
|
||||
case COMPROMISED:
|
||||
return "Compromised " + getCode();
|
||||
case INACTIVE_TRUSTED:
|
||||
return "Inactive (Trusted)" + getCode();
|
||||
case INACTIVE_UNDECIDED:
|
||||
return "Inactive (Undecided)" + getCode();
|
||||
case INACTIVE_UNTRUSTED:
|
||||
return "Inactive (Untrusted)" + getCode();
|
||||
case UNTRUSTED:
|
||||
default:
|
||||
return "Untrusted " + getCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static Trust fromBoolean(Boolean trusted) {
|
||||
return trusted ? TRUSTED : UNTRUSTED;
|
||||
}
|
||||
|
||||
public static Trust fromCode(int code) {
|
||||
return trustsByValue.get(code);
|
||||
}
|
||||
}
|
||||
|
||||
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) {
|
||||
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, SignalProtocolAddress remoteAddress, IdentityKey identityKey) {
|
||||
this(account, store, remoteAddress);
|
||||
this.fingerprint = fingerprint;
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
|
||||
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, SignalProtocolAddress remoteAddress) {
|
||||
this.cipher = new SessionCipher(store, remoteAddress);
|
||||
this.remoteAddress = remoteAddress;
|
||||
this.sqLiteAxolotlStore = store;
|
||||
@ -111,10 +53,14 @@ public class XmppAxolotlSession {
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
return identityKey == null ? null : CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
|
||||
}
|
||||
|
||||
public AxolotlAddress getRemoteAddress() {
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public SignalProtocolAddress getRemoteAddress() {
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
@ -126,71 +72,90 @@ public class XmppAxolotlSession {
|
||||
this.fresh = false;
|
||||
}
|
||||
|
||||
protected void setTrust(Trust trust) {
|
||||
sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
|
||||
protected void setTrust(FingerprintStatus status) {
|
||||
sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
|
||||
}
|
||||
|
||||
protected Trust getTrust() {
|
||||
Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
|
||||
return (trust == null) ? Trust.UNDECIDED : trust;
|
||||
public FingerprintStatus getTrust() {
|
||||
FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint());
|
||||
return (status == null) ? FingerprintStatus.createActiveUndecided() : status;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] processReceiving(byte[] encryptedKey) {
|
||||
byte[] plaintext = null;
|
||||
Trust trust = getTrust();
|
||||
switch (trust) {
|
||||
case INACTIVE_TRUSTED:
|
||||
case UNDECIDED:
|
||||
case UNTRUSTED:
|
||||
case TRUSTED:
|
||||
public byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException {
|
||||
byte[] plaintext;
|
||||
FingerprintStatus status = getTrust();
|
||||
if (!status.isCompromised()) {
|
||||
try {
|
||||
CiphertextMessage ciphertextMessage;
|
||||
try {
|
||||
try {
|
||||
PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
|
||||
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
|
||||
String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", "");
|
||||
if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) {
|
||||
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.fingerprint + ", received message with fingerprint " + fingerprint);
|
||||
} else {
|
||||
this.fingerprint = fingerprint;
|
||||
plaintext = cipher.decrypt(message);
|
||||
if (message.getPreKeyId().isPresent()) {
|
||||
preKeyId = message.getPreKeyId().get();
|
||||
}
|
||||
}
|
||||
} catch (InvalidMessageException | InvalidVersionException e) {
|
||||
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
|
||||
WhisperMessage message = new WhisperMessage(encryptedKey);
|
||||
plaintext = cipher.decrypt(message);
|
||||
} catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
|
||||
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
|
||||
ciphertextMessage = new PreKeySignalMessage(encryptedKey.key);
|
||||
Optional<Integer> optionalPreKeyId = ((PreKeySignalMessage) ciphertextMessage).getPreKeyId();
|
||||
IdentityKey identityKey = ((PreKeySignalMessage) ciphertextMessage).getIdentityKey();
|
||||
if (!optionalPreKeyId.isPresent()) {
|
||||
throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
|
||||
}
|
||||
} catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
|
||||
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
|
||||
preKeyId = optionalPreKeyId.get();
|
||||
if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
|
||||
throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
|
||||
}
|
||||
this.identityKey = identityKey;
|
||||
} catch (InvalidVersionException | InvalidMessageException e) {
|
||||
ciphertextMessage = new SignalMessage(encryptedKey.key);
|
||||
}
|
||||
|
||||
if (plaintext != null && trust == Trust.INACTIVE_TRUSTED) {
|
||||
setTrust(Trust.TRUSTED);
|
||||
if (ciphertextMessage instanceof PreKeySignalMessage) {
|
||||
plaintext = cipher.decrypt((PreKeySignalMessage) ciphertextMessage);
|
||||
} else {
|
||||
plaintext = cipher.decrypt((SignalMessage) ciphertextMessage);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case COMPROMISED:
|
||||
default:
|
||||
// ignore
|
||||
break;
|
||||
} catch (InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException | InvalidKeyIdException | UntrustedIdentityException e) {
|
||||
if (!(e instanceof DuplicateMessageException)) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
throw new CryptoFailedException("Error decrypting WhisperMessage " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||
}
|
||||
if (!status.isActive()) {
|
||||
setTrust(status.toActive());
|
||||
}
|
||||
} else {
|
||||
throw new CryptoFailedException("not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised");
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] processSending(@NonNull byte[] outgoingMessage) {
|
||||
Trust trust = getTrust();
|
||||
if (trust == Trust.TRUSTED) {
|
||||
CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
|
||||
return ciphertextMessage.serialize();
|
||||
public AxolotlKey processSending(@NonNull byte[] outgoingMessage) {
|
||||
FingerprintStatus status = getTrust();
|
||||
if (status.isTrustedAndActive()) {
|
||||
try {
|
||||
CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
|
||||
return new AxolotlKey(ciphertextMessage.serialize(),ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(XmppAxolotlSession o) {
|
||||
return getTrust().compareTo(o.getTrust());
|
||||
}
|
||||
|
||||
public static class AxolotlKey {
|
||||
|
||||
|
||||
public final byte[] key;
|
||||
public final boolean prekey;
|
||||
|
||||
public AxolotlKey(byte[] key, boolean prekey) {
|
||||
this.key = key;
|
||||
this.prekey = prekey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
package eu.siacs.conversations.crypto.sasl;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.xml.TagWriter;
|
||||
|
||||
public class Anonymous extends SaslMechanism {
|
||||
|
||||
public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
|
||||
super(tagWriter, account, rng);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMechanism() {
|
||||
return "ANONYMOUS";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientFirstMessage() {
|
||||
return "";
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package eu.siacs.conversations.crypto.sasl;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -52,7 +51,7 @@ public class DigestMd5 extends SaslMechanism {
|
||||
+ account.getPassword();
|
||||
final MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
|
||||
final String cNonce = new BigInteger(100, rng).toString(32);
|
||||
final String cNonce = CryptoHelper.random(100,rng);
|
||||
final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
|
||||
(":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
|
||||
final String a2 = "AUTHENTICATE:" + digestUri;
|
||||
|
@ -0,0 +1,29 @@
|
||||
package eu.siacs.conversations.crypto.sasl;
|
||||
|
||||
import android.util.Base64;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.xml.TagWriter;
|
||||
|
||||
public class External extends SaslMechanism {
|
||||
|
||||
public External(TagWriter tagWriter, Account account, SecureRandom rng) {
|
||||
super(tagWriter, account, rng);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 25;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMechanism() {
|
||||
return "EXTERNAL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientFirstMessage() {
|
||||
return Base64.encodeToString(account.getJid().toBareJid().toString().getBytes(),Base64.NO_WRAP);
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ public abstract class SaslMechanism {
|
||||
final protected Account account;
|
||||
final protected SecureRandom rng;
|
||||
|
||||
protected static enum State {
|
||||
protected enum State {
|
||||
INITIAL,
|
||||
AUTH_TEXT_SENT,
|
||||
RESPONSE_SENT,
|
||||
@ -26,6 +26,10 @@ public abstract class SaslMechanism {
|
||||
public AuthenticationException(final Exception inner) {
|
||||
super(inner);
|
||||
}
|
||||
|
||||
public AuthenticationException(final String message, final Exception exception) {
|
||||
super(message,exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidStateException extends AuthenticationException {
|
||||
|
@ -0,0 +1,233 @@
|
||||
package eu.siacs.conversations.crypto.sasl;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.util.Base64;
|
||||
import android.util.LruCache;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.xml.TagWriter;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
|
||||
abstract class ScramMechanism extends SaslMechanism {
|
||||
// TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
|
||||
private final static String GS2_HEADER = "n,,";
|
||||
private String clientFirstMessageBare;
|
||||
private final String clientNonce;
|
||||
private byte[] serverSignature = null;
|
||||
static HMac HMAC;
|
||||
static Digest DIGEST;
|
||||
private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
|
||||
private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
|
||||
|
||||
private static class KeyPair {
|
||||
final byte[] clientKey;
|
||||
final byte[] serverKey;
|
||||
|
||||
KeyPair(final byte[] clientKey, final byte[] serverKey) {
|
||||
this.clientKey = clientKey;
|
||||
this.serverKey = serverKey;
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
CACHE = new LruCache<String, KeyPair>(10) {
|
||||
protected KeyPair create(final String k) {
|
||||
// Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
|
||||
// Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()'
|
||||
// is applied to prevent commas in the strings breaking things.
|
||||
final String[] kparts = k.split(",", 4);
|
||||
try {
|
||||
final byte[] saltedPassword, serverKey, clientKey;
|
||||
saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(),
|
||||
Base64.decode(CryptoHelper.hexToString(kparts[2]), Base64.DEFAULT), Integer.valueOf(kparts[3]));
|
||||
serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
|
||||
clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
|
||||
|
||||
return new KeyPair(clientKey, serverKey);
|
||||
} catch (final InvalidKeyException | NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static final LruCache<String, KeyPair> CACHE;
|
||||
|
||||
protected State state = State.INITIAL;
|
||||
|
||||
ScramMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
|
||||
super(tagWriter, account, rng);
|
||||
|
||||
// This nonce should be different for each authentication attempt.
|
||||
clientNonce = CryptoHelper.random(100,rng);
|
||||
clientFirstMessageBare = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientFirstMessage() {
|
||||
if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
|
||||
clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) +
|
||||
",r=" + this.clientNonce;
|
||||
state = State.AUTH_TEXT_SENT;
|
||||
}
|
||||
return Base64.encodeToString(
|
||||
(GS2_HEADER + clientFirstMessageBare).getBytes(Charset.defaultCharset()),
|
||||
Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponse(final String challenge) throws AuthenticationException {
|
||||
switch (state) {
|
||||
case AUTH_TEXT_SENT:
|
||||
if (challenge == null) {
|
||||
throw new AuthenticationException("challenge can not be null");
|
||||
}
|
||||
byte[] serverFirstMessage;
|
||||
try {
|
||||
serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new AuthenticationException("Unable to decode server challenge",e);
|
||||
}
|
||||
final Tokenizer tokenizer = new Tokenizer(serverFirstMessage);
|
||||
String nonce = "";
|
||||
int iterationCount = -1;
|
||||
String salt = "";
|
||||
for (final String token : tokenizer) {
|
||||
if (token.charAt(1) == '=') {
|
||||
switch (token.charAt(0)) {
|
||||
case 'i':
|
||||
try {
|
||||
iterationCount = Integer.parseInt(token.substring(2));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
salt = token.substring(2);
|
||||
break;
|
||||
case 'r':
|
||||
nonce = token.substring(2);
|
||||
break;
|
||||
case 'm':
|
||||
/*
|
||||
* RFC 5802:
|
||||
* m: This attribute is reserved for future extensibility. In this
|
||||
* version of SCRAM, its presence in a client or a server message
|
||||
* MUST cause authentication failure when the attribute is parsed by
|
||||
* the other end.
|
||||
*/
|
||||
throw new AuthenticationException("Server sent reserved token: `m'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iterationCount < 0) {
|
||||
throw new AuthenticationException("Server did not send iteration count");
|
||||
}
|
||||
if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
|
||||
throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce);
|
||||
}
|
||||
if (salt.isEmpty()) {
|
||||
throw new AuthenticationException("Server sent empty salt");
|
||||
}
|
||||
|
||||
final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString(
|
||||
GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce;
|
||||
final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
|
||||
+ clientFinalMessageWithoutProof).getBytes();
|
||||
|
||||
// Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
|
||||
final KeyPair keys = CACHE.get(
|
||||
CryptoHelper.bytesToHex(account.getJid().toBareJid().toString().getBytes()) + ","
|
||||
+ CryptoHelper.bytesToHex(account.getPassword().getBytes()) + ","
|
||||
+ CryptoHelper.bytesToHex(salt.getBytes()) + ","
|
||||
+ String.valueOf(iterationCount)
|
||||
);
|
||||
if (keys == null) {
|
||||
throw new AuthenticationException("Invalid keys generated");
|
||||
}
|
||||
final byte[] clientSignature;
|
||||
try {
|
||||
serverSignature = hmac(keys.serverKey, authMessage);
|
||||
final byte[] storedKey = digest(keys.clientKey);
|
||||
|
||||
clientSignature = hmac(storedKey, authMessage);
|
||||
|
||||
} catch (final InvalidKeyException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
|
||||
final byte[] clientProof = new byte[keys.clientKey.length];
|
||||
|
||||
for (int i = 0; i < clientProof.length; i++) {
|
||||
clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
|
||||
}
|
||||
|
||||
|
||||
final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" +
|
||||
Base64.encodeToString(clientProof, Base64.NO_WRAP);
|
||||
state = State.RESPONSE_SENT;
|
||||
return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
|
||||
case RESPONSE_SENT:
|
||||
try {
|
||||
final String clientCalculatedServerFinalMessage = "v=" +
|
||||
Base64.encodeToString(serverSignature, Base64.NO_WRAP);
|
||||
if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
|
||||
throw new Exception();
|
||||
}
|
||||
state = State.VALID_SERVER_RESPONSE;
|
||||
return "";
|
||||
} catch(Exception e) {
|
||||
throw new AuthenticationException("Server final message does not match calculated final message");
|
||||
}
|
||||
default:
|
||||
throw new InvalidStateException(state);
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized byte[] hmac(final byte[] key, final byte[] input)
|
||||
throws InvalidKeyException {
|
||||
HMAC.init(new KeyParameter(key));
|
||||
HMAC.update(input, 0, input.length);
|
||||
final byte[] out = new byte[HMAC.getMacSize()];
|
||||
HMAC.doFinal(out, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static synchronized byte[] digest(byte[] bytes) {
|
||||
DIGEST.reset();
|
||||
DIGEST.update(bytes, 0, bytes.length);
|
||||
final byte[] out = new byte[DIGEST.getDigestSize()];
|
||||
DIGEST.doFinal(out, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the
|
||||
* pseudorandom function (PRF) and with dkLen == output length of
|
||||
* HMAC() == output length of H().
|
||||
*/
|
||||
private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations)
|
||||
throws InvalidKeyException {
|
||||
byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE));
|
||||
byte[] out = u.clone();
|
||||
for (int i = 1; i < iterations; i++) {
|
||||
u = hmac(key, u);
|
||||
for (int j = 0; j < u.length; j++) {
|
||||
out[j] ^= u[j];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|