Compare commits
1090 Commits
v4.51.2-co
...
v4.68.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbecd2a2fc | ||
|
|
3772dd40ac | ||
|
|
f69a0f0261 | ||
|
|
cb323ffb84 | ||
|
|
0db73e71a0 | ||
|
|
eeb0c838db | ||
|
|
dc48ee5aed | ||
|
|
c0acfa57a9 | ||
|
|
3e166ef927 | ||
|
|
4942d83de5 | ||
|
|
4c30b39e71 | ||
|
|
e55f4fe6b6 | ||
|
|
aff74cffa0 | ||
|
|
8b29bb8664 | ||
|
|
3cee57b6c2 | ||
|
|
857f4a4fc8 | ||
|
|
a942293a74 | ||
|
|
550b121990 | ||
|
|
cc84901a49 | ||
|
|
9d3764c5d9 | ||
|
|
0950235ccd | ||
|
|
8ed7fc894e | ||
|
|
e504ffa225 | ||
|
|
9c63b37bb4 | ||
|
|
5c110ca359 | ||
|
|
1ab61beeb9 | ||
|
|
8e45a546c9 | ||
|
|
745a7f76ea | ||
|
|
8cb9ab3204 | ||
|
|
12533d1414 | ||
|
|
bd1c164d57 | ||
|
|
7446c2096d | ||
|
|
8ce5c4b885 | ||
|
|
ab76112f5f | ||
|
|
9c54e39eae | ||
|
|
61eab44474 | ||
|
|
f6285ec710 | ||
|
|
ed878ec4b4 | ||
|
|
e38d41d67a | ||
|
|
3d237d72bd | ||
|
|
8044d2390c | ||
|
|
6b82e6b5ac | ||
|
|
842e6a93e2 | ||
|
|
f140f054e5 | ||
|
|
5cd4726e23 | ||
|
|
bccc58d693 | ||
|
|
e25f1c1481 | ||
|
|
fc4e690996 | ||
|
|
dadb2f9d37 | ||
|
|
5bf15b0587 | ||
|
|
5f9c0c3204 | ||
|
|
dfa4f0c309 | ||
|
|
f0063b4b0d | ||
|
|
5dc51c34ea | ||
|
|
5bf7a55bfa | ||
|
|
eb9ae8d5dc | ||
|
|
2a133587cc | ||
|
|
0e4a19c368 | ||
|
|
813c820227 | ||
|
|
870cee5707 | ||
|
|
4e55d2d941 | ||
|
|
8e962bf992 | ||
|
|
0815715f7b | ||
|
|
85e4697b7f | ||
|
|
16fdb9bf4c | ||
|
|
46f3d50a54 | ||
|
|
3a38240fb2 | ||
|
|
662f0b8fb6 | ||
|
|
96ce42ae91 | ||
|
|
93f587b851 | ||
|
|
89a940ec81 | ||
|
|
a33771b15d | ||
|
|
9a566e5559 | ||
|
|
6e75d42a92 | ||
|
|
575413cac9 | ||
|
|
6a9476c6d0 | ||
|
|
5468f1705c | ||
|
|
5ea132e712 | ||
|
|
8128fcf8bc | ||
|
|
e89655f793 | ||
|
|
2db2b068c4 | ||
|
|
a59e214317 | ||
|
|
ae2b6e4d7a | ||
|
|
b10fc6a0b0 | ||
|
|
70977e5228 | ||
|
|
4482391574 | ||
|
|
bd078fc883 | ||
|
|
644af87782 | ||
|
|
1ce36c1069 | ||
|
|
0a71005ecc | ||
|
|
698618a4b3 | ||
|
|
f9642dd79f | ||
|
|
85d1a3c016 | ||
|
|
38c74c81a6 | ||
|
|
4c04991b70 | ||
|
|
293a339fed | ||
|
|
5255a527f9 | ||
|
|
9440dfb66c | ||
|
|
7a019eee19 | ||
|
|
93f56a5dc8 | ||
|
|
68264228b8 | ||
|
|
66c1b8e26c | ||
|
|
5776c048ea | ||
|
|
76dd09bc50 | ||
|
|
73d18d3abd | ||
|
|
c1c9d0c8a3 | ||
|
|
64420ead7c | ||
|
|
6d035c6888 | ||
|
|
833ca8cce9 | ||
|
|
d02d506b13 | ||
|
|
f306056e5d | ||
|
|
58ec669d15 | ||
|
|
d1b61bfed3 | ||
|
|
325e0c6781 | ||
|
|
8d66cd52b5 | ||
|
|
4b9277629c | ||
|
|
6515a6188b | ||
|
|
8b3ca52502 | ||
|
|
fae003e085 | ||
|
|
4b961d2d8f | ||
|
|
e27fc512b4 | ||
|
|
8f0f600b6b | ||
|
|
5950610690 | ||
|
|
fce3df0c82 | ||
|
|
e2021231c6 | ||
|
|
f61dd7509e | ||
|
|
db2b64e58c | ||
|
|
d70999c386 | ||
|
|
eb6ecc59ab | ||
|
|
1e0e2fadfd | ||
|
|
4325f714b9 | ||
|
|
137cd45497 | ||
|
|
f3dbe4416f | ||
|
|
7fb55c0f51 | ||
|
|
fdc6cbc507 | ||
|
|
072085ae82 | ||
|
|
04a8996348 | ||
|
|
c26dcc2618 | ||
|
|
a4dc340bbc | ||
|
|
3c069fb588 | ||
|
|
1fe38f5ed1 | ||
|
|
841c9424e9 | ||
|
|
9c44a0c7d3 | ||
|
|
2883d2eb31 | ||
|
|
f5aade943e | ||
|
|
d17c3f39d0 | ||
|
|
9ac9ace6b8 | ||
|
|
c9d2cef58d | ||
|
|
a9e30eefdc | ||
|
|
1a895db9bd | ||
|
|
a955bc3b9b | ||
|
|
96e888a4f5 | ||
|
|
99ff0c1e3c | ||
|
|
599e89b1f9 | ||
|
|
33c527f15e | ||
|
|
eb02dacfdc | ||
|
|
e6a0e5b858 | ||
|
|
545ba80697 | ||
|
|
1e250ee95c | ||
|
|
5a12eedc2c | ||
|
|
5605fde777 | ||
|
|
9ac142688a | ||
|
|
2791790bf5 | ||
|
|
1752972be9 | ||
|
|
c877aba09f | ||
|
|
70e33518a9 | ||
|
|
cb81a9f783 | ||
|
|
b6b499d865 | ||
|
|
6704ad8193 | ||
|
|
942628a261 | ||
|
|
4ea8bac10d | ||
|
|
eafccc5721 | ||
|
|
a01bec3a11 | ||
|
|
3868175b85 | ||
|
|
904cb01067 | ||
|
|
5c0cb425a6 | ||
|
|
9dbb2ef630 | ||
|
|
bafd2817ee | ||
|
|
3380293923 | ||
|
|
a549c1ec8b | ||
|
|
ad84997ce0 | ||
|
|
42e2576813 | ||
|
|
31b995fa98 | ||
|
|
0364bec995 | ||
|
|
aa39f3d0a3 | ||
|
|
db545f43ea | ||
|
|
bbe003a454 | ||
|
|
819f0f68f6 | ||
|
|
8c0160937b | ||
|
|
6de789dfe3 | ||
|
|
afa2bb3bf5 | ||
|
|
89e66c0741 | ||
|
|
0dc4afba99 | ||
|
|
152578e576 | ||
|
|
63d6ab6fa7 | ||
|
|
75c8c59d78 | ||
|
|
87a59b6a9b | ||
|
|
2001fa86cf | ||
|
|
52747782a7 | ||
|
|
66f2668326 | ||
|
|
b262efc24c | ||
|
|
ce7ad76447 | ||
|
|
9e98b6616e | ||
|
|
f4c9eaa904 | ||
|
|
f8a0988e5f | ||
|
|
bf919207ed | ||
|
|
dac6b5c992 | ||
|
|
7f8043777e | ||
|
|
854b3feb36 | ||
|
|
22447e6ddb | ||
|
|
be2ec36e1f | ||
|
|
98cf16479d | ||
|
|
584735cbd0 | ||
|
|
3741493cb7 | ||
|
|
4ea861fe5c | ||
|
|
cd3df4d3c1 | ||
|
|
881a1edccb | ||
|
|
1b7b574289 | ||
|
|
d1d7498447 | ||
|
|
50c18727e7 | ||
|
|
e9bfde470a | ||
|
|
68f718a210 | ||
|
|
c3e528ad4b | ||
|
|
28af97c400 | ||
|
|
c2e4c343ab | ||
|
|
8a78589c2f | ||
|
|
841ee18435 | ||
|
|
71f54701d2 | ||
|
|
1c99939dfa | ||
|
|
50462cecd0 | ||
|
|
aa6a32f023 | ||
|
|
c4dc9064e3 | ||
|
|
bc5be10a0e | ||
|
|
98d9b57379 | ||
|
|
021a16050a | ||
|
|
555104aff0 | ||
|
|
95d63b78f4 | ||
|
|
80f9e1f4f1 | ||
|
|
a77997a4de | ||
|
|
ec4eb8e2a9 | ||
|
|
1bdeade71e | ||
|
|
629ba105cb | ||
|
|
891a1af995 | ||
|
|
0fbc6ac151 | ||
|
|
a6384d1b73 | ||
|
|
2fb9514890 | ||
|
|
fe89794505 | ||
|
|
08800c9faf | ||
|
|
469a4700d2 | ||
|
|
6707f974a5 | ||
|
|
c122cada2b | ||
|
|
96f02d8c95 | ||
|
|
dd717b60b8 | ||
|
|
3c20c7f4b4 | ||
|
|
1a09e70a04 | ||
|
|
027453bbd2 | ||
|
|
b621efa4a5 | ||
|
|
2915e4698c | ||
|
|
b687b1a4c5 | ||
|
|
b53827f32b | ||
|
|
d9641128a8 | ||
|
|
dfb5562142 | ||
|
|
d467c04749 | ||
|
|
3d7cffef2b | ||
|
|
f2fe81d9b5 | ||
|
|
cf98a22269 | ||
|
|
49f75d7036 | ||
|
|
ce940235b0 | ||
|
|
f5626f678d | ||
|
|
b3a59c3946 | ||
|
|
93c390c4fc | ||
|
|
941ab5a98f | ||
|
|
2ecdf803c0 | ||
|
|
5b2a399392 | ||
|
|
a9ea1d7606 | ||
|
|
1ce8ac2de6 | ||
|
|
e2019579fb | ||
|
|
fb3c6e56ee | ||
|
|
3fad007ae0 | ||
|
|
8891b6c930 | ||
|
|
400c592acf | ||
|
|
e13f3254ad | ||
|
|
bf40a07bb9 | ||
|
|
8f3a6b8479 | ||
|
|
7642b7cc72 | ||
|
|
e12ea60d85 | ||
|
|
0b13c4aed6 | ||
|
|
47919382e9 | ||
|
|
d60d67ee7e | ||
|
|
559aa687a5 | ||
|
|
bc0761f002 | ||
|
|
c0c2fc0eba | ||
|
|
44fe43c74c | ||
|
|
53a2a5d693 | ||
|
|
2334c26cbb | ||
|
|
0b6dde46d9 | ||
|
|
98d9d81aff | ||
|
|
736a62b632 | ||
|
|
cea6a83d8a | ||
|
|
2751fd7efc | ||
|
|
2822042eeb | ||
|
|
dc46d88ddd | ||
|
|
e04f76b558 | ||
|
|
a758056494 | ||
|
|
1ecdea5db3 | ||
|
|
e1bb773d85 | ||
|
|
7e934eff5d | ||
|
|
cfdf5603af | ||
|
|
45bfb8c6b6 | ||
|
|
65608a51b8 | ||
|
|
b6314597fe | ||
|
|
20a588199a | ||
|
|
59916f1e95 | ||
|
|
8b91f8f9e7 | ||
|
|
cbc3cce66f | ||
|
|
b4b63b5860 | ||
|
|
b9ae15a890 | ||
|
|
d955389c46 | ||
|
|
975eb885c1 | ||
|
|
a3aed96757 | ||
|
|
dc70bfabaf | ||
|
|
6932340671 | ||
|
|
f6637b7caf | ||
|
|
4f4be44caa | ||
|
|
7832497ba7 | ||
|
|
7d06e2395f | ||
|
|
3a479d7eef | ||
|
|
8fe8a1e9ee | ||
|
|
2d8b2e7fb0 | ||
|
|
9c0365f92c | ||
|
|
b48abb08d2 | ||
|
|
d8f3e032c7 | ||
|
|
8dbcb255ad | ||
|
|
db06cbbc86 | ||
|
|
98ab23c1a3 | ||
|
|
d0ca9ba6a6 | ||
|
|
b242368675 | ||
|
|
664527ce63 | ||
|
|
99e4f80be0 | ||
|
|
702dae9fcd | ||
|
|
48fe1ba559 | ||
|
|
382ac7ba0d | ||
|
|
a46f47f352 | ||
|
|
e984d8a42c | ||
|
|
554bad6b8d | ||
|
|
ed13c97ad7 | ||
|
|
d33873d59a | ||
|
|
1234899ea1 | ||
|
|
13027dc44b | ||
|
|
5b4d74b7fe | ||
|
|
18c7bc2b5b | ||
|
|
bbbee0f372 | ||
|
|
cf9d090154 | ||
|
|
718471917f | ||
|
|
bb97407cde | ||
|
|
92ce678e29 | ||
|
|
e100aea2c7 | ||
|
|
fea3b6cb4a | ||
|
|
afbc132faa | ||
|
|
b27198286d | ||
|
|
ac93d81032 | ||
|
|
9981e5ca76 | ||
|
|
7dd3efeb53 | ||
|
|
d38d702adf | ||
|
|
04a000a8a8 | ||
|
|
3bbf0741ee | ||
|
|
e9a336100b | ||
|
|
fb600e9829 | ||
|
|
4a455ff958 | ||
|
|
707e238e5c | ||
|
|
90f22a4b66 | ||
|
|
b4f134adf7 | ||
|
|
1e00fc6149 | ||
|
|
f52133a69c | ||
|
|
91b142e0d9 | ||
|
|
26a9dd98c1 | ||
|
|
99e38e1d23 | ||
|
|
a2d8a25fd9 | ||
|
|
d86d625bcc | ||
|
|
18e3fb6609 | ||
|
|
da33ba0ed5 | ||
|
|
66f021d01a | ||
|
|
40231ea45f | ||
|
|
cd80a47c04 | ||
|
|
1033bd7bda | ||
|
|
b4f60f3acb | ||
|
|
bed3b571cc | ||
|
|
c8dd4e5254 | ||
|
|
514048171b | ||
|
|
32e9901592 | ||
|
|
d83f86a469 | ||
|
|
403d53586c | ||
|
|
6acae58694 | ||
|
|
a3f9737e63 | ||
|
|
263af7c139 | ||
|
|
7f2439f1e9 | ||
|
|
ae87d23003 | ||
|
|
3192cc0aac | ||
|
|
6102e9aa72 | ||
|
|
f4a152b0fe | ||
|
|
2b11bca7dc | ||
|
|
07d19f38e3 | ||
|
|
cd228c439e | ||
|
|
7a859c8961 | ||
|
|
543f38c75d | ||
|
|
f7b150f2d2 | ||
|
|
11328f643f | ||
|
|
f270a6b8c4 | ||
|
|
3fec23fd36 | ||
|
|
e01838e996 | ||
|
|
f70e41e7cd | ||
|
|
c4ec0c9897 | ||
|
|
989b071a6d | ||
|
|
c39751f9db | ||
|
|
dbf74a2234 | ||
|
|
837230d72d | ||
|
|
f544ec4126 | ||
|
|
79dbf85c1e | ||
|
|
61fe6cc961 | ||
|
|
70c88b68e2 | ||
|
|
d70c33d20f | ||
|
|
6b2e000e61 | ||
|
|
b9f11dafff | ||
|
|
9b32eaeb8a | ||
|
|
a99c0d438e | ||
|
|
c634c24afb | ||
|
|
2ddd1437cf | ||
|
|
9da309ca48 | ||
|
|
cfcd451db7 | ||
|
|
5ab72fd1a9 | ||
|
|
daace9bd1a | ||
|
|
69adcd1d69 | ||
|
|
0711a22188 | ||
|
|
3a06412cd8 | ||
|
|
51c82702e2 | ||
|
|
1b01196ec6 | ||
|
|
1cd6b58ece | ||
|
|
ea8e13b1c8 | ||
|
|
f392229393 | ||
|
|
a299bafe89 | ||
|
|
d2bf539504 | ||
|
|
903c3989b9 | ||
|
|
00996f0d7a | ||
|
|
4aded3a436 | ||
|
|
9acdc37729 | ||
|
|
d4cdcbe54f | ||
|
|
6fa2a0f411 | ||
|
|
558a8e4a14 | ||
|
|
8947b82034 | ||
|
|
56551025e9 | ||
|
|
befb4939d5 | ||
|
|
289f7aba63 | ||
|
|
28bd245b96 | ||
|
|
c5e7300df2 | ||
|
|
fe25d941bb | ||
|
|
4cda267f3b | ||
|
|
82ba7e2b8b | ||
|
|
41ebaf3938 | ||
|
|
090c400037 | ||
|
|
12b1232ac0 | ||
|
|
204a84c522 | ||
|
|
526afd539b | ||
|
|
d708984abd | ||
|
|
9d39db6428 | ||
|
|
67a8ec0d39 | ||
|
|
297a7d0ef8 | ||
|
|
4712833853 | ||
|
|
11d17f7496 | ||
|
|
36df3f234f | ||
|
|
098b298646 | ||
|
|
2f9320989a | ||
|
|
ec8d5defd4 | ||
|
|
981676c7f8 | ||
|
|
7c5ae57784 | ||
|
|
fc7be87468 | ||
|
|
e55d8007fc | ||
|
|
43b7aa2d52 | ||
|
|
cd1bad0718 | ||
|
|
6b47618351 | ||
|
|
b6d384120d | ||
|
|
1268b26c1f | ||
|
|
f1233bfddc | ||
|
|
1aa3e6afea | ||
|
|
ce21eb241a | ||
|
|
f96fb72eb1 | ||
|
|
207c467c6b | ||
|
|
9d1d9e33ed | ||
|
|
e4a76c0690 | ||
|
|
124c3e25e9 | ||
|
|
5cb1201903 | ||
|
|
bb6ca80d5a | ||
|
|
dc7c54a1f8 | ||
|
|
23401440bf | ||
|
|
f8f959e05a | ||
|
|
edbd4d2d03 | ||
|
|
a0b4065be3 | ||
|
|
1b2f964f32 | ||
|
|
eaf5280d99 | ||
|
|
d435da980f | ||
|
|
8d3a91f3a4 | ||
|
|
b80c339c5a | ||
|
|
34159fc9da | ||
|
|
b509ee9ee0 | ||
|
|
a6819448b9 | ||
|
|
f2847f9aa5 | ||
|
|
8f01e5e1c3 | ||
|
|
acb2f43620 | ||
|
|
62ac65e4d8 | ||
|
|
8f183bdcdc | ||
|
|
3d135d155e | ||
|
|
090c811391 | ||
|
|
2a9e8dc525 | ||
|
|
cb0b22cf2c | ||
|
|
5aba3517ce | ||
|
|
726f665388 | ||
|
|
e2ac55e9ac | ||
|
|
fa5729bac6 | ||
|
|
e714cb6423 | ||
|
|
35a0162d5c | ||
|
|
76740adc3f | ||
|
|
1c814141a2 | ||
|
|
5545daf992 | ||
|
|
d300615d90 | ||
|
|
908a5260c2 | ||
|
|
7aac6644c3 | ||
|
|
3b673c07a0 | ||
|
|
d726da822c | ||
|
|
7894f72b0f | ||
|
|
4c5822ac67 | ||
|
|
b917cccbee | ||
|
|
01d2d05d8e | ||
|
|
4de86cb6cf | ||
|
|
8861ad76ed | ||
|
|
ef86372635 | ||
|
|
ccff7b1148 | ||
|
|
ed0825112d | ||
|
|
b8df90531f | ||
|
|
f099c3591c | ||
|
|
ed33e048ad | ||
|
|
7fd3bfa30c | ||
|
|
07a492a32c | ||
|
|
11fffbd79e | ||
|
|
eff564ad88 | ||
|
|
d3d53e6099 | ||
|
|
53d122ed55 | ||
|
|
1778c1ef7d | ||
|
|
a510bc74e6 | ||
|
|
a9ecdbdfec | ||
|
|
06ab3cf013 | ||
|
|
3db5da1c8d | ||
|
|
5937a50b6d | ||
|
|
b4191ee5cc | ||
|
|
c63e42715e | ||
|
|
26e582d806 | ||
|
|
ee9270845a | ||
|
|
6cf33897c0 | ||
|
|
2161bbb8fa | ||
|
|
b75088874e | ||
|
|
9ac1897880 | ||
|
|
36c43ed2fa | ||
|
|
8084822f16 | ||
|
|
959718618f | ||
|
|
75f3fe0cec | ||
|
|
b800477365 | ||
|
|
b191341c57 | ||
|
|
88a40be901 | ||
|
|
3fef58057e | ||
|
|
b156e4a79a | ||
|
|
30ac264cd3 | ||
|
|
a9b00e1cd3 | ||
|
|
d94fc4bc13 | ||
|
|
40b5339ef8 | ||
|
|
86f0456e8c | ||
|
|
48a693793f | ||
|
|
ff28d72db6 | ||
|
|
456857bbbd | ||
|
|
7f17b66a6c | ||
|
|
310ec8f296 | ||
|
|
0c2afa9438 | ||
|
|
c3832cf8b1 | ||
|
|
a2de8a2a05 | ||
|
|
3b601896d2 | ||
|
|
e1a90bcb00 | ||
|
|
2b65916344 | ||
|
|
f149005026 | ||
|
|
5eb663aa1b | ||
|
|
12b7d6c0e3 | ||
|
|
723639d928 | ||
|
|
e0502c24e1 | ||
|
|
358d6333c3 | ||
|
|
0b279d1df3 | ||
|
|
8e0fba7992 | ||
|
|
d5419ec9fa | ||
|
|
33e3f78be6 | ||
|
|
3c5ad519dd | ||
|
|
17c5b858b5 | ||
|
|
f6f6496c9c | ||
|
|
b1d725e23a | ||
|
|
a74622997e | ||
|
|
b1a200001e | ||
|
|
3b1041fa1f | ||
|
|
a83ccc18bb | ||
|
|
618b1b5ace | ||
|
|
14858adc88 | ||
|
|
c07f35f3aa | ||
|
|
87eab27996 | ||
|
|
b7296a4fe3 | ||
|
|
3fb9ae1fb4 | ||
|
|
9705939489 | ||
|
|
eca67b1204 | ||
|
|
c59fc3581a | ||
|
|
e00f8c94ff | ||
|
|
4186153f0c | ||
|
|
6c01807f4f | ||
|
|
9d35fb397b | ||
|
|
c9f2f57427 | ||
|
|
c862ab0c56 | ||
|
|
7aaaa57c14 | ||
|
|
11b6394a87 | ||
|
|
bdd48fd2df | ||
|
|
e99af75400 | ||
|
|
321440e13f | ||
|
|
0556d984e0 | ||
|
|
0ba1f66136 | ||
|
|
7562555687 | ||
|
|
668ccfcd12 | ||
|
|
9c0337c4ef | ||
|
|
3fde06ab0f | ||
|
|
73959f328a | ||
|
|
cca85bfee3 | ||
|
|
575caa53d3 | ||
|
|
33874a8866 | ||
|
|
b8e909a134 | ||
|
|
5193a5d309 | ||
|
|
7db288b9aa | ||
|
|
9f033e64aa | ||
|
|
5a15ba97dc | ||
|
|
ce6ec72683 | ||
|
|
eedbcdd564 | ||
|
|
0ca2848e01 | ||
|
|
208275b6a9 | ||
|
|
4bdcaa72cd | ||
|
|
8c6001fa5a | ||
|
|
c4e88abce1 | ||
|
|
eea7174f1d | ||
|
|
3f7d0688fc | ||
|
|
6d319618c6 | ||
|
|
4250fa707b | ||
|
|
7734cd2c8f | ||
|
|
57467bb338 | ||
|
|
8ad61a52b9 | ||
|
|
9742a212a2 | ||
|
|
fd21fc1a31 | ||
|
|
1b5a0ab9f3 | ||
|
|
f466fef20a | ||
|
|
9bc70adbbd | ||
|
|
6f39f9849a | ||
|
|
5bc950ed28 | ||
|
|
b80d460a8f | ||
|
|
3f555ce5e2 | ||
|
|
9513b476ef | ||
|
|
8f9e79ae37 | ||
|
|
53b681ef67 | ||
|
|
9a8094cb8a | ||
|
|
00ee6d0bbd | ||
|
|
83f6640bd3 | ||
|
|
2afb939ee6 | ||
|
|
7c442865c5 | ||
|
|
b3d57edb24 | ||
|
|
6d6e017c71 | ||
|
|
fc6b5c1d7c | ||
|
|
6ecd3b59fd | ||
|
|
456bcf3d57 | ||
|
|
f12a9b9ac7 | ||
|
|
00b6a222bd | ||
|
|
b8ccc4453e | ||
|
|
dbb31420af | ||
|
|
35f4f3f81e | ||
|
|
acbfff89d3 | ||
|
|
6b37675a81 | ||
|
|
a471ffa6d8 | ||
|
|
7bf090fdab | ||
|
|
4e0279200f | ||
|
|
78055e3ccb | ||
|
|
f5e6fd6340 | ||
|
|
2d60d5fb1f | ||
|
|
c6dd25a119 | ||
|
|
68d29d9a0f | ||
|
|
1d63970a25 | ||
|
|
2b1ffac564 | ||
|
|
e2d3a43593 | ||
|
|
8e13403cca | ||
|
|
3c6a7b76ca | ||
|
|
428128651e | ||
|
|
326678f214 | ||
|
|
1f994495f8 | ||
|
|
fb1637006d | ||
|
|
37a35e8f70 | ||
|
|
1290d0ead9 | ||
|
|
ef0f26b64c | ||
|
|
485d211768 | ||
|
|
f1ea035197 | ||
|
|
6f961ade74 | ||
|
|
b8e17e0116 | ||
|
|
040e1fe8f6 | ||
|
|
e9c92bdf51 | ||
|
|
48c33f3dcd | ||
|
|
6b2bc924dd | ||
|
|
a65c4f90f4 | ||
|
|
04bb4b351a | ||
|
|
e02e4d52b4 | ||
|
|
6f3c4434f6 | ||
|
|
711715ca1e | ||
|
|
d6000af843 | ||
|
|
9b0954a898 | ||
|
|
42a2c33fd7 | ||
|
|
a4d18a18d9 | ||
|
|
bf32409d4e | ||
|
|
e38aec225f | ||
|
|
995b7a4712 | ||
|
|
9fe3026941 | ||
|
|
520658e1b8 | ||
|
|
f822d8eddb | ||
|
|
2f879ce4d6 | ||
|
|
24528bf101 | ||
|
|
822682caba | ||
|
|
5dc3cc65a8 | ||
|
|
0f80caffb5 | ||
|
|
6c428b2777 | ||
|
|
c9be37b84a | ||
|
|
87ea2f86c0 | ||
|
|
7e80be5ca0 | ||
|
|
989a818a67 | ||
|
|
af2e17df9e | ||
|
|
728ec1c16d | ||
|
|
f859c5b1b5 | ||
|
|
ab600d7df1 | ||
|
|
4644f64fd6 | ||
|
|
c274312265 | ||
|
|
f8e63098a2 | ||
|
|
264d353ec2 | ||
|
|
2b58dcbe7f | ||
|
|
dc791487c5 | ||
|
|
5637f132d4 | ||
|
|
9e6cca1cd0 | ||
|
|
640c82d517 | ||
|
|
20d1a93b09 | ||
|
|
f5d1b11bda | ||
|
|
66c7f8bcb2 | ||
|
|
d8fa46c558 | ||
|
|
10bfc8a753 | ||
|
|
9848599807 | ||
|
|
2e38ebcfbb | ||
|
|
f875623cd0 | ||
|
|
e6f9cb9929 | ||
|
|
6aac3baa55 | ||
|
|
a860315587 | ||
|
|
a73a73e42c | ||
|
|
a3358e5b21 | ||
|
|
7e9e2fead2 | ||
|
|
0269a3eb6f | ||
|
|
f449a45912 | ||
|
|
f69d4ccd22 | ||
|
|
0e2df2adbb | ||
|
|
d46894e5db | ||
|
|
951a61117a | ||
|
|
7a038ab09d | ||
|
|
707a2aca0a | ||
|
|
624837fcf1 | ||
|
|
e3ea36c76f | ||
|
|
453996c374 | ||
|
|
8add9ba0a6 | ||
|
|
da11b56eab | ||
|
|
19377c2132 | ||
|
|
b2bff39fe1 | ||
|
|
5f7075d39a | ||
|
|
40d9d663ec | ||
|
|
31f9b77c32 | ||
|
|
690a66a093 | ||
|
|
e7e7d36774 | ||
|
|
f95a37956c | ||
|
|
1e2a27f902 | ||
|
|
d90e3dc210 | ||
|
|
5df4b56c0d | ||
|
|
436da1cb32 | ||
|
|
4d0dbbc6cd | ||
|
|
033bf77cbb | ||
|
|
1068c3ca7e | ||
|
|
df4422369d | ||
|
|
a62183c9e0 | ||
|
|
de48cf8243 | ||
|
|
acd4fc4518 | ||
|
|
da59ed019f | ||
|
|
e73b174d1d | ||
|
|
2753a22e3a | ||
|
|
79fc33630b | ||
|
|
bf5331ba6e | ||
|
|
3be47d3e54 | ||
|
|
f9de131017 | ||
|
|
f1f505d41c | ||
|
|
51603be5ec | ||
|
|
2152b4a2cd | ||
|
|
a70023a32b | ||
|
|
5038210d78 | ||
|
|
28bbfd88b2 | ||
|
|
d05a71c8fe | ||
|
|
245b0a7e50 | ||
|
|
ceb9e4aee2 | ||
|
|
d2e94dad7e | ||
|
|
240b2108f3 | ||
|
|
f68d99d16d | ||
|
|
44e845c875 | ||
|
|
d8e2368a18 | ||
|
|
172a43679d | ||
|
|
82305ce2b3 | ||
|
|
eaf73edcad | ||
|
|
543a4ee177 | ||
|
|
fd2a464bae | ||
|
|
b06152ba58 | ||
|
|
c24d285cd3 | ||
|
|
be39cd653e | ||
|
|
6813f47bc1 | ||
|
|
8e795c4177 | ||
|
|
9c96afee09 | ||
|
|
d507be0ab0 | ||
|
|
75a52f801a | ||
|
|
d3b123f3a9 | ||
|
|
da3cdd984b | ||
|
|
6184e5f828 | ||
|
|
133bd44b85 | ||
|
|
0c254c9621 | ||
|
|
1faf196f82 | ||
|
|
81c7887d47 | ||
|
|
e62e630987 | ||
|
|
739e38a047 | ||
|
|
8c23b17517 | ||
|
|
fda8f3e1ce | ||
|
|
9e5f64c431 | ||
|
|
dc689d325b | ||
|
|
0a883dc234 | ||
|
|
3824e90997 | ||
|
|
5158a15379 | ||
|
|
1bae79af5b | ||
|
|
58b7612987 | ||
|
|
9506da6dd3 | ||
|
|
4ea886d05a | ||
|
|
7a52fccfd1 | ||
|
|
f79d308a9f | ||
|
|
5aa64641d2 | ||
|
|
ae594a0400 | ||
|
|
eeece55b45 | ||
|
|
9fbc50d26f | ||
|
|
16ebf0556a | ||
|
|
bb104b5763 | ||
|
|
9bac88697b | ||
|
|
7ab240643e | ||
|
|
70d5b798b2 | ||
|
|
4e7a92637c | ||
|
|
2e19d0459b | ||
|
|
0970fd7040 | ||
|
|
81532cad95 | ||
|
|
dcb5f7b211 | ||
|
|
40fd7ca332 | ||
|
|
7f867a6185 | ||
|
|
23e55ac5f7 | ||
|
|
455974cb05 | ||
|
|
66a668f55b | ||
|
|
7ecb50a3fe | ||
|
|
f10d1eac61 | ||
|
|
b92c389a5b | ||
|
|
9dfc57c462 | ||
|
|
3ea1492d67 | ||
|
|
c041614d1f | ||
|
|
d9c5907ea9 | ||
|
|
0b6a52277d | ||
|
|
944adb5d7c | ||
|
|
ac4129c1e1 | ||
|
|
0220a88ea5 | ||
|
|
6d33763ec9 | ||
|
|
3cedadbb97 | ||
|
|
5a6d339a89 | ||
|
|
905d8a4f33 | ||
|
|
917d312ea0 | ||
|
|
ddc01b539f | ||
|
|
38e4733433 | ||
|
|
cbd7160e23 | ||
|
|
f2b3acb0c9 | ||
|
|
49c7b5c442 | ||
|
|
7b7d180207 | ||
|
|
21cf390d0e | ||
|
|
5e59f77f83 | ||
|
|
62814490b3 | ||
|
|
e5fedb8163 | ||
|
|
cfc74c1080 | ||
|
|
eaf53ad3b9 | ||
|
|
a1f0c198a7 | ||
|
|
6b1e48e485 | ||
|
|
83ea919434 | ||
|
|
c94e93b916 | ||
|
|
49d418bb39 | ||
|
|
23adda1817 | ||
|
|
7c729c2c4e | ||
|
|
ecf7a416eb | ||
|
|
2f87f2bb62 | ||
|
|
39f4102e81 | ||
|
|
4fcd6b15ed | ||
|
|
8f9ed4bc40 | ||
|
|
73dedd79d2 | ||
|
|
2754b397d5 | ||
|
|
7d949ee8fd | ||
|
|
28e2f22550 | ||
|
|
eaa1760511 | ||
|
|
53dfd3f4c0 | ||
|
|
ed2b049ad4 | ||
|
|
092fb40333 | ||
|
|
36a4225858 | ||
|
|
e551ea8bd9 | ||
|
|
5a28b1bf1c | ||
|
|
4cd1129c92 | ||
|
|
a5d7bc4efc | ||
|
|
1ff5b2af2a | ||
|
|
82446ce30a | ||
|
|
6465248483 | ||
|
|
48e7f82466 | ||
|
|
6fef21ebc0 | ||
|
|
837e594607 | ||
|
|
ab0cb55b80 | ||
|
|
2d24c8c525 | ||
|
|
40383f3733 | ||
|
|
e14861d79d | ||
|
|
b29b3d0432 | ||
|
|
c21d4861c0 | ||
|
|
a6786e5c2b | ||
|
|
77caa9e9d4 | ||
|
|
835ef02872 | ||
|
|
279dcb1428 | ||
|
|
4a8c312e0a | ||
|
|
c2bc376f87 | ||
|
|
73160d4d26 | ||
|
|
1dd2a4e9c5 | ||
|
|
ed0c4b8de5 | ||
|
|
4f921d761d | ||
|
|
37f85d6deb | ||
|
|
e1b75c78ab | ||
|
|
5e83206e6e | ||
|
|
1ea6838db6 | ||
|
|
fb82420376 | ||
|
|
109d67956f | ||
|
|
9f7b2e2cfd | ||
|
|
22f9bfeceb | ||
|
|
ef4c7e96da | ||
|
|
02865f99a9 | ||
|
|
ef6019f13b | ||
|
|
33d02bb7b8 | ||
|
|
d34df2c1cf | ||
|
|
7fdf540742 | ||
|
|
f916aabb98 | ||
|
|
4ae7d56db4 | ||
|
|
e3878ffde7 | ||
|
|
5221b6fb43 | ||
|
|
5e0fe86858 | ||
|
|
c86ced0911 | ||
|
|
0aad82d3d7 | ||
|
|
ce86adab82 | ||
|
|
b543f727be | ||
|
|
8de7e0f198 | ||
|
|
866dacf198 | ||
|
|
3589fa381d | ||
|
|
5d54ebfaa0 | ||
|
|
fea2b6253f | ||
|
|
ba6e1ab15a | ||
|
|
1d9fff3c98 | ||
|
|
526adce603 | ||
|
|
e7f568e162 | ||
|
|
7d15c602a6 | ||
|
|
bdb30ebc48 | ||
|
|
a31da7616d | ||
|
|
f1147c10ee | ||
|
|
544a5386ad | ||
|
|
2d502213e4 | ||
|
|
55e9f8722f | ||
|
|
b8602ee004 | ||
|
|
e37c4b1f87 | ||
|
|
f7a3bb2ae8 | ||
|
|
7d70ea78cd | ||
|
|
3907ec8b51 | ||
|
|
f2b9bf0b8c | ||
|
|
fadcc606f8 | ||
|
|
92e97e61c1 | ||
|
|
4b5b9fbde8 | ||
|
|
711d22a0ed | ||
|
|
06757153b3 | ||
|
|
38597aea00 | ||
|
|
b10ce080a9 | ||
|
|
72e10ac597 | ||
|
|
5b591364ba | ||
|
|
ace1855797 | ||
|
|
ddedf73939 | ||
|
|
538014935e | ||
|
|
5e9bbf1200 | ||
|
|
29d6d3c041 | ||
|
|
53ab303fd9 | ||
|
|
24103ee856 | ||
|
|
20e368ab5e | ||
|
|
05763191ce | ||
|
|
f6685fb9c9 | ||
|
|
dbdf9602c2 | ||
|
|
6173f7049c | ||
|
|
4adacf4b98 | ||
|
|
8cb6ed26a1 | ||
|
|
fd7aa9ccfa | ||
|
|
e2a48d1714 | ||
|
|
a5c4c1e0a6 | ||
|
|
b29d03e872 | ||
|
|
dff11092ec | ||
|
|
5e9c4e8fa3 | ||
|
|
c346f32762 | ||
|
|
d2d450aff2 | ||
|
|
09af858be8 | ||
|
|
9c8a99c79c | ||
|
|
c6b9855198 | ||
|
|
c142928fad | ||
|
|
fc0cfd5188 | ||
|
|
d9c78e5c3e | ||
|
|
b449fceca0 | ||
|
|
f0d15c0bce | ||
|
|
8f031f61ea | ||
|
|
502e8559f0 | ||
|
|
8cf6f7e936 | ||
|
|
0ef01cc620 | ||
|
|
930828ef86 | ||
|
|
9ebe920195 | ||
|
|
0df36047e7 | ||
|
|
94604921f9 | ||
|
|
284fe294ac | ||
|
|
ffa01d491a | ||
|
|
1d9513e743 | ||
|
|
277c9e22f1 | ||
|
|
4e7b4da941 | ||
|
|
7e72c9c33b | ||
|
|
e0e2c3a3f5 | ||
|
|
3b5e444e76 | ||
|
|
02006e3ff5 | ||
|
|
8083596f19 | ||
|
|
6551689a0c | ||
|
|
3fbf21a34e | ||
|
|
b598431237 | ||
|
|
3b5d9a2cae | ||
|
|
3bd8aa8a86 | ||
|
|
fe5fca8eaf | ||
|
|
848101a783 | ||
|
|
d7c350f987 | ||
|
|
4c526f0b3c | ||
|
|
876ffb5b13 | ||
|
|
0b14cf3d6a | ||
|
|
c2044b36b1 | ||
|
|
f970b4acfa | ||
|
|
e69c0af613 | ||
|
|
42e2dd8b30 | ||
|
|
4fd6e7b033 | ||
|
|
3580816eac | ||
|
|
f1c2e5f194 | ||
|
|
b6d59f1d46 | ||
|
|
a3521681e7 | ||
|
|
ca10e1136c | ||
|
|
282fb2af0e | ||
|
|
1f5a597d50 | ||
|
|
a32666817c | ||
|
|
b9bd3f2b4c | ||
|
|
4173efbe5a | ||
|
|
8121c8bd41 | ||
|
|
6ca8218d55 | ||
|
|
653afbdd46 | ||
|
|
4453d1752f | ||
|
|
5782c8a58b | ||
|
|
3e041befc8 | ||
|
|
6d3847c26f | ||
|
|
4bdfc37bc1 | ||
|
|
28afe0d829 | ||
|
|
04dddd3378 | ||
|
|
23a14583fe | ||
|
|
19c83de510 | ||
|
|
e94d4b64cf | ||
|
|
7380c99f3a | ||
|
|
2961a372c3 | ||
|
|
5e2a4fb058 | ||
|
|
a16242b9f8 | ||
|
|
acfba9ac96 | ||
|
|
006343460e | ||
|
|
1cc8634cc7 |
15
.github/workflows/android.yml
vendored
@@ -1,6 +1,11 @@
|
|||||||
name: Android CI
|
name: Android CI
|
||||||
|
|
||||||
on: [push]
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- '4.**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -9,9 +14,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: set up JDK 1.8
|
- name: set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 1.8
|
||||||
|
|
||||||
|
- name: Install NDK
|
||||||
|
run: echo "y" | sudo /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;20.0.5594570" --sdk_root=${ANDROID_SDK_ROOT}
|
||||||
|
|
||||||
|
- name: Validate Gradle Wrapper
|
||||||
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew qa
|
run: ./gradlew qa
|
||||||
|
|||||||
10
.tx/config
@@ -1,10 +0,0 @@
|
|||||||
[main]
|
|
||||||
host = https://www.transifex.com
|
|
||||||
lang_map = da_DK:da-rDK,he:iw,id:in,kn_IN:kn-rIN,pt_BR:pt-rBR,pt_PT:pt,qu_EC:qu-rEC,sv_SE:sv-rSE,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW
|
|
||||||
|
|
||||||
[signal-android.master]
|
|
||||||
file_filter = res/values-<lang>/strings.xml
|
|
||||||
source_file = res/values/strings.xml
|
|
||||||
source_lang = en
|
|
||||||
type = ANDROID
|
|
||||||
|
|
||||||
@@ -1,730 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="org.thoughtcrime.securesms">
|
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2"/>
|
|
||||||
|
|
||||||
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
|
|
||||||
android:label="Access to TextSecure Secrets"
|
|
||||||
android:protectionLevel="signature" />
|
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
|
||||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
|
|
||||||
<uses-feature android:name="android.hardware.location" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.location.gps" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
|
||||||
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_PROFILE"/>
|
|
||||||
<uses-permission android:name="android.permission.WRITE_PROFILE"/>
|
|
||||||
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
|
|
||||||
tools:ignore="ProtectedPermissions"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.SEND_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
|
||||||
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
|
|
||||||
|
|
||||||
<!-- For sending/receiving events -->
|
|
||||||
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_CALENDAR"/>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Normal -->
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
|
||||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
|
||||||
|
|
||||||
<!-- So we can add a TextSecure 'Account' -->
|
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
|
||||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
|
||||||
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
|
|
||||||
|
|
||||||
<!-- For conversation 'shortcuts' on the desktop -->
|
|
||||||
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
|
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
|
||||||
|
|
||||||
<!-- For fixing MMS -->
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
|
||||||
|
|
||||||
<!-- Set image as wallpaper -->
|
|
||||||
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
||||||
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
|
||||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
|
||||||
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
|
||||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
|
|
||||||
|
|
||||||
<application android:name=".ApplicationContext"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
tools:replace="android:allowBackup"
|
|
||||||
android:allowBackup="false"
|
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
|
||||||
android:largeHeap="true">
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.geo.API_KEY"
|
|
||||||
android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/>
|
|
||||||
|
|
||||||
<meta-data android:name="com.google.android.gms.version"
|
|
||||||
android:value="@integer/google_play_services_version" />
|
|
||||||
|
|
||||||
<meta-data android:name="com.google.android.gms.car.application"
|
|
||||||
android:resource="@xml/automotive_app_desc" />
|
|
||||||
|
|
||||||
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
|
|
||||||
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
|
||||||
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
|
|
||||||
|
|
||||||
<activity android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
|
|
||||||
android:theme="@style/TextSecure.LightTheme.WebRTCCall"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|fontScale"
|
|
||||||
android:launchMode="singleTask"/>
|
|
||||||
|
|
||||||
<activity android:name=".InviteActivity"
|
|
||||||
android:theme="@style/Signal.Light.NoActionBar.Invite"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:parentActivityName=".MainActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".PromptMmsActivity"
|
|
||||||
android:label="Configure MMS Settings"
|
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".DeviceProvisioningActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="tsdevice"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".preferences.MmsPreferencesActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".ShareActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:taskAffinity=""
|
|
||||||
android:noHistory="true"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
|
||||||
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
<data android:mimeType="audio/*" />
|
|
||||||
<data android:mimeType="image/*" />
|
|
||||||
<data android:mimeType="text/plain" />
|
|
||||||
<data android:mimeType="video/*" />
|
|
||||||
<data android:mimeType="application/*"/>
|
|
||||||
<data android:mimeType="text/*"/>
|
|
||||||
<data android:mimeType="*/*"/>
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.service.chooser.chooser_target_service"
|
|
||||||
android:value=".service.DirectShareService" />
|
|
||||||
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".stickers.StickerPackPreviewActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:noHistory="true"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="sgnl"
|
|
||||||
android:host="addstickers" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity-alias android:name=".RoutingActivity"
|
|
||||||
android:targetActivity=".MainActivity"
|
|
||||||
android:exported="true">
|
|
||||||
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
|
|
||||||
android:resource="@mipmap/ic_launcher" />
|
|
||||||
<meta-data android:name="com.sec.minimode.icon.landscape.normal"
|
|
||||||
android:resource="@mipmap/ic_launcher" />
|
|
||||||
|
|
||||||
</activity-alias>
|
|
||||||
|
|
||||||
<activity android:name=".conversation.ConversationActivity"
|
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:parentActivityName=".MainActivity">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".longmessage.LongMessageActivity" />
|
|
||||||
|
|
||||||
<activity android:name=".conversation.ConversationPopupActivity"
|
|
||||||
android:windowSoftInputMode="stateVisible"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:taskAffinity=""
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:theme="@style/TextSecure.LightTheme.Popup"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
|
||||||
|
|
||||||
<activity android:name=".MessageDetailsActivity"
|
|
||||||
android:label="@string/AndroidManifest__message_details"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".GroupCreateActivity"
|
|
||||||
android:windowSoftInputMode="stateVisible"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".DatabaseMigrationActivity"
|
|
||||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".migrations.ApplicationMigrationActivity"
|
|
||||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".ExperienceUpgradeActivity"
|
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".PassphraseCreateActivity"
|
|
||||||
android:label="@string/AndroidManifest__create_passphrase"
|
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".PassphrasePromptActivity"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:theme="@style/TextSecure.LightIntroTheme"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".NewConversationActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:windowSoftInputMode="stateAlwaysVisible"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".PushContactSelectionActivity"
|
|
||||||
android:label="@string/AndroidManifest__select_contacts"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".giph.ui.GiphyActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".mediasend.MediaSendActivity"
|
|
||||||
android:theme="@style/TextSecure.FullScreenMedia"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:launchMode="singleTop"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".PassphraseChangeActivity"
|
|
||||||
android:label="@string/AndroidManifest__change_passphrase"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".VerifyIdentityActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".ApplicationPreferencesActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".registration.RegistrationNavigationActivity"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".revealable.ViewOnceMessageActivity"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:theme="@style/TextSecure.FullScreenMedia"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".stickers.StickerManagementActivity"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".DeviceActivity"
|
|
||||||
android:label="@string/AndroidManifest__linked_devices"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".LogSubmitActivity"
|
|
||||||
android:label="@string/AndroidManifest__log_submit"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".MediaPreviewActivity"
|
|
||||||
android:label="@string/AndroidManifest__media_preview"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".mediaoverview.MediaOverviewActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".DummyActivity"
|
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
|
||||||
android:enabled="true"
|
|
||||||
android:allowTaskReparenting="true"
|
|
||||||
android:noHistory="true"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:alwaysRetainTaskState="false"
|
|
||||||
android:stateNotNeeded="true"
|
|
||||||
android:clearTaskOnLaunch="true"
|
|
||||||
android:finishOnTaskLaunch="true" />
|
|
||||||
|
|
||||||
<activity android:name=".PlayServicesProblemActivity"
|
|
||||||
android:theme="@style/TextSecure.DialogActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".SmsSendtoActivity">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SENDTO" />
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="sms" />
|
|
||||||
<data android:scheme="smsto" />
|
|
||||||
<data android:scheme="mms" />
|
|
||||||
<data android:scheme="mmsto" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:theme="@style/NoAnimation.Theme.BlackScreen"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
|
||||||
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".RecipientPreferenceActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".BlockedContactsActivity"
|
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".scribbles.StickerSelectActivity"
|
|
||||||
android:theme="@style/TextSecure.DarkTheme"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".scribbles.NewStickerSelectActivity"
|
|
||||||
android:theme="@style/TextSecure.DarkTheme"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
|
|
||||||
android:theme="@style/TextSecure.DarkTheme"/>
|
|
||||||
|
|
||||||
<activity android:name=".CreateProfileActivity"
|
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
|
||||||
android:windowSoftInputMode="stateVisible"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".ClearProfileAvatarActivity"
|
|
||||||
android:theme="@style/Theme.AppCompat.Dialog.Alert"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:icon="@drawable/clear_profile_avatar"
|
|
||||||
android:label="@string/AndroidManifest_remove_photo">
|
|
||||||
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO"/>
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".contactshare.ContactShareEditActivity"
|
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".contactshare.ContactNameEditActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".contactshare.SharedContactDetailsActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".ShortcutLauncherActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:exported="true"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".maps.PlacePickerActivity"
|
|
||||||
android:label="@string/PlacePickerActivity_title"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".usernames.ProfileEditActivityV2"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:windowSoftInputMode="adjustResize"/>
|
|
||||||
|
|
||||||
<activity android:name=".MainActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
|
||||||
|
|
||||||
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
|
||||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
|
||||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
|
||||||
<service android:enabled="true" android:name=".service.IncomingMessageObserver$ForegroundService"/>
|
|
||||||
|
|
||||||
<service android:name=".service.QuickResponseService"
|
|
||||||
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
|
||||||
android:exported="true" >
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:scheme="sms" />
|
|
||||||
<data android:scheme="smsto" />
|
|
||||||
<data android:scheme="mms" />
|
|
||||||
<data android:scheme="mmsto" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.accounts.AccountAuthenticator" />
|
|
||||||
</intent-filter>
|
|
||||||
<meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" />
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service android:name=".service.ContactsSyncAdapterService" android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.content.SyncAdapter"/>
|
|
||||||
</intent-filter>
|
|
||||||
<meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" />
|
|
||||||
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service android:name=".service.DirectShareService"
|
|
||||||
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.service.chooser.ChooserTargetService" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service android:name=".service.GenericForegroundService"/>
|
|
||||||
|
|
||||||
<service android:name=".gcm.FcmService">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<receiver android:name=".service.SmsListener"
|
|
||||||
android:permission="android.permission.BROADCAST_SMS"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter android:priority="1001">
|
|
||||||
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.provider.Telephony.SMS_DELIVER"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.SmsDeliveryListener"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.services.MESSAGE_SENT"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.MmsListener"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="true"
|
|
||||||
android:permission="android.permission.BROADCAST_WAP_PUSH">
|
|
||||||
<intent-filter android:priority="1001">
|
|
||||||
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED"/>
|
|
||||||
<data android:mimeType="application/vnd.wap.mms-message" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
|
|
||||||
<data android:mimeType="application/vnd.wap.mms-message" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.MarkReadReceiver"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.notifications.CLEAR"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.RemoteReplyReceiver"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.notifications.WEAR_REPLY"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.AndroidAutoHeardReceiver"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_HEARD"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.AndroidAutoReplyReceiver"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_REPLY"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.ExpirationListener" />
|
|
||||||
|
|
||||||
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
|
||||||
|
|
||||||
<provider android:name=".providers.PartProvider"
|
|
||||||
android:grantUriPermissions="true"
|
|
||||||
android:exported="false"
|
|
||||||
android:authorities="org.thoughtcrime.provider.securesms" />
|
|
||||||
|
|
||||||
<provider android:name=".providers.MmsBodyProvider"
|
|
||||||
android:grantUriPermissions="true"
|
|
||||||
android:exported="false"
|
|
||||||
android:authorities="org.thoughtcrime.provider.securesms.mms" />
|
|
||||||
|
|
||||||
<provider android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="org.thoughtcrime.securesms.fileprovider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
|
|
||||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" />
|
|
||||||
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Conversation"
|
|
||||||
android:authorities="org.thoughtcrime.securesms.database.conversation"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$ConversationList"
|
|
||||||
android:authorities="org.thoughtcrime.securesms.database.conversationlist"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Attachment"
|
|
||||||
android:authorities="org.thoughtcrime.securesms.database.attachment"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Sticker"
|
|
||||||
android:authorities="org.thoughtcrime.securesms.database.sticker"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$StickerPack"
|
|
||||||
android:authorities="org.thoughtcrime.securesms.database.stickerpack"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider android:name="androidx.camera.camera2.impl.Camera2Initializer"
|
|
||||||
android:authorities="${applicationId}.camerax-init"
|
|
||||||
android:exported="false"
|
|
||||||
android:enabled="false" />
|
|
||||||
|
|
||||||
<receiver android:name=".service.BootReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.RESTART"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.DirectoryRefreshListener">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.RotateSignedPreKeyListener">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.RotateSenderCertificateListener">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.LocalBackupListener">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.PersistentConnectionBootListener">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.LocaleChangedReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.LOCALE_CHANGED"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.DeleteNotificationReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.DELETE_NOTIFICATION"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".ExperienceUpgradeActivity$AppUpgradeReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
|
|
||||||
<data android:scheme="package" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.ExperienceUpgradeActivity.DISMISS_ACTION"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".service.PanicResponderListener"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".gcm.FcmJobService"
|
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
|
||||||
android:enabled="@bool/enable_job_service"
|
|
||||||
tools:targetApi="26" />
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
|
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
|
||||||
android:enabled="@bool/enable_job_service"
|
|
||||||
tools:targetApi="26" />
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".jobmanager.KeepAliveService"
|
|
||||||
android:enabled="@bool/enable_alarm_manager" />
|
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver"
|
|
||||||
android:enabled="@bool/enable_alarm_manager" />
|
|
||||||
|
|
||||||
<!-- Probably don't need this one -->
|
|
||||||
<receiver
|
|
||||||
android:name=".jobmanager.BootReceiver"
|
|
||||||
android:enabled="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
|
||||||
|
|
||||||
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false"/>
|
|
||||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
|
||||||
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
|
|
||||||
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
|
|
||||||
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="632.0dip" />
|
|
||||||
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="598.0dip" />
|
|
||||||
|
|
||||||
</application>
|
|
||||||
</manifest>
|
|
||||||
74
BUILDING.md
@@ -1,74 +0,0 @@
|
|||||||
Building Signal
|
|
||||||
===============
|
|
||||||
|
|
||||||
Basics
|
|
||||||
------
|
|
||||||
|
|
||||||
Signal uses [Gradle](http://gradle.org) to build the project and to maintain
|
|
||||||
dependencies. However, you needn't install it yourself; the
|
|
||||||
"gradle wrapper" `gradlew`, mentioned below, will do that for you.
|
|
||||||
|
|
||||||
Building Signal
|
|
||||||
---------------
|
|
||||||
|
|
||||||
The following steps should help you (re)build Signal from the command line.
|
|
||||||
|
|
||||||
1. Checkout the Signal-Android project source with the command:
|
|
||||||
|
|
||||||
git clone https://github.com/signalapp/Signal-Android.git
|
|
||||||
|
|
||||||
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed.
|
|
||||||
3. Ensure that the following packages are installed from the Android SDK manager:
|
|
||||||
* Android SDK Build Tools (see buildToolsVersion in build.gradle)
|
|
||||||
* SDK Platform (All API levels)
|
|
||||||
* Android Support Repository
|
|
||||||
* Google Repository
|
|
||||||
4. Create a local.properties file at the root of your source checkout and add an sdk.dir entry to it. For example:
|
|
||||||
|
|
||||||
sdk.dir=/Application/android-sdk-macosx
|
|
||||||
|
|
||||||
5. Using Java 8
|
|
||||||
|
|
||||||
6. Execute Gradle:
|
|
||||||
|
|
||||||
./gradlew build
|
|
||||||
|
|
||||||
Visual assets
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Source assets tend to be large binary blobs, which are best stored outside of git repositories. Some source files are SVGs that can be auto-colored and sized using a tool like [android-res-utils](https://github.com/sebkur/android-res-utils).
|
|
||||||
|
|
||||||
Sample command for generating our audio placeholder image:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pngs_from_svg.py ic_audio.svg /path/to/Signal/res/ 150 --color #000 --opacity 0.54 --suffix _light
|
|
||||||
pngs_from_svg.py ic_audio.svg /path/to/Signal/res/ 150 --color #fff --opacity 1.00 --suffix _light
|
|
||||||
```
|
|
||||||
|
|
||||||
Setting up a development environment
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
[Android Studio](https://developer.android.com/sdk/installing/studio.html) is the recommended development environment.
|
|
||||||
|
|
||||||
1. Install Android Studio.
|
|
||||||
2. Open Android Studio. On a new installation, the Quickstart panel will appear. If you have open projects, close them using "File > Close Project" to see the Quickstart panel.
|
|
||||||
3. From the Quickstart panel, choose "Configure" then "SDK Manager".
|
|
||||||
4. In the SDK Tools tab of the SDK Manager, make sure that the "Android Support Repository" is installed, and that the latest "Android SDK build-tools" are installed. Click "OK" to return to the Quickstart panel.
|
|
||||||
5. From the Quickstart panel, choose "Checkout from Version Control" then "git".
|
|
||||||
6. Paste the URL for the Signal-Android project when prompted (https://github.com/signalapp/Signal-Android.git).
|
|
||||||
7. Android studio should detect the presence of a project file and ask you whether to open it. Click "yes".
|
|
||||||
9. Default config options should be good enough.
|
|
||||||
9. Project initialisation and build should proceed.
|
|
||||||
|
|
||||||
Contributing code
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Code contributions should be sent via github as pull requests, from feature branches [as explained here](https://help.github.com/articles/using-pull-requests).
|
|
||||||
|
|
||||||
Mailing list
|
|
||||||
------------
|
|
||||||
|
|
||||||
Development discussion happens on the whispersystems mailing list.
|
|
||||||
[To join](https://lists.riseup.net/www/info/whispersystems)
|
|
||||||
Send emails to whispersystems@lists.riseup.net
|
|
||||||
|
|
||||||
@@ -27,7 +27,6 @@ Interested in helping to translate Signal? Contribute here:
|
|||||||
https://www.transifex.com/projects/p/signal-android/
|
https://www.transifex.com/projects/p/signal-android/
|
||||||
|
|
||||||
## Contributing Code
|
## Contributing Code
|
||||||
Instructions on how to setup your development environment and build Signal can be found in [BUILDING.md](https://github.com/signalapp/Signal-Android/blob/master/BUILDING.md).
|
|
||||||
|
|
||||||
If you're new to the Signal codebase, we recommend going through our issues and picking out a simple bug to fix (check the "easy" label in our issues) in order to get yourself familiar. Also please have a look at the [CONTRIBUTING.md](https://github.com/signalapp/Signal-Android/blob/master/CONTRIBUTING.md), that might answer some of your questions.
|
If you're new to the Signal codebase, we recommend going through our issues and picking out a simple bug to fix (check the "easy" label in our issues) in order to get yourself familiar. Also please have a look at the [CONTRIBUTING.md](https://github.com/signalapp/Signal-Android/blob/master/CONTRIBUTING.md), that might answer some of your questions.
|
||||||
|
|
||||||
@@ -60,9 +59,7 @@ The form and manner of this distribution makes it eligible for export under the
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright 2011 Whisper Systems
|
Copyright 2013-2020 Signal
|
||||||
|
|
||||||
Copyright 2013-2017 Open Whisper Systems
|
|
||||||
|
|
||||||
Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html
|
Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
|||||||
10
app/.tx/config
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
lang_map = da_DK:da-rDK,fil:tl,he:iw,id:in,kn_IN:kn-rIN,pa_PK:pa-rPK,pt_BR:pt-rBR,pt_PT:pt,qu_EC:qu-rEC,sv_SE:sv-rSE,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW
|
||||||
|
|
||||||
|
[signal-android.master]
|
||||||
|
file_filter = src/main/res/values-<lang>/strings.xml
|
||||||
|
source_file = src/main/res/values/strings.xml
|
||||||
|
source_lang = en
|
||||||
|
type = ANDROID
|
||||||
|
|
||||||
295
app/build.gradle
@@ -15,7 +15,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.1'
|
classpath 'com.android.tools.build:gradle:3.6.3'
|
||||||
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0'
|
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0'
|
||||||
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
|
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
|
||||||
}
|
}
|
||||||
@@ -80,122 +80,8 @@ protobuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
def canonicalVersionCode = 683
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
def canonicalVersionName = "4.68.2"
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
|
||||||
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
|
||||||
implementation 'androidx.preference:preference:1.0.0'
|
|
||||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
|
||||||
implementation 'androidx.navigation:navigation-fragment:2.1.0'
|
|
||||||
implementation 'androidx.navigation:navigation-ui:2.1.0'
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
|
|
||||||
implementation "androidx.camera:camera-core:1.0.0-alpha06"
|
|
||||||
implementation "androidx.camera:camera-camera2:1.0.0-alpha06"
|
|
||||||
|
|
||||||
implementation('com.google.firebase:firebase-messaging:17.3.4') {
|
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
|
||||||
}
|
|
||||||
|
|
||||||
implementation 'com.google.android.gms:play-services-maps:16.1.0'
|
|
||||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
|
||||||
|
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
|
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
|
||||||
|
|
||||||
implementation 'org.conscrypt:conscrypt-android:2.0.0'
|
|
||||||
implementation 'org.signal:aesgcmprovider:0.0.3'
|
|
||||||
|
|
||||||
implementation project(':libsignal-service')
|
|
||||||
|
|
||||||
implementation 'org.signal:ringrtc-android:0.2.0'
|
|
||||||
|
|
||||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
|
||||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
|
||||||
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
|
||||||
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
|
||||||
implementation 'com.github.bumptech.glide:glide:4.9.0'
|
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
|
||||||
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
|
||||||
implementation 'com.makeramen:roundedimageview:2.1.0'
|
|
||||||
implementation 'com.pnikosis:materialish-progress:1.5'
|
|
||||||
implementation 'org.greenrobot:eventbus:3.0.0'
|
|
||||||
implementation 'pl.tajchert:waitingdots:0.1.0'
|
|
||||||
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
|
|
||||||
implementation 'com.melnykov:floatingactionbutton:1.3.0'
|
|
||||||
implementation 'com.google.zxing:android-integration:3.1.0'
|
|
||||||
implementation 'mobi.upod:time-duration-picker:1.1.3'
|
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
|
||||||
implementation 'com.google.zxing:core:3.2.1'
|
|
||||||
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
|
|
||||||
exclude group: 'com.android.support', module: 'support-annotations'
|
|
||||||
}
|
|
||||||
implementation ('cn.carbswang.android:NumberPickerView:1.0.9') {
|
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
|
||||||
}
|
|
||||||
implementation ('com.tomergoldst.android:tooltips:1.0.6') {
|
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
|
||||||
}
|
|
||||||
implementation ('com.klinkerapps:android-smsmms:4.0.1') {
|
|
||||||
exclude group: 'com.squareup.okhttp', module: 'okhttp'
|
|
||||||
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
|
|
||||||
}
|
|
||||||
implementation 'com.annimon:stream:1.1.8'
|
|
||||||
implementation ('com.takisoft.fix:colorpicker:0.9.1') {
|
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
|
||||||
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
|
||||||
}
|
|
||||||
|
|
||||||
implementation 'com.airbnb.android:lottie:3.0.7'
|
|
||||||
|
|
||||||
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
|
||||||
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
|
||||||
implementation 'org.signal:android-database-sqlcipher:3.5.9-S3'
|
|
||||||
implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
|
|
||||||
exclude group: 'com.fasterxml.jackson.core'
|
|
||||||
exclude group: 'org.freemarker'
|
|
||||||
}
|
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
|
||||||
testImplementation 'org.assertj:assertj-core:3.11.1'
|
|
||||||
testImplementation 'org.mockito:mockito-core:1.9.5'
|
|
||||||
testImplementation 'org.powermock:powermock-api-mockito:1.6.1'
|
|
||||||
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
|
|
||||||
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
|
|
||||||
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
|
|
||||||
|
|
||||||
testImplementation 'androidx.test:core:1.2.0'
|
|
||||||
androidTestImplementation 'androidx.multidex:multidex:2.0.1'
|
|
||||||
androidTestImplementation 'androidx.multidex:multidex-instrumentation:2.0.0'
|
|
||||||
androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
|
|
||||||
androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2'
|
|
||||||
androidTestImplementation ('org.assertj:assertj-core:1.7.1') {
|
|
||||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
|
||||||
}
|
|
||||||
androidTestImplementation ('com.squareup.assertj:assertj-android:1.1.1') {
|
|
||||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
|
||||||
exclude group: 'com.android.support', module: 'support-annotations'
|
|
||||||
}
|
|
||||||
testImplementation 'org.robolectric:robolectric:4.2'
|
|
||||||
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencyVerification {
|
|
||||||
configuration = '(play|website)(Debug|Release)RuntimeClasspath'
|
|
||||||
}
|
|
||||||
|
|
||||||
def canonicalVersionCode = 575
|
|
||||||
def canonicalVersionName = "4.51.2"
|
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['universal' : 0,
|
def abiPostFix = ['universal' : 0,
|
||||||
@@ -229,17 +115,18 @@ android {
|
|||||||
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
|
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
|
||||||
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
|
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
|
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
|
||||||
|
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
|
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
||||||
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
||||||
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
||||||
buildConfigField "String", "USER_AGENT", "\"OWA\""
|
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
||||||
buildConfigField "boolean", "DEV_BUILD", "false"
|
buildConfigField "String", "CDS_MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
|
||||||
buildConfigField "String", "MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
|
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
|
||||||
buildConfigField "String", "KEY_BACKUP_ENCLAVE_NAME", "\"f2e2a5004794a6c1bac5c4949eadbc243dd02e02d1a93f10fe24584fb70815d8\""
|
buildConfigField "String", "KBS_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\""
|
||||||
buildConfigField "String", "KEY_BACKUP_MRENCLAVE", "\"f51f435802ada769e67aaf5744372bb7e7d519eecf996d335eb5b46b872b5789\""
|
|
||||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
||||||
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\""
|
||||||
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
||||||
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
||||||
|
|
||||||
@@ -257,6 +144,8 @@ android {
|
|||||||
universalApk true
|
universalApk true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -305,11 +194,17 @@ android {
|
|||||||
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
|
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
|
||||||
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
|
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
|
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
|
||||||
|
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
|
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
||||||
buildConfigField "String", "MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"b657cad56d518827b0938949bb1e5727a9a4db358dd6a88e55e710a89ffa50bd\""
|
||||||
buildConfigField "String", "KEY_BACKUP_ENCLAVE_NAME", "\"b5a865941f95887018c86725cc92308d34a3084dc2b4e7bd2de5e5e1690b50c6\""
|
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\""
|
||||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
||||||
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=\""
|
||||||
|
}
|
||||||
|
flipper {
|
||||||
|
initWith debug
|
||||||
|
minifyEnabled false
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
@@ -345,32 +240,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
manifest.srcFile '../AndroidManifest.xml'
|
|
||||||
java.srcDirs = ['../src']
|
|
||||||
resources.srcDirs = ['../src']
|
|
||||||
aidl.srcDirs = ['../src']
|
|
||||||
renderscript.srcDirs = ['../src']
|
|
||||||
res.srcDirs = ['../res']
|
|
||||||
assets.srcDirs = ['../assets']
|
|
||||||
jniLibs.srcDirs = ['../libs']
|
|
||||||
proto.srcDir '../protobuf'
|
|
||||||
}
|
|
||||||
androidTest {
|
|
||||||
java.srcDirs = ['../test/androidTest/java']
|
|
||||||
}
|
|
||||||
test {
|
|
||||||
java.srcDirs = ['../test/unitTest/java']
|
|
||||||
resources.srcDirs = ['../test/unitTest/resources']
|
|
||||||
}
|
|
||||||
staging {
|
|
||||||
res.srcDirs = ['../staging/res']
|
|
||||||
}
|
|
||||||
|
|
||||||
website.manifest.srcFile '../website/AndroidManifest.xml'
|
|
||||||
}
|
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
abortOnError true
|
abortOnError true
|
||||||
baseline file("lint-baseline.xml")
|
baseline file("lint-baseline.xml")
|
||||||
@@ -384,6 +253,132 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
lintChecks project(':lintchecks')
|
||||||
|
|
||||||
|
implementation('androidx.appcompat:appcompat:1.1.0-beta01') {
|
||||||
|
force = true
|
||||||
|
}
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
|
implementation 'com.google.android.material:material:1.1.0'
|
||||||
|
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
||||||
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
|
implementation 'androidx.preference:preference:1.0.0'
|
||||||
|
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||||
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
|
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
|
implementation 'androidx.navigation:navigation-fragment:2.1.0'
|
||||||
|
implementation 'androidx.navigation:navigation-ui:2.1.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
|
||||||
|
implementation "androidx.camera:camera-core:1.0.0-beta01"
|
||||||
|
implementation "androidx.camera:camera-camera2:1.0.0-beta01"
|
||||||
|
implementation "androidx.camera:camera-lifecycle:1.0.0-beta01"
|
||||||
|
implementation "androidx.concurrent:concurrent-futures:1.0.0"
|
||||||
|
implementation "androidx.autofill:autofill:1.0.0"
|
||||||
|
implementation "androidx.paging:paging-common:2.1.2"
|
||||||
|
implementation "androidx.paging:paging-runtime:2.1.2"
|
||||||
|
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
|
||||||
|
implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
|
||||||
|
|
||||||
|
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
|
||||||
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
|
}
|
||||||
|
|
||||||
|
implementation 'com.google.android.gms:play-services-maps:16.1.0'
|
||||||
|
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||||
|
|
||||||
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
|
||||||
|
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
||||||
|
|
||||||
|
implementation 'org.conscrypt:conscrypt-android:2.0.0'
|
||||||
|
implementation 'org.signal:aesgcmprovider:0.0.3'
|
||||||
|
|
||||||
|
implementation project(':libsignal-service')
|
||||||
|
implementation 'org.signal:zkgroup-android:0.7.0'
|
||||||
|
|
||||||
|
implementation 'org.signal:argon2:13.1@aar'
|
||||||
|
|
||||||
|
implementation 'org.signal:ringrtc-android:2.4.1'
|
||||||
|
|
||||||
|
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||||
|
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||||
|
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||||
|
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
||||||
|
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
||||||
|
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
||||||
|
implementation 'com.makeramen:roundedimageview:2.1.0'
|
||||||
|
implementation 'com.pnikosis:materialish-progress:1.5'
|
||||||
|
implementation 'org.greenrobot:eventbus:3.0.0'
|
||||||
|
implementation 'pl.tajchert:waitingdots:0.1.0'
|
||||||
|
implementation 'com.melnykov:floatingactionbutton:1.3.0'
|
||||||
|
implementation 'com.google.zxing:android-integration:3.1.0'
|
||||||
|
implementation 'mobi.upod:time-duration-picker:1.1.3'
|
||||||
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
|
implementation 'com.google.zxing:core:3.2.1'
|
||||||
|
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
|
||||||
|
exclude group: 'com.android.support', module: 'support-annotations'
|
||||||
|
}
|
||||||
|
implementation ('cn.carbswang.android:NumberPickerView:1.0.9') {
|
||||||
|
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||||
|
}
|
||||||
|
implementation ('com.tomergoldst.android:tooltips:1.0.6') {
|
||||||
|
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||||
|
}
|
||||||
|
implementation ('com.klinkerapps:android-smsmms:4.0.1') {
|
||||||
|
exclude group: 'com.squareup.okhttp', module: 'okhttp'
|
||||||
|
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
|
||||||
|
}
|
||||||
|
implementation 'com.annimon:stream:1.1.8'
|
||||||
|
implementation ('com.takisoft.fix:colorpicker:0.9.1') {
|
||||||
|
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||||
|
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
||||||
|
}
|
||||||
|
|
||||||
|
implementation 'com.airbnb.android:lottie:3.0.7'
|
||||||
|
|
||||||
|
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
||||||
|
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
||||||
|
implementation 'org.signal:android-database-sqlcipher:3.5.9-S3'
|
||||||
|
implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
|
||||||
|
exclude group: 'com.fasterxml.jackson.core'
|
||||||
|
exclude group: 'org.freemarker'
|
||||||
|
}
|
||||||
|
implementation 'dnsjava:dnsjava:2.1.9'
|
||||||
|
|
||||||
|
flipperImplementation 'com.facebook.flipper:flipper:0.32.2'
|
||||||
|
flipperImplementation 'com.facebook.soloader:soloader:0.8.2'
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
testImplementation 'org.assertj:assertj-core:3.11.1'
|
||||||
|
testImplementation 'org.mockito:mockito-core:1.9.5'
|
||||||
|
testImplementation 'org.powermock:powermock-api-mockito:1.6.5'
|
||||||
|
testImplementation 'org.powermock:powermock-module-junit4:1.6.5'
|
||||||
|
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.5'
|
||||||
|
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.5'
|
||||||
|
|
||||||
|
testImplementation 'androidx.test:core:1.2.0'
|
||||||
|
testImplementation ('org.robolectric:robolectric:4.2') {
|
||||||
|
exclude group: 'com.google.protobuf', module: 'protobuf-java'
|
||||||
|
}
|
||||||
|
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
||||||
|
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyVerification {
|
||||||
|
configuration = '(play|website)(Debug|Release)RuntimeClasspath'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def assembleWebsiteDescriptor = { variant, file ->
|
def assembleWebsiteDescriptor = { variant, file ->
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
errorLine1=" List<SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();"
|
errorLine1=" List<SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();"
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/org/thoughtcrime/securesms/util/dualsim/SubscriptionManagerCompat.java"
|
file="src/main/java/org/thoughtcrime/securesms/util/dualsim/SubscriptionManagerCompat.java"
|
||||||
line="101"
|
line="101"
|
||||||
column="35"/>
|
column="35"/>
|
||||||
</issue>
|
</issue>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
errorLine1=" drawables.getColor(1, 0xff000000);"
|
errorLine1=" drawables.getColor(1, 0xff000000);"
|
||||||
errorLine2=" ~">
|
errorLine2=" ~">
|
||||||
<location
|
<location
|
||||||
file="src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java"
|
file="src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java"
|
||||||
line="187"
|
line="187"
|
||||||
column="36"/>
|
column="36"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|||||||
16
app/lint.xml
@@ -1,15 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<lint>
|
<lint>
|
||||||
|
|
||||||
|
<!-- Wont pass lint or qa with a STOPSHIP in a comment -->
|
||||||
|
<issue id="StopShip" severity="fatal" />
|
||||||
|
|
||||||
<!-- L10N errors -->
|
<!-- L10N errors -->
|
||||||
<!-- This is a runtime crash so we don't want to ship with this. -->
|
<!-- This is a runtime crash so we don't want to ship with this. -->
|
||||||
<issue id="StringFormatMatches" severity="error" />
|
<issue id="StringFormatMatches" severity="error" />
|
||||||
|
|
||||||
<!-- L10N warnings -->
|
<!-- L10N warnings -->
|
||||||
<issue id="MissingTranslation" severity="warning" />
|
<issue id="MissingTranslation" severity="ignore" />
|
||||||
<issue id="MissingQuantity" severity="warning" />
|
<issue id="MissingQuantity" severity="warning" />
|
||||||
<issue id="ExtraTranslation" severity="warning" />
|
<issue id="ExtraTranslation" severity="warning" />
|
||||||
<issue id="ImpliedQuantity" severity="warning" />
|
<issue id="ImpliedQuantity" severity="warning" />
|
||||||
|
<issue id="TypographyDashes" severity="error" >
|
||||||
|
<ignore path="*/res/values-*" /> <!-- Ignore for non-English -->
|
||||||
|
</issue>
|
||||||
|
|
||||||
<issue id="CanvasSize" severity="error" />
|
<issue id="CanvasSize" severity="error" />
|
||||||
<issue id="HardcodedText" severity="error" />
|
<issue id="HardcodedText" severity="error" />
|
||||||
@@ -17,9 +23,17 @@
|
|||||||
<issue id="ButtonOrder" severity="error" />
|
<issue id="ButtonOrder" severity="error" />
|
||||||
<issue id="ExtraTranslation" severity="warning" />
|
<issue id="ExtraTranslation" severity="warning" />
|
||||||
|
|
||||||
|
<!-- Custom lints -->
|
||||||
|
<issue id="LogNotSignal" severity="error" />
|
||||||
|
<issue id="LogNotAppSignal" severity="error" />
|
||||||
|
<issue id="LogTagInlined" severity="error" />
|
||||||
|
|
||||||
<issue id="RestrictedApi" severity="error">
|
<issue id="RestrictedApi" severity="error">
|
||||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
|
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
|
||||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
|
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
|
||||||
|
<ignore path="*/org/thoughtcrime/securesms/conversation/*.java" />
|
||||||
|
<ignore path="*/org/thoughtcrime/securesms/lock/v2/CreateKbsPinViewModel.java" />
|
||||||
|
<ignore path="*/org/thoughtcrime/securesms/jobs/StickerPackDownloadJob.java" />
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
</lint>
|
</lint>
|
||||||
|
|||||||
29
app/sampledata/contacts.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "Ottttooooooooo Ocataaaaaaaavius",
|
||||||
|
"number": "+1 (555) 555-5555",
|
||||||
|
"label": "Mobile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Victor Von Doom Phd",
|
||||||
|
"number": "+1 (555) 123-4567",
|
||||||
|
"label": "Home"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Flash Thompson",
|
||||||
|
"number": "+1 (555) 435-1261",
|
||||||
|
"label": "Work"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dr. Curtis Connors",
|
||||||
|
"number": "+1 (555) 992-1567",
|
||||||
|
"label": "Mobile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Billy Russo",
|
||||||
|
"number": "+1 (555) 234-1516",
|
||||||
|
"label": "Mobile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package org.thoughtcrime.securesms.lock;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.thoughtcrime.securesms.util.Hex;
|
||||||
|
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||||
|
import org.whispersystems.signalservice.api.kbs.KbsData;
|
||||||
|
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public final class PinHashing_hashPin_Test {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void argon2_hashed_pin_password() throws IOException {
|
||||||
|
String pin = "password";
|
||||||
|
byte[] backupId = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
|
||||||
|
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"));
|
||||||
|
|
||||||
|
HashedPin hashedPin = PinHashing.hashPin(pin, () -> backupId);
|
||||||
|
KbsData kbsData = hashedPin.createNewKbsData(masterKey);
|
||||||
|
|
||||||
|
assertArrayEquals(hashedPin.getKbsAccessKey(), kbsData.getKbsAccessKey());
|
||||||
|
assertArrayEquals(Hex.fromStringCondensed("ab7e8499d21f80a6600b3b9ee349ac6d72c07e3359fe885a934ba7aa844429f8"), kbsData.getKbsAccessKey());
|
||||||
|
assertArrayEquals(Hex.fromStringCondensed("3f33ce58eb25b40436592a30eae2a8fabab1899095f4e2fba6e2d0dc43b4a2d9cac5a3931748522393951e0e54dec769"), kbsData.getCipherText());
|
||||||
|
assertEquals(masterKey, kbsData.getMasterKey());
|
||||||
|
|
||||||
|
String localPinHash = PinHashing.localPinHash(pin);
|
||||||
|
assertTrue(PinHashing.verifyLocalPinHash(localPinHash, pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void argon2_hashed_pin_another_password() throws IOException {
|
||||||
|
String pin = "anotherpassword";
|
||||||
|
byte[] backupId = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
|
||||||
|
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("88a787415a2ecd79da0d1016a82a27c5c695c9a19b88b0aa1d35683280aa9a67"));
|
||||||
|
|
||||||
|
HashedPin hashedPin = PinHashing.hashPin(pin, () -> backupId);
|
||||||
|
KbsData kbsData = hashedPin.createNewKbsData(masterKey);
|
||||||
|
|
||||||
|
assertArrayEquals(hashedPin.getKbsAccessKey(), kbsData.getKbsAccessKey());
|
||||||
|
assertArrayEquals(Hex.fromStringCondensed("301d9dd1e96f20ce51083f67d3298fd37b97525de8324d5e12ed2d407d3d927b"), kbsData.getKbsAccessKey());
|
||||||
|
assertArrayEquals(Hex.fromStringCondensed("9d9b05402ea39c17ff1c9298c8a0e86784a352aa02a74943bf8bcf07ec0f4b574a5b786ad0182c8d308d9eb06538b8c9"), kbsData.getCipherText());
|
||||||
|
assertEquals(masterKey, kbsData.getMasterKey());
|
||||||
|
|
||||||
|
String localPinHash = PinHashing.localPinHash(pin);
|
||||||
|
assertTrue(PinHashing.verifyLocalPinHash(localPinHash, pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void argon2_hashed_pin_password_with_spaces_diacritics_and_non_arabic_numerals() throws IOException {
|
||||||
|
String pin = " Pass६örd ";
|
||||||
|
byte[] backupId = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
|
||||||
|
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("9571f3fde1e58588ba49bcf82be1b301ca3859a6f59076f79a8f47181ef952bf"));
|
||||||
|
|
||||||
|
HashedPin hashedPin = PinHashing.hashPin(pin, () -> backupId);
|
||||||
|
KbsData kbsData = hashedPin.createNewKbsData(masterKey);
|
||||||
|
|
||||||
|
assertArrayEquals(hashedPin.getKbsAccessKey(), kbsData.getKbsAccessKey());
|
||||||
|
assertArrayEquals(Hex.fromStringCondensed("ab645acdccc1652a48a34b2ac6926340ff35c03034013f68760f20013f028dd8"), kbsData.getKbsAccessKey());
|
||||||
|
assertArrayEquals(Hex.fromStringCondensed("11c0ba1834db15e47c172f6c987c64bd4cfc69c6047dd67a022afeec0165a10943f204d5b8f37b3cb7bab21c6dfc39c8"), kbsData.getCipherText());
|
||||||
|
assertEquals(masterKey, kbsData.getMasterKey());
|
||||||
|
|
||||||
|
assertEquals("577939bccb2b6638c39222d5a97998a867c5e154e30b82cc120f2dd07a3de987", kbsData.getMasterKey().deriveRegistrationLock());
|
||||||
|
|
||||||
|
String localPinHash = PinHashing.localPinHash(pin);
|
||||||
|
assertTrue(PinHashing.verifyLocalPinHash(localPinHash, pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void argon2_hashed_pin_password_with_just_non_arabic_numerals() throws IOException {
|
||||||
|
String pin = " ६१८ ";
|
||||||
|
byte[] backupId = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
|
||||||
|
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("0432d735b32f66d0e3a70d4f9cc821a8529521a4937d26b987715d8eff4e4c54"));
|
||||||
|
|
||||||
|
HashedPin hashedPin = PinHashing.hashPin(pin, () -> backupId);
|
||||||
|
KbsData kbsData = hashedPin.createNewKbsData(masterKey);
|
||||||
|
|
||||||
|
|
||||||
|
assertArrayEquals(hashedPin.getKbsAccessKey(), kbsData.getKbsAccessKey());
|
||||||
|
assertArrayEquals(Hex.fromStringCondensed("d2fedabd0d4c17a371491c9722578843a26be3b4923e28d452ab2fc5491e794b"), kbsData.getKbsAccessKey());
|
||||||
|
assertArrayEquals(Hex.fromStringCondensed("877ef871ef1fc668401c717ef21aa12e8523579fb1ff4474b76f28c2293537c80cc7569996c9e0229bea7f378e3a824e"), kbsData.getCipherText());
|
||||||
|
assertEquals(masterKey, kbsData.getMasterKey());
|
||||||
|
|
||||||
|
assertEquals("23a75cb1df1a87df45cc2ed167c2bdc85ab1220b847c88761b0005cac907fce5", kbsData.getMasterKey().deriveRegistrationLock());
|
||||||
|
|
||||||
|
String localPinHash = PinHashing.localPinHash(pin);
|
||||||
|
assertTrue(PinHashing.verifyLocalPinHash(localPinHash, pin));
|
||||||
|
}
|
||||||
|
}
|
||||||
9
app/src/flipper/AndroidManifest.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="org.thoughtcrime.securesms">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".FlipperApplicationContext"
|
||||||
|
tools:replace="android:name"/>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import com.facebook.flipper.android.AndroidFlipperClient;
|
||||||
|
import com.facebook.flipper.core.FlipperClient;
|
||||||
|
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
|
||||||
|
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
||||||
|
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
||||||
|
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
|
||||||
|
import com.facebook.soloader.SoLoader;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.FlipperSqlCipherAdapter;
|
||||||
|
|
||||||
|
public class FlipperApplicationContext extends ApplicationContext {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
SoLoader.init(this, false);
|
||||||
|
|
||||||
|
FlipperClient client = AndroidFlipperClient.getInstance(this);
|
||||||
|
client.addPlugin(new InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()));
|
||||||
|
client.addPlugin(new DatabasesFlipperPlugin(new FlipperSqlCipherAdapter(this)));
|
||||||
|
client.addPlugin(new SharedPreferencesFlipperPlugin(this));
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.facebook.flipper.plugins.databases.DatabaseDescriptor;
|
||||||
|
import com.facebook.flipper.plugins.databases.DatabaseDriver;
|
||||||
|
|
||||||
|
import net.sqlcipher.DatabaseUtils;
|
||||||
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
import net.sqlcipher.database.SQLiteStatement;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver}
|
||||||
|
* and made to work with SqlCipher. Unfortunately I couldn't use it directly, nor subclass it.
|
||||||
|
*/
|
||||||
|
public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdapter.Descriptor> {
|
||||||
|
|
||||||
|
public FlipperSqlCipherAdapter(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Descriptor> getDatabases() {
|
||||||
|
return Collections.singletonList(new Descriptor(DatabaseFactory.getRawDatabase(getContext())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getTableNames(Descriptor descriptor) {
|
||||||
|
SQLiteDatabase db = descriptor.getReadable();
|
||||||
|
List<String> tableNames = new ArrayList<>();
|
||||||
|
|
||||||
|
try (Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type IN (?, ?)", new String[] { "table", "view" })) {
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
tableNames.add(cursor.getString(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatabaseGetTableDataResponse getTableData(Descriptor descriptor, String table, String order, boolean reverse, int start, int count) {
|
||||||
|
SQLiteDatabase db = descriptor.getReadable();
|
||||||
|
|
||||||
|
long total = DatabaseUtils.queryNumEntries(db, table);
|
||||||
|
String orderBy = order != null ? order + (reverse ? " DESC" : " ASC") : null;
|
||||||
|
String limitBy = start + ", " + count;
|
||||||
|
|
||||||
|
try (Cursor cursor = db.query(table, null, null, null, null, null, orderBy, limitBy)) {
|
||||||
|
String[] columnNames = cursor.getColumnNames();
|
||||||
|
List<List<Object>> rows = cursorToList(cursor);
|
||||||
|
|
||||||
|
return new DatabaseGetTableDataResponse(Arrays.asList(columnNames), rows, start, rows.size(), total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatabaseGetTableStructureResponse getTableStructure(Descriptor descriptor, String table) {
|
||||||
|
SQLiteDatabase db = descriptor.getReadable();
|
||||||
|
|
||||||
|
Map<String, String> foreignKeyValues = new HashMap<>();
|
||||||
|
|
||||||
|
try(Cursor cursor = db.rawQuery("PRAGMA foreign_key_list(" + table + ")", null)) {
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
String from = cursor.getString(cursor.getColumnIndex("from"));
|
||||||
|
String to = cursor.getString(cursor.getColumnIndex("to"));
|
||||||
|
String tableName = cursor.getString(cursor.getColumnIndex("table")) + "(" + to + ")";
|
||||||
|
|
||||||
|
foreignKeyValues.put(from, tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<String> structureColumns = Arrays.asList("column_name", "data_type", "nullable", "default", "primary_key", "foreign_key");
|
||||||
|
List<List<Object>> structureValues = new ArrayList<>();
|
||||||
|
|
||||||
|
try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + table + ")", null)) {
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
String columnName = cursor.getString(cursor.getColumnIndex("name"));
|
||||||
|
String foreignKey = foreignKeyValues.containsKey(columnName) ? foreignKeyValues.get(columnName) : null;
|
||||||
|
|
||||||
|
structureValues.add(Arrays.asList(columnName,
|
||||||
|
cursor.getString(cursor.getColumnIndex("type")),
|
||||||
|
cursor.getInt(cursor.getColumnIndex("notnull")) == 0,
|
||||||
|
getObjectFromColumnIndex(cursor, cursor.getColumnIndex("dflt_value")),
|
||||||
|
cursor.getInt(cursor.getColumnIndex("pk")) == 1,
|
||||||
|
foreignKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<String> indexesColumns = Arrays.asList("index_name", "unique", "indexed_column_name");
|
||||||
|
List<List<Object>> indexesValues = new ArrayList<>();
|
||||||
|
|
||||||
|
try (Cursor indexesCursor = db.rawQuery("PRAGMA index_list(" + table + ")", null)) {
|
||||||
|
List<String> indexedColumnNames = new ArrayList<>();
|
||||||
|
String indexName = indexesCursor.getString(indexesCursor.getColumnIndex("name"));
|
||||||
|
|
||||||
|
try(Cursor indexInfoCursor = db.rawQuery("PRAGMA index_info(" + indexName + ")", null)) {
|
||||||
|
while (indexInfoCursor.moveToNext()) {
|
||||||
|
indexedColumnNames.add(indexInfoCursor.getString(indexInfoCursor.getColumnIndex("name")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexesValues.add(Arrays.asList(indexName,
|
||||||
|
indexesCursor.getInt(indexesCursor.getColumnIndex("unique")) == 1,
|
||||||
|
TextUtils.join(",", indexedColumnNames)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DatabaseGetTableStructureResponse(structureColumns, structureValues, indexesColumns, indexesValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatabaseGetTableInfoResponse getTableInfo(Descriptor databaseDescriptor, String table) {
|
||||||
|
SQLiteDatabase db = databaseDescriptor.getReadable();
|
||||||
|
|
||||||
|
try (Cursor cursor = db.rawQuery("SELECT sql FROM sqlite_master WHERE name = ?", new String[] { table })) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
return new DatabaseGetTableInfoResponse(cursor.getString(cursor.getColumnIndex("sql")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatabaseExecuteSqlResponse executeSQL(Descriptor descriptor, String query) {
|
||||||
|
SQLiteDatabase db = descriptor.getWritable();
|
||||||
|
|
||||||
|
String firstWordUpperCase = getFirstWord(query).toUpperCase();
|
||||||
|
|
||||||
|
switch (firstWordUpperCase) {
|
||||||
|
case "UPDATE":
|
||||||
|
case "DELETE":
|
||||||
|
return executeUpdateDelete(db, query);
|
||||||
|
case "INSERT":
|
||||||
|
return executeInsert(db, query);
|
||||||
|
case "SELECT":
|
||||||
|
case "PRAGMA":
|
||||||
|
case "EXPLAIN":
|
||||||
|
return executeSelect(db, query);
|
||||||
|
default:
|
||||||
|
return executeRawQuery(db, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getFirstWord(String s) {
|
||||||
|
s = s.trim();
|
||||||
|
int firstSpace = s.indexOf(' ');
|
||||||
|
return firstSpace >= 0 ? s.substring(0, firstSpace) : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DatabaseExecuteSqlResponse executeUpdateDelete(SQLiteDatabase database, String query) {
|
||||||
|
SQLiteStatement statement = database.compileStatement(query);
|
||||||
|
int count = statement.executeUpdateDelete();
|
||||||
|
|
||||||
|
return DatabaseExecuteSqlResponse.successfulUpdateDelete(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DatabaseExecuteSqlResponse executeInsert(SQLiteDatabase database, String query) {
|
||||||
|
SQLiteStatement statement = database.compileStatement(query);
|
||||||
|
long insertedId = statement.executeInsert();
|
||||||
|
|
||||||
|
return DatabaseExecuteSqlResponse.successfulInsert(insertedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DatabaseExecuteSqlResponse executeSelect(SQLiteDatabase database, String query) {
|
||||||
|
try (Cursor cursor = database.rawQuery(query, null)) {
|
||||||
|
String[] columnNames = cursor.getColumnNames();
|
||||||
|
List<List<Object>> rows = cursorToList(cursor);
|
||||||
|
|
||||||
|
return DatabaseExecuteSqlResponse.successfulSelect(Arrays.asList(columnNames), rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DatabaseExecuteSqlResponse executeRawQuery(SQLiteDatabase database, String query) {
|
||||||
|
database.execSQL(query);
|
||||||
|
return DatabaseExecuteSqlResponse.successfulRawQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull List<List<Object>> cursorToList(Cursor cursor) {
|
||||||
|
List<List<Object>> rows = new ArrayList<>();
|
||||||
|
int numColumns = cursor.getColumnCount();
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
List<Object> values = new ArrayList<>(numColumns);
|
||||||
|
|
||||||
|
for (int column = 0; column < numColumns; column++) {
|
||||||
|
values.add(getObjectFromColumnIndex(cursor, column));
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.add(values);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable Object getObjectFromColumnIndex(Cursor cursor, int column) {
|
||||||
|
switch (cursor.getType(column)) {
|
||||||
|
case Cursor.FIELD_TYPE_NULL:
|
||||||
|
return null;
|
||||||
|
case Cursor.FIELD_TYPE_INTEGER:
|
||||||
|
return cursor.getLong(column);
|
||||||
|
case Cursor.FIELD_TYPE_FLOAT:
|
||||||
|
return cursor.getDouble(column);
|
||||||
|
case Cursor.FIELD_TYPE_BLOB:
|
||||||
|
return cursor.getBlob(column);
|
||||||
|
case Cursor.FIELD_TYPE_STRING:
|
||||||
|
default:
|
||||||
|
return cursor.getString(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Descriptor implements DatabaseDescriptor {
|
||||||
|
private final SQLCipherOpenHelper sqlCipherOpenHelper;
|
||||||
|
|
||||||
|
Descriptor(@NonNull SQLCipherOpenHelper sqlCipherOpenHelper) {
|
||||||
|
this.sqlCipherOpenHelper = sqlCipherOpenHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return sqlCipherOpenHelper.getDatabaseName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull SQLiteDatabase getReadable() {
|
||||||
|
return sqlCipherOpenHelper.getReadableDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull SQLiteDatabase getWritable() {
|
||||||
|
return sqlCipherOpenHelper.getWritableDatabase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
app/src/flipper/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Signal (Flipper)</string>
|
||||||
|
</resources>
|
||||||
764
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,764 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="org.thoughtcrime.securesms">
|
||||||
|
|
||||||
|
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle" />
|
||||||
|
|
||||||
|
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
|
||||||
|
android:label="Access to TextSecure Secrets"
|
||||||
|
android:protectionLevel="signature" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.location" android:required="false"/>
|
||||||
|
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
|
||||||
|
<uses-feature android:name="android.hardware.location.gps" android:required="false"/>
|
||||||
|
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
|
||||||
|
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
|
||||||
|
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
|
||||||
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
|
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_PROFILE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_PROFILE"/>
|
||||||
|
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
|
||||||
|
tools:ignore="ProtectedPermissions"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_SMS"/>
|
||||||
|
<uses-permission android:name="android.permission.SEND_SMS"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
|
||||||
|
|
||||||
|
<!-- For sending/receiving events -->
|
||||||
|
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_CALENDAR"/>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Normal -->
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||||
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||||
|
|
||||||
|
<!-- So we can add a TextSecure 'Account' -->
|
||||||
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||||
|
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
|
||||||
|
|
||||||
|
<!-- For conversation 'shortcuts' on the desktop -->
|
||||||
|
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
|
||||||
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||||
|
|
||||||
|
<!-- For fixing MMS -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||||
|
|
||||||
|
<!-- Set image as wallpaper -->
|
||||||
|
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||||
|
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
|
||||||
|
|
||||||
|
<application android:name=".ApplicationContext"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
tools:replace="android:allowBackup"
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
|
android:largeHeap="true">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.geo.API_KEY"
|
||||||
|
android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/>
|
||||||
|
|
||||||
|
<meta-data android:name="com.google.android.gms.version"
|
||||||
|
android:value="@integer/google_play_services_version" />
|
||||||
|
|
||||||
|
<meta-data android:name="com.google.android.gms.car.application"
|
||||||
|
android:resource="@xml/automotive_app_desc" />
|
||||||
|
|
||||||
|
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
|
||||||
|
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
||||||
|
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
|
||||||
|
|
||||||
|
<activity android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
|
||||||
|
android:theme="@style/TextSecure.LightTheme.WebRTCCall"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:supportsPictureInPicture="true"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||||
|
android:taskAffinity=".calling"
|
||||||
|
android:launchMode="singleTask"/>
|
||||||
|
|
||||||
|
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
|
||||||
|
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".InviteActivity"
|
||||||
|
android:theme="@style/Signal.Light.NoActionBar.Invite"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.thoughtcrime.securesms.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".PromptMmsActivity"
|
||||||
|
android:label="Configure MMS Settings"
|
||||||
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".DeviceProvisioningActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="tsdevice"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".preferences.MmsPreferencesActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".sharing.ShareActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:noHistory="true"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:mimeType="audio/*" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
<data android:mimeType="application/*"/>
|
||||||
|
<data android:mimeType="text/*"/>
|
||||||
|
<data android:mimeType="*/*"/>
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.chooser.chooser_target_service"
|
||||||
|
android:value=".service.DirectShareService" />
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".stickers.StickerPackPreviewActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="sgnl"
|
||||||
|
android:host="addstickers" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<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"
|
||||||
|
android:host="signal.art"
|
||||||
|
android:pathPrefix="/addstickers"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity-alias android:name=".RoutingActivity"
|
||||||
|
android:targetActivity=".MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
|
||||||
|
android:resource="@mipmap/ic_launcher" />
|
||||||
|
<meta-data android:name="com.sec.minimode.icon.landscape.normal"
|
||||||
|
android:resource="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity android:name=".conversation.ConversationActivity"
|
||||||
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.thoughtcrime.securesms.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".longmessage.LongMessageActivity" />
|
||||||
|
|
||||||
|
<activity android:name=".conversation.ConversationPopupActivity"
|
||||||
|
android:windowSoftInputMode="stateVisible"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:theme="@style/TextSecure.LightTheme.Popup"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
|
<activity android:name=".messagedetails.MessageDetailsActivity"
|
||||||
|
android:label="@string/AndroidManifest__message_details"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.pendingmemberinvites.PendingMemberInvitesActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.managegroup.ManageGroupActivity"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".recipients.ui.managerecipient.ManageRecipientActivity"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".DatabaseMigrationActivity"
|
||||||
|
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".migrations.ApplicationMigrationActivity"
|
||||||
|
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".PassphraseCreateActivity"
|
||||||
|
android:label="@string/AndroidManifest__create_passphrase"
|
||||||
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".PassphrasePromptActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/TextSecure.LightIntroTheme"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".NewConversationActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="stateAlwaysVisible"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".PushContactSelectionActivity"
|
||||||
|
android:label="@string/AndroidManifest__select_contacts"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".giph.ui.GiphyActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".mediasend.MediaSendActivity"
|
||||||
|
android:theme="@style/TextSecure.FullScreenMedia"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".PassphraseChangeActivity"
|
||||||
|
android:label="@string/AndroidManifest__change_passphrase"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".VerifyIdentityActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".ApplicationPreferencesActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".revealable.ViewOnceMessageActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/TextSecure.FullScreenMedia"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".stickers.StickerManagementActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".DeviceActivity"
|
||||||
|
android:label="@string/AndroidManifest__linked_devices"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".logsubmit.SubmitDebugLogActivity"
|
||||||
|
android:label="@string/AndroidManifest__log_submit"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".MediaPreviewActivity"
|
||||||
|
android:label="@string/AndroidManifest__media_preview"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".AvatarPreviewActivity"
|
||||||
|
android:label="@string/AndroidManifest__media_preview"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".mediaoverview.MediaOverviewActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".DummyActivity"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
|
android:enabled="true"
|
||||||
|
android:allowTaskReparenting="true"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:alwaysRetainTaskState="false"
|
||||||
|
android:stateNotNeeded="true"
|
||||||
|
android:clearTaskOnLaunch="true"
|
||||||
|
android:finishOnTaskLaunch="true" />
|
||||||
|
|
||||||
|
<activity android:name=".PlayServicesProblemActivity"
|
||||||
|
android:theme="@style/TextSecure.DialogActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".SmsSendtoActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SENDTO" />
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="sms" />
|
||||||
|
<data android:scheme="smsto" />
|
||||||
|
<data android:scheme="mms" />
|
||||||
|
<data android:scheme="mmsto" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:theme="@style/NoAnimation.Theme.BlackScreen"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".mediasend.AvatarSelectionActivity"
|
||||||
|
android:theme="@style/TextSecure.FullScreenMedia"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".BlockedContactsActivity"
|
||||||
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
|
||||||
|
android:theme="@style/TextSecure.DarkTheme"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".profiles.edit.EditProfileActivity"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
|
||||||
|
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".lock.v2.KbsMigrationActivity"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".ClearProfileAvatarActivity"
|
||||||
|
android:theme="@style/Theme.AppCompat.Dialog.Alert"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:icon="@drawable/clear_profile_avatar"
|
||||||
|
android:label="@string/AndroidManifest_remove_photo">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".messagerequests.MessageRequestMegaphoneActivity"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".contactshare.ContactShareEditActivity"
|
||||||
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".contactshare.ContactNameEditActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".contactshare.SharedContactDetailsActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".ShortcutLauncherActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:exported="true"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".maps.PlacePickerActivity"
|
||||||
|
android:label="@string/PlacePickerActivity_title"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".MainActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
|
<activity android:name=".pin.PinRestoreActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.addtogroup.AddToGroupsActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
|
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
||||||
|
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||||
|
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||||
|
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
|
||||||
|
|
||||||
|
<service android:name=".service.QuickResponseService"
|
||||||
|
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
||||||
|
android:exported="true" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:scheme="sms" />
|
||||||
|
<data android:scheme="smsto" />
|
||||||
|
<data android:scheme="mms" />
|
||||||
|
<data android:scheme="mmsto" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.accounts.AccountAuthenticator" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" />
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service android:name=".service.ContactsSyncAdapterService" android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.SyncAdapter"/>
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" />
|
||||||
|
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service android:name=".service.DirectShareService"
|
||||||
|
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.chooser.ChooserTargetService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service android:name=".service.GenericForegroundService"/>
|
||||||
|
|
||||||
|
<service android:name=".gcm.FcmFetchService" />
|
||||||
|
|
||||||
|
<service android:name=".gcm.FcmReceiveService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver android:name=".service.SmsListener"
|
||||||
|
android:permission="android.permission.BROADCAST_SMS"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter android:priority="1001">
|
||||||
|
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.provider.Telephony.SMS_DELIVER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".service.SmsDeliveryListener"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.services.MESSAGE_SENT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".service.MmsListener"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BROADCAST_WAP_PUSH">
|
||||||
|
<intent-filter android:priority="1001">
|
||||||
|
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED"/>
|
||||||
|
<data android:mimeType="application/vnd.wap.mms-message" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
|
||||||
|
<data android:mimeType="application/vnd.wap.mms-message" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.MarkReadReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.notifications.CLEAR"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.RemoteReplyReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.notifications.WEAR_REPLY"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.AndroidAutoHeardReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_HEARD"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.AndroidAutoReplyReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_REPLY"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".service.ExpirationListener" />
|
||||||
|
|
||||||
|
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
||||||
|
|
||||||
|
<provider android:name=".providers.PartProvider"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:authorities="org.thoughtcrime.provider.securesms" />
|
||||||
|
|
||||||
|
<provider android:name=".providers.MmsBodyProvider"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:authorities="org.thoughtcrime.provider.securesms.mms" />
|
||||||
|
|
||||||
|
<provider android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="org.thoughtcrime.securesms.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
|
||||||
|
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" />
|
||||||
|
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
<provider android:name=".database.DatabaseContentProviders$Conversation"
|
||||||
|
android:authorities="org.thoughtcrime.securesms.database.conversation"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<provider android:name=".database.DatabaseContentProviders$ConversationList"
|
||||||
|
android:authorities="org.thoughtcrime.securesms.database.conversationlist"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<provider android:name=".database.DatabaseContentProviders$Attachment"
|
||||||
|
android:authorities="org.thoughtcrime.securesms.database.attachment"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<provider android:name=".database.DatabaseContentProviders$Sticker"
|
||||||
|
android:authorities="org.thoughtcrime.securesms.database.sticker"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<provider android:name=".database.DatabaseContentProviders$StickerPack"
|
||||||
|
android:authorities="org.thoughtcrime.securesms.database.stickerpack"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<receiver android:name=".service.BootReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.RESTART"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".service.DirectoryRefreshListener">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".service.RotateSignedPreKeyListener">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".service.RotateSenderCertificateListener">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".service.LocalBackupListener">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".service.PersistentConnectionBootListener">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.LocaleChangedReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.LOCALE_CHANGED"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver"/>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.DeleteNotificationReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thoughtcrime.securesms.DELETE_NOTIFICATION"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".service.PanicResponderListener"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".gcm.FcmJobService"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
|
android:enabled="@bool/enable_job_service"
|
||||||
|
tools:targetApi="26" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
|
android:enabled="@bool/enable_job_service"
|
||||||
|
tools:targetApi="26" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".jobmanager.KeepAliveService"
|
||||||
|
android:enabled="@bool/enable_alarm_manager" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver"
|
||||||
|
android:enabled="@bool/enable_alarm_manager" />
|
||||||
|
|
||||||
|
<!-- Probably don't need this one -->
|
||||||
|
<receiver
|
||||||
|
android:name=".jobmanager.BootReceiver"
|
||||||
|
android:enabled="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
||||||
|
|
||||||
|
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false"/>
|
||||||
|
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||||
|
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
|
||||||
|
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
|
||||||
|
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="632.0dip" />
|
||||||
|
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="598.0dip" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
BIN
app/src/main/assets/emoji/Activity.webp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
app/src/main/assets/emoji/Flags_0.webp
Normal file
|
After Width: | Height: | Size: 415 KiB |
BIN
app/src/main/assets/emoji/Flags_1.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
app/src/main/assets/emoji/Foods.webp
Normal file
|
After Width: | Height: | Size: 344 KiB |
BIN
app/src/main/assets/emoji/Nature.webp
Normal file
|
After Width: | Height: | Size: 395 KiB |
BIN
app/src/main/assets/emoji/Objects.webp
Normal file
|
After Width: | Height: | Size: 622 KiB |
BIN
app/src/main/assets/emoji/People_0.webp
Normal file
|
After Width: | Height: | Size: 599 KiB |
BIN
app/src/main/assets/emoji/People_1.webp
Normal file
|
After Width: | Height: | Size: 559 KiB |
BIN
app/src/main/assets/emoji/People_2.webp
Normal file
|
After Width: | Height: | Size: 643 KiB |
BIN
app/src/main/assets/emoji/People_3.webp
Normal file
|
After Width: | Height: | Size: 647 KiB |
BIN
app/src/main/assets/emoji/People_4.webp
Normal file
|
After Width: | Height: | Size: 602 KiB |
BIN
app/src/main/assets/emoji/People_5.webp
Normal file
|
After Width: | Height: | Size: 589 KiB |
BIN
app/src/main/assets/emoji/People_6.webp
Normal file
|
After Width: | Height: | Size: 531 KiB |
BIN
app/src/main/assets/emoji/Places.webp
Normal file
|
After Width: | Height: | Size: 589 KiB |
BIN
app/src/main/assets/emoji/Symbols.webp
Normal file
|
After Width: | Height: | Size: 384 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||||
|
|
||||||
|
public final class AppCapabilities {
|
||||||
|
|
||||||
|
private AppCapabilities() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final boolean UUID_CAPABLE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param storageCapable Whether or not the user can use storage service. This is another way of
|
||||||
|
* asking if the user has set a Signal PIN or not.
|
||||||
|
*/
|
||||||
|
public static SignalServiceProfile.Capabilities getCapabilities(boolean storageCapable) {
|
||||||
|
return new SignalServiceProfile.Capabilities(UUID_CAPABLE, FeatureFlags.groupsV2(), storageCapable);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.insights.InsightsOptOut;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||||
|
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
|
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule of thumb: if there's something you want to do on the first app launch that involves
|
||||||
|
* persisting state to the database, you'll almost certainly *also* want to do it post backup
|
||||||
|
* restore, since a backup restore will wipe the current state of the database.
|
||||||
|
*/
|
||||||
|
public final class AppInitialization {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(AppInitialization.class);
|
||||||
|
|
||||||
|
private AppInitialization() {}
|
||||||
|
|
||||||
|
public static void onFirstEverAppLaunch(@NonNull Context context) {
|
||||||
|
Log.i(TAG, "onFirstEverAppLaunch()");
|
||||||
|
|
||||||
|
InsightsOptOut.userRequestedOptOut(context);
|
||||||
|
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
|
||||||
|
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
||||||
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
|
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
|
||||||
|
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||||
|
SignalStore.onFirstEverAppLaunch();
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onPostBackupRestore(@NonNull Context context) {
|
||||||
|
Log.i(TAG, "onPostBackupRestore()");
|
||||||
|
|
||||||
|
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||||
|
SignalStore.onFirstEverAppLaunch();
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,59 +17,58 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
|
||||||
import androidx.camera.camera2.Camera2AppConfig;
|
|
||||||
import androidx.camera.core.CameraX;
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||||
import androidx.multidex.MultiDexApplication;
|
import androidx.multidex.MultiDexApplication;
|
||||||
|
|
||||||
import com.google.android.gms.security.ProviderInstaller;
|
import com.google.android.gms.security.ProviderInstaller;
|
||||||
|
|
||||||
import org.conscrypt.Conscrypt;
|
import org.conscrypt.Conscrypt;
|
||||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||||
import org.signal.ringrtc.CallConnectionFactory;
|
import org.signal.ringrtc.CallManager;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
||||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||||
import org.thoughtcrime.securesms.insights.InsightsOptOut;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
|
||||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
||||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
import org.thoughtcrime.securesms.logging.SignalUncaughtExceptionHandler;
|
||||||
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
|
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
|
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||||
|
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
|
||||||
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
||||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||||
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
|
|
||||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||||
import org.thoughtcrime.securesms.util.FrameRateTracker;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||||
import org.webrtc.voiceengine.WebRtcAudioManager;
|
import org.webrtc.voiceengine.WebRtcAudioManager;
|
||||||
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
||||||
@@ -96,7 +95,6 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
private ViewOnceMessageManager viewOnceMessageManager;
|
private ViewOnceMessageManager viewOnceMessageManager;
|
||||||
private TypingStatusRepository typingStatusRepository;
|
private TypingStatusRepository typingStatusRepository;
|
||||||
private TypingStatusSender typingStatusSender;
|
private TypingStatusSender typingStatusSender;
|
||||||
private IncomingMessageObserver incomingMessageObserver;
|
|
||||||
private PersistentLogger persistentLogger;
|
private PersistentLogger persistentLogger;
|
||||||
|
|
||||||
private volatile boolean isAppVisible;
|
private volatile boolean isAppVisible;
|
||||||
@@ -107,13 +105,14 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
Log.i(TAG, "onCreate()");
|
|
||||||
initializeSecurityProvider();
|
initializeSecurityProvider();
|
||||||
initializeLogging();
|
initializeLogging();
|
||||||
|
Log.i(TAG, "onCreate()");
|
||||||
initializeCrashHandling();
|
initializeCrashHandling();
|
||||||
initializeFirstEverAppLaunch();
|
|
||||||
initializeAppDependencies();
|
initializeAppDependencies();
|
||||||
|
initializeFirstEverAppLaunch();
|
||||||
initializeApplicationMigrations();
|
initializeApplicationMigrations();
|
||||||
initializeMessageRetrieval();
|
initializeMessageRetrieval();
|
||||||
initializeExpiringMessageManager();
|
initializeExpiringMessageManager();
|
||||||
@@ -127,8 +126,14 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
initializeRingRtc();
|
initializeRingRtc();
|
||||||
initializePendingMessages();
|
initializePendingMessages();
|
||||||
initializeBlobProvider();
|
initializeBlobProvider();
|
||||||
initializeCameraX();
|
initializeCleanup();
|
||||||
|
|
||||||
|
FeatureFlags.init();
|
||||||
NotificationChannels.create(this);
|
NotificationChannels.create(this);
|
||||||
|
RefreshPreKeysJob.scheduleIfNecessary();
|
||||||
|
StorageSyncHelper.scheduleRoutineSync();
|
||||||
|
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
|
||||||
|
RegistrationUtil.maybeMarkRegistrationComplete(this);
|
||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 21) {
|
if (Build.VERSION.SDK_INT < 21) {
|
||||||
@@ -136,16 +141,19 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().beginJobLoop();
|
ApplicationDependencies.getJobManager().beginJobLoop();
|
||||||
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart(@NonNull LifecycleOwner owner) {
|
public void onStart(@NonNull LifecycleOwner owner) {
|
||||||
isAppVisible = true;
|
isAppVisible = true;
|
||||||
Log.i(TAG, "App is now visible.");
|
Log.i(TAG, "App is now visible.");
|
||||||
|
FeatureFlags.refreshIfNecessary();
|
||||||
ApplicationDependencies.getRecipientCache().warmUp();
|
ApplicationDependencies.getRecipientCache().warmUp();
|
||||||
executePendingContactSync();
|
executePendingContactSync();
|
||||||
KeyCachingService.onAppForegrounded(this);
|
KeyCachingService.onAppForegrounded(this);
|
||||||
ApplicationDependencies.getFrameRateTracker().begin();
|
ApplicationDependencies.getFrameRateTracker().begin();
|
||||||
|
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -153,7 +161,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
isAppVisible = false;
|
isAppVisible = false;
|
||||||
Log.i(TAG, "App is no longer visible.");
|
Log.i(TAG, "App is no longer visible.");
|
||||||
KeyCachingService.onAppBackgrounded(this);
|
KeyCachingService.onAppBackgrounded(this);
|
||||||
MessageNotifier.setVisibleThread(-1);
|
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
||||||
ApplicationDependencies.getFrameRateTracker().end();
|
ApplicationDependencies.getFrameRateTracker().end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +222,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
private void initializeCrashHandling() {
|
private void initializeCrashHandling() {
|
||||||
final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler();
|
final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(originalHandler));
|
Thread.setDefaultUncaughtExceptionHandler(new SignalUncaughtExceptionHandler(originalHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeApplicationMigrations() {
|
private void initializeApplicationMigrations() {
|
||||||
@@ -222,7 +230,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void initializeMessageRetrieval() {
|
public void initializeMessageRetrieval() {
|
||||||
this.incomingMessageObserver = new IncomingMessageObserver(this);
|
ApplicationDependencies.getIncomingMessageObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAppDependencies() {
|
private void initializeAppDependencies() {
|
||||||
@@ -233,10 +241,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
|
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
|
||||||
if (!SQLCipherOpenHelper.databaseFileExists(this)) {
|
if (!SQLCipherOpenHelper.databaseFileExists(this)) {
|
||||||
Log.i(TAG, "First ever app launch!");
|
Log.i(TAG, "First ever app launch!");
|
||||||
|
AppInitialization.onFirstEverAppLaunch(this);
|
||||||
InsightsOptOut.userRequestedOptOut(this);
|
|
||||||
TextSecurePreferences.setAppMigrationVersion(this, ApplicationMigrations.CURRENT_VERSION);
|
|
||||||
TextSecurePreferences.setJobManagerVersion(this, JobManager.CURRENT_VERSION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
|
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
|
||||||
@@ -297,6 +302,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
add("Moto G4");
|
add("Moto G4");
|
||||||
add("TA-1053");
|
add("TA-1053");
|
||||||
add("Mi A1");
|
add("Mi A1");
|
||||||
|
add("Mi A2");
|
||||||
add("E5823"); // Sony z5 compact
|
add("E5823"); // Sony z5 compact
|
||||||
add("Redmi Note 5");
|
add("Redmi Note 5");
|
||||||
add("FP2"); // Fairphone FP2
|
add("FP2"); // Fairphone FP2
|
||||||
@@ -316,9 +322,9 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
CallConnectionFactory.initialize(this, new RingRtcLogger());
|
CallManager.initialize(this, new RingRtcLogger());
|
||||||
} catch (UnsatisfiedLinkError e) {
|
} catch (UnsatisfiedLinkError e) {
|
||||||
Log.w(TAG, e);
|
throw new AssertionError("Unable to load ringrtc library", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,22 +366,16 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeBlobProvider() {
|
private void initializeBlobProvider() {
|
||||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
BlobProvider.getInstance().onSessionStart(this);
|
BlobProvider.getInstance().onSessionStart(this);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
private void initializeCleanup() {
|
||||||
private void initializeCameraX() {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
if (CameraXUtil.isSupported()) {
|
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
||||||
new Thread(() -> {
|
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
||||||
try {
|
});
|
||||||
CameraX.init(this, Camera2AppConfig.create(this));
|
|
||||||
} catch (Throwable t) {
|
|
||||||
Log.w(TAG, "Failed to initialize CameraX.");
|
|
||||||
}
|
|
||||||
}, "signal-camerax-initialization").start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -29,6 +29,7 @@ import androidx.fragment.app.FragmentManager;
|
|||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.help.HelpFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
|
||||||
@@ -38,11 +39,10 @@ import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
|||||||
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
||||||
|
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.usernames.ProfileEditActivityV2;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ import org.thoughtcrime.securesms.util.ThemeUtil;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarActivity
|
public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
{
|
{
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -67,6 +67,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
|
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
|
||||||
private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
|
private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
|
||||||
private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
|
private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
|
||||||
|
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
|
||||||
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
@@ -155,6 +156,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_STORAGE));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_STORAGE));
|
||||||
this.findPreference(PREFERENCE_CATEGORY_DEVICES)
|
this.findPreference(PREFERENCE_CATEGORY_DEVICES)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
|
||||||
|
this.findPreference(PREFERENCE_CATEGORY_HELP)
|
||||||
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_HELP));
|
||||||
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
|
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
||||||
|
|
||||||
@@ -241,6 +244,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
case PREFERENCE_CATEGORY_ADVANCED:
|
case PREFERENCE_CATEGORY_ADVANCED:
|
||||||
fragment = new AdvancedPreferenceFragment();
|
fragment = new AdvancedPreferenceFragment();
|
||||||
break;
|
break;
|
||||||
|
case PREFERENCE_CATEGORY_HELP:
|
||||||
|
fragment = new HelpFragment();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
@@ -266,14 +272,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
if (FeatureFlags.USERNAMES) {
|
requireActivity().startActivity(EditProfileActivity.getIntentForUserProfileEdit(preference.getContext()));
|
||||||
requireActivity().startActivity(ProfileEditActivityV2.getLaunchIntent(requireContext()));
|
|
||||||
} else {
|
|
||||||
Intent intent = new Intent(preference.getContext(), CreateProfileActivity.class);
|
|
||||||
intent.putExtra(CreateProfileActivity.EXCLUDE_SYSTEM, true);
|
|
||||||
|
|
||||||
requireActivity().startActivity(intent);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.transition.TransitionInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget;
|
||||||
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity for displaying avatars full screen.
|
||||||
|
*/
|
||||||
|
public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(AvatarPreviewActivity.class);
|
||||||
|
|
||||||
|
private static final String RECIPIENT_ID_EXTRA = "recipient_id";
|
||||||
|
|
||||||
|
public static @NonNull Intent intentFromRecipientId(@NonNull Context context,
|
||||||
|
@NonNull RecipientId recipientId)
|
||||||
|
{
|
||||||
|
Intent intent = new Intent(context, AvatarPreviewActivity.class);
|
||||||
|
intent.putExtra(RECIPIENT_ID_EXTRA, recipientId.serialize());
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bundle createTransitionBundle(@NonNull Activity activity, @NonNull View from) {
|
||||||
|
return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
|
super.onCreate(savedInstanceState, ready);
|
||||||
|
|
||||||
|
setTheme(R.style.TextSecure_MediaPreview);
|
||||||
|
setContentView(R.layout.contact_photo_preview_activity);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
postponeEnterTransition();
|
||||||
|
TransitionInflater inflater = TransitionInflater.from(this);
|
||||||
|
getWindow().setSharedElementEnterTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_enter_transition_set));
|
||||||
|
getWindow().setSharedElementReturnTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_return_transition_set));
|
||||||
|
}
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
|
||||||
|
ImageView avatar = findViewById(R.id.avatar);
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
|
||||||
|
showSystemUI();
|
||||||
|
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
RecipientId recipientId = RecipientId.from(getIntent().getStringExtra(RECIPIENT_ID_EXTRA));
|
||||||
|
|
||||||
|
Recipient.live(recipientId).observe(this, recipient -> {
|
||||||
|
ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient, recipient.getProfileAvatar())
|
||||||
|
: recipient.getContactPhoto();
|
||||||
|
FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
|
||||||
|
: recipient.getFallbackContactPhoto();
|
||||||
|
|
||||||
|
Resources resources = this.getResources();
|
||||||
|
|
||||||
|
GlideApp.with(this)
|
||||||
|
.asBitmap()
|
||||||
|
.load(contactPhoto)
|
||||||
|
.fallback(fallbackPhoto.asCallCard(this))
|
||||||
|
.error(fallbackPhoto.asCallCard(this))
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
|
.addListener(new RequestListener<Bitmap>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
|
||||||
|
Log.w(TAG, "Unable to load avatar, or avatar removed, closing");
|
||||||
|
finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(new CustomTarget<Bitmap>() {
|
||||||
|
@Override
|
||||||
|
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
|
||||||
|
avatar.setImageDrawable(RoundedBitmapDrawableFactory.create(resources, resource));
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
startPostponedEnterTransition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbar.setTitle(recipient.getDisplayName(context));
|
||||||
|
});
|
||||||
|
|
||||||
|
avatar.setOnClickListener(v -> toggleUiVisibility());
|
||||||
|
|
||||||
|
showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showAndHideWithSystemUI(@NonNull Window window, @NonNull View... views) {
|
||||||
|
window.getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> {
|
||||||
|
boolean hide = (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
|
||||||
|
|
||||||
|
for (View view : views) {
|
||||||
|
view.animate()
|
||||||
|
.alpha(hide ? 0 : 1)
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleUiVisibility() {
|
||||||
|
int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
|
||||||
|
if ((systemUiVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
|
||||||
|
showSystemUI();
|
||||||
|
} else {
|
||||||
|
hideSystemUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideSystemUI() {
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_IMMERSIVE |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||||
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_FULLSCREEN );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSystemUI() {
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSupportNavigateUp() {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageActivityHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all activities. The vast majority of activities shouldn't extend this directly.
|
||||||
|
* Instead, they should extend {@link PassphraseRequiredActivity} so they're protected by
|
||||||
|
* screen lock.
|
||||||
|
*/
|
||||||
|
public abstract class BaseActivity extends AppCompatActivity {
|
||||||
|
private static final String TAG = Log.tag(BaseActivity.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
logEvent("onCreate()");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
initializeScreenshotSecurity();
|
||||||
|
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
logEvent("onStart()");
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
logEvent("onStop()");
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
logEvent("onDestroy()");
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeScreenshotSecurity() {
|
||||||
|
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
|
} else {
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void startActivitySceneTransition(Intent intent, View sharedView, String transitionName) {
|
||||||
|
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this, sharedView, transitionName)
|
||||||
|
.toBundle();
|
||||||
|
ActivityCompat.startActivity(this, intent, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||||
|
protected void setStatusBarColor(int color) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
getWindow().setStatusBarColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context newBase) {
|
||||||
|
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(newBase, TextSecurePreferences.getLanguage(newBase)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logEvent(@NonNull String event) {
|
||||||
|
Log.d(TAG, "[" + Log.tag(getClass()) + "] " + event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||||
|
import org.thoughtcrime.securesms.conversation.ConversationMessage;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface BindableConversationItem extends Unbindable {
|
||||||
|
void bind(@NonNull ConversationMessage messageRecord,
|
||||||
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
|
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||||
|
@NonNull GlideRequests glideRequests,
|
||||||
|
@NonNull Locale locale,
|
||||||
|
@NonNull Set<ConversationMessage> batchSelected,
|
||||||
|
@NonNull Recipient recipients,
|
||||||
|
@Nullable String searchQuery,
|
||||||
|
boolean pulseHighlight);
|
||||||
|
|
||||||
|
ConversationMessage getConversationMessage();
|
||||||
|
|
||||||
|
void setEventListener(@Nullable EventListener listener);
|
||||||
|
|
||||||
|
interface EventListener {
|
||||||
|
void onQuoteClicked(MmsMessageRecord messageRecord);
|
||||||
|
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
||||||
|
void onMoreTextClicked(@NonNull RecipientId conversationRecipientId, long messageId, boolean isMms);
|
||||||
|
void onStickerClicked(@NonNull StickerLocator stickerLocator);
|
||||||
|
void onViewOnceMessageClicked(@NonNull MmsMessageRecord messageRecord);
|
||||||
|
void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView);
|
||||||
|
void onAddToContactsClicked(@NonNull Contact contact);
|
||||||
|
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
|
||||||
|
void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
|
||||||
|
void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms);
|
||||||
|
void onGroupMemberAvatarClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId);
|
||||||
|
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface BindableConversationListItem extends Unbindable {
|
||||||
|
|
||||||
|
void bind(@NonNull ThreadRecord thread,
|
||||||
|
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
||||||
|
@NonNull Set<Long> typingThreads,
|
||||||
|
@NonNull Set<Long> selectedThreads, boolean batchMode);
|
||||||
|
|
||||||
|
void setBatchMode(boolean batchMode);
|
||||||
|
void updateTypingIndicator(@NonNull Set<Long> typingThreads);
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be used whenever we want to prompt the user to block/unblock a recipient.
|
||||||
|
*/
|
||||||
|
public final class BlockUnblockDialog {
|
||||||
|
|
||||||
|
private BlockUnblockDialog() { }
|
||||||
|
|
||||||
|
public static void showBlockFor(@NonNull Context context,
|
||||||
|
@NonNull Lifecycle lifecycle,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onBlock)
|
||||||
|
{
|
||||||
|
SimpleTask.run(lifecycle,
|
||||||
|
() -> buildBlockFor(context, recipient, onBlock, null),
|
||||||
|
AlertDialog.Builder::show);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showBlockAndDeleteFor(@NonNull Context context,
|
||||||
|
@NonNull Lifecycle lifecycle,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onBlock,
|
||||||
|
@NonNull Runnable onBlockAndDelete)
|
||||||
|
{
|
||||||
|
SimpleTask.run(lifecycle,
|
||||||
|
() -> buildBlockFor(context, recipient, onBlock, onBlockAndDelete),
|
||||||
|
AlertDialog.Builder::show);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showUnblockFor(@NonNull Context context,
|
||||||
|
@NonNull Lifecycle lifecycle,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onUnblock)
|
||||||
|
{
|
||||||
|
SimpleTask.run(lifecycle,
|
||||||
|
() -> buildUnblockFor(context, recipient, onUnblock),
|
||||||
|
AlertDialog.Builder::show);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private static AlertDialog.Builder buildBlockFor(@NonNull Context context,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onBlock,
|
||||||
|
@Nullable Runnable onBlockAndDelete)
|
||||||
|
{
|
||||||
|
recipient = recipient.resolve();
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
|
Resources resources = context.getResources();
|
||||||
|
|
||||||
|
if (recipient.isGroup()) {
|
||||||
|
if (DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireGroupId())) {
|
||||||
|
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_and_leave_s, recipient.getDisplayName(context)));
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_you_will_no_longer_receive_messages_or_updates);
|
||||||
|
builder.setPositiveButton(R.string.BlockUnblockDialog_block_and_leave, ((dialog, which) -> onBlock.run()));
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
} else {
|
||||||
|
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context)));
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_group_members_wont_be_able_to_add_you);
|
||||||
|
builder.setPositiveButton(R.string.RecipientPreferenceActivity_block, ((dialog, which) -> onBlock.run()));
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context)));
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_blocked_people_wont_be_able_to_call_you_or_send_you_messages);
|
||||||
|
|
||||||
|
if (onBlockAndDelete != null) {
|
||||||
|
builder.setNeutralButton(android.R.string.cancel, null);
|
||||||
|
builder.setPositiveButton(R.string.BlockUnblockDialog_block_and_delete, (d, w) -> onBlockAndDelete.run());
|
||||||
|
builder.setNegativeButton(R.string.BlockUnblockDialog_block, (d, w) -> onBlock.run());
|
||||||
|
} else {
|
||||||
|
builder.setPositiveButton(R.string.BlockUnblockDialog_block, ((dialog, which) -> onBlock.run()));
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private static AlertDialog.Builder buildUnblockFor(@NonNull Context context,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onUnblock)
|
||||||
|
{
|
||||||
|
recipient = recipient.resolve();
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
|
Resources resources = context.getResources();
|
||||||
|
|
||||||
|
if (recipient.isGroup()) {
|
||||||
|
if (DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireGroupId())) {
|
||||||
|
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_group_members_will_be_able_to_add_you);
|
||||||
|
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
} else {
|
||||||
|
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_group_members_will_be_able_to_add_you);
|
||||||
|
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_you_will_be_able_to_call_and_message_each_other);
|
||||||
|
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.ListFragment;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.cursoradapter.widget.CursorAdapter;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.cursoradapter.widget.CursorAdapter;
|
||||||
|
import androidx.fragment.app.ListFragment;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader;
|
import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
@@ -25,21 +24,18 @@ import org.thoughtcrime.securesms.preferences.BlockedContactListItem;
|
|||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
|
||||||
public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity {
|
public class BlockedContactsActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPreCreate() {
|
public void onPreCreate() {
|
||||||
dynamicTheme.onCreate(this);
|
dynamicTheme.onCreate(this);
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
@@ -51,16 +47,12 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
dynamicTheme.onResume(this);
|
dynamicTheme.onResume(this);
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onSupportNavigateUp() {
|
||||||
switch (item.getItemId()) {
|
onBackPressed();
|
||||||
case android.R.id.home: finish(); return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BlockedContactsFragment
|
public static class BlockedContactsFragment
|
||||||
@@ -76,14 +68,14 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
setListAdapter(new BlockedContactAdapter(getActivity(), GlideApp.with(this), null));
|
setListAdapter(new BlockedContactAdapter(requireActivity(), GlideApp.with(this), null));
|
||||||
getLoaderManager().initLoader(0, null, this);
|
LoaderManager.getInstance(this).initLoader(0, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
getLoaderManager().restartLoader(0, null, this);
|
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -114,10 +106,10 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
Recipient recipient = ((BlockedContactListItem)view).getRecipient();
|
Recipient recipient = ((BlockedContactListItem)view).getRecipient();
|
||||||
Intent intent = new Intent(getActivity(), RecipientPreferenceActivity.class);
|
BlockUnblockDialog.showUnblockFor(requireContext(), getLifecycle(), recipient, () -> {
|
||||||
intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId());
|
RecipientUtil.unblock(requireContext(), recipient);
|
||||||
|
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
||||||
startActivity(intent);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BlockedContactAdapter extends CursorAdapter {
|
private static class BlockedContactAdapter extends CursorAdapter {
|
||||||
@@ -143,7 +135,5 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
|
|||||||
((BlockedContactListItem) view).set(glideRequests, recipient);
|
((BlockedContactListItem) view).set(glideRequests, recipient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
|
||||||
|
public class ClearProfileAvatarActivity extends Activity {
|
||||||
|
|
||||||
|
private static final String ARG_TITLE = "arg_title";
|
||||||
|
|
||||||
|
public static Intent createForUserProfilePhoto() {
|
||||||
|
return new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent createForGroupProfilePhoto() {
|
||||||
|
Intent intent = new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
|
||||||
|
intent.putExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_group_photo);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
int titleId = getIntent().getIntExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_profile_photo);
|
||||||
|
|
||||||
|
new AlertDialog.Builder(new ContextThemeWrapper(this, DynamicTheme.isDarkTheme(this) ? R.style.TextSecure_DarkTheme : R.style.TextSecure_LightTheme))
|
||||||
|
.setMessage(titleId)
|
||||||
|
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
|
||||||
|
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra("delete", true);
|
||||||
|
setResult(Activity.RESULT_OK, result);
|
||||||
|
finish();
|
||||||
|
})
|
||||||
|
.setOnCancelListener(dialog -> finish())
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,30 +3,28 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.VerifySpan;
|
import org.thoughtcrime.securesms.util.VerifySpan;
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
@@ -51,7 +49,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context));
|
Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context));
|
||||||
String name = recipient.toShortString(context);
|
String name = recipient.getDisplayName(context);
|
||||||
String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name);
|
String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name);
|
||||||
SpannableString spannableString = new SpannableString(introduction + " " +
|
SpannableString spannableString = new SpannableString(introduction + " " +
|
||||||
context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact));
|
context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact));
|
||||||
@@ -105,7 +103,6 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processMessageRecord(messageRecord);
|
processMessageRecord(messageRecord);
|
||||||
processPendingMessageRecords(messageRecord.getThreadId(), mismatch);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -115,26 +112,6 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
else processIncomingMessageRecord(messageRecord);
|
else processIncomingMessageRecord(messageRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processPendingMessageRecords(long threadId, IdentityKeyMismatch mismatch) {
|
|
||||||
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(getContext());
|
|
||||||
Cursor cursor = mmsSmsDatabase.getIdentityConflictMessagesForThread(threadId);
|
|
||||||
MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(cursor);
|
|
||||||
MessageRecord record;
|
|
||||||
|
|
||||||
try {
|
|
||||||
while ((record = reader.getNext()) != null) {
|
|
||||||
for (IdentityKeyMismatch recordMismatch : record.getIdentityKeyMismatches()) {
|
|
||||||
if (mismatch.equals(recordMismatch)) {
|
|
||||||
processMessageRecord(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (reader != null)
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processOutgoingMessageRecord(MessageRecord messageRecord) {
|
private void processOutgoingMessageRecord(MessageRecord messageRecord) {
|
||||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
||||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
|
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
|
||||||
@@ -175,11 +152,13 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
messageRecord.getDateSent(),
|
messageRecord.getDateSent(),
|
||||||
legacy ? Base64.decode(messageRecord.getBody()) : null,
|
legacy ? Base64.decode(messageRecord.getBody()) : null,
|
||||||
!legacy ? Base64.decode(messageRecord.getBody()) : null,
|
!legacy ? Base64.decode(messageRecord.getBody()) : null,
|
||||||
0, null);
|
0,
|
||||||
|
0,
|
||||||
|
null);
|
||||||
|
|
||||||
long pushId = pushDatabase.insert(envelope);
|
long pushId = pushDatabase.insert(envelope);
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().add(new PushDecryptJob(getContext(), pushId, messageRecord.getId()));
|
ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), pushId, messageRecord.getId()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
@@ -19,20 +19,18 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -44,14 +42,16 @@ import java.lang.ref.WeakReference;
|
|||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity
|
public abstract class ContactSelectionActivity extends PassphraseRequiredActivity
|
||||||
implements SwipeRefreshLayout.OnRefreshListener,
|
implements SwipeRefreshLayout.OnRefreshListener,
|
||||||
ContactSelectionListFragment.OnContactSelectedListener
|
ContactSelectionListFragment.OnContactSelectedListener,
|
||||||
|
ContactSelectionListFragment.ScrollCallback
|
||||||
{
|
{
|
||||||
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
public static final String EXTRA_LAYOUT_RES_ID = "layout_res_id";
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||||
|
|
||||||
protected ContactSelectionListFragment contactsFragment;
|
protected ContactSelectionListFragment contactsFragment;
|
||||||
|
|
||||||
@@ -60,18 +60,17 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
|||||||
@Override
|
@Override
|
||||||
protected void onPreCreate() {
|
protected void onPreCreate() {
|
||||||
dynamicTheme.onCreate(this);
|
dynamicTheme.onCreate(this);
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle icicle, boolean ready) {
|
protected void onCreate(Bundle icicle, boolean ready) {
|
||||||
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
||||||
int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL
|
int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL
|
||||||
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS;
|
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
|
||||||
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(R.layout.contact_selection_activity);
|
setContentView(getIntent().getIntExtra(EXTRA_LAYOUT_RES_ID, R.layout.contact_selection_activity));
|
||||||
|
|
||||||
initializeToolbar();
|
initializeToolbar();
|
||||||
initializeResources();
|
initializeResources();
|
||||||
@@ -82,7 +81,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
dynamicTheme.onResume(this);
|
dynamicTheme.onResume(this);
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ContactFilterToolbar getToolbar() {
|
protected ContactFilterToolbar getToolbar() {
|
||||||
@@ -90,10 +88,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeToolbar() {
|
private void initializeToolbar() {
|
||||||
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
|
this.toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
assert getSupportActionBar() != null;
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||||
getSupportActionBar().setIcon(null);
|
getSupportActionBar().setIcon(null);
|
||||||
@@ -116,11 +113,24 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactSelected(Optional<RecipientId> recipientId, String number) {}
|
public boolean onContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {}
|
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBeginScroll() {
|
||||||
|
hideKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideKeyboard() {
|
||||||
|
ServiceUtil.getInputMethodManager(this)
|
||||||
|
.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
|
||||||
|
toolbar.clearFocus();
|
||||||
|
}
|
||||||
|
|
||||||
private static class RefreshDirectoryTask extends AsyncTask<Context, Void, Void> {
|
private static class RefreshDirectoryTask extends AsyncTask<Context, Void, Void> {
|
||||||
|
|
||||||
private final WeakReference<ContactSelectionActivity> activity;
|
private final WeakReference<ContactSelectionActivity> activity;
|
||||||
@@ -0,0 +1,647 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.animation.LayoutTransition;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.CycleInterpolator;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.HorizontalScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintSet;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import androidx.transition.AutoTransition;
|
||||||
|
import androidx.transition.TransitionManager;
|
||||||
|
|
||||||
|
import com.annimon.stream.Collectors;
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
import com.google.android.material.chip.ChipGroup;
|
||||||
|
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactChip;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
|
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||||
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for selecting a one or more contacts from a list.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class ContactSelectionListFragment extends LoggingFragment
|
||||||
|
implements LoaderManager.LoaderCallbacks<Cursor>
|
||||||
|
{
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static final String TAG = Log.tag(ContactSelectionListFragment.class);
|
||||||
|
|
||||||
|
private static final int CHIP_GROUP_EMPTY_CHILD_COUNT = 1;
|
||||||
|
private static final int CHIP_GROUP_REVEAL_DURATION_MS = 150;
|
||||||
|
|
||||||
|
public static final int NO_LIMIT = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
public static final String DISPLAY_MODE = "display_mode";
|
||||||
|
public static final String MULTI_SELECT = "multi_select";
|
||||||
|
public static final String REFRESHABLE = "refreshable";
|
||||||
|
public static final String RECENTS = "recents";
|
||||||
|
public static final String TOTAL_CAPACITY = "total_capacity";
|
||||||
|
public static final String CURRENT_SELECTION = "current_selection";
|
||||||
|
|
||||||
|
private ConstraintLayout constraintLayout;
|
||||||
|
private TextView emptyText;
|
||||||
|
private OnContactSelectedListener onContactSelectedListener;
|
||||||
|
private SwipeRefreshLayout swipeRefresh;
|
||||||
|
private View showContactsLayout;
|
||||||
|
private Button showContactsButton;
|
||||||
|
private TextView showContactsDescription;
|
||||||
|
private ProgressWheel showContactsProgress;
|
||||||
|
private String cursorFilter;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private RecyclerViewFastScroller fastScroller;
|
||||||
|
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
||||||
|
private ChipGroup chipGroup;
|
||||||
|
private HorizontalScrollView chipGroupScrollContainer;
|
||||||
|
private TextView groupLimit;
|
||||||
|
|
||||||
|
@Nullable private FixedViewsAdapter headerAdapter;
|
||||||
|
@Nullable private FixedViewsAdapter footerAdapter;
|
||||||
|
@Nullable private ListCallback listCallback;
|
||||||
|
@Nullable private ScrollCallback scrollCallback;
|
||||||
|
private GlideRequests glideRequests;
|
||||||
|
private int selectionLimit;
|
||||||
|
private Set<RecipientId> currentSelection;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
|
||||||
|
if (context instanceof ListCallback) {
|
||||||
|
listCallback = (ListCallback) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof ScrollCallback) {
|
||||||
|
scrollCallback = (ScrollCallback) context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle icicle) {
|
||||||
|
super.onActivityCreated(icicle);
|
||||||
|
|
||||||
|
initializeCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
Permissions.with(this)
|
||||||
|
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
|
||||||
|
.ifNecessary()
|
||||||
|
.onAllGranted(() -> {
|
||||||
|
if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
|
||||||
|
handleContactPermissionGranted();
|
||||||
|
} else {
|
||||||
|
LoaderManager.getInstance(this).initLoader(0, null, this);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onAnyDenied(() -> {
|
||||||
|
FragmentActivity activity = requireActivity();
|
||||||
|
|
||||||
|
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||||
|
|
||||||
|
if (activity.getIntent().getBooleanExtra(RECENTS, false)) {
|
||||||
|
LoaderManager.getInstance(this).initLoader(0, null, ContactSelectionListFragment.this);
|
||||||
|
} else {
|
||||||
|
initializeNoContactsPermission();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
|
||||||
|
|
||||||
|
emptyText = view.findViewById(android.R.id.empty);
|
||||||
|
recyclerView = view.findViewById(R.id.recycler_view);
|
||||||
|
swipeRefresh = view.findViewById(R.id.swipe_refresh);
|
||||||
|
fastScroller = view.findViewById(R.id.fast_scroller);
|
||||||
|
showContactsLayout = view.findViewById(R.id.show_contacts_container);
|
||||||
|
showContactsButton = view.findViewById(R.id.show_contacts_button);
|
||||||
|
showContactsDescription = view.findViewById(R.id.show_contacts_description);
|
||||||
|
showContactsProgress = view.findViewById(R.id.progress);
|
||||||
|
chipGroup = view.findViewById(R.id.chipGroup);
|
||||||
|
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
|
||||||
|
groupLimit = view.findViewById(R.id.group_limit);
|
||||||
|
constraintLayout = view.findViewById(R.id.container);
|
||||||
|
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
|
recyclerView.setItemAnimator(new DefaultItemAnimator() {
|
||||||
|
@Override
|
||||||
|
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
swipeRefresh.setEnabled(requireActivity().getIntent().getBooleanExtra(REFRESHABLE, true));
|
||||||
|
|
||||||
|
selectionLimit = requireActivity().getIntent().getIntExtra(TOTAL_CAPACITY, NO_LIMIT);
|
||||||
|
currentSelection = getCurrentSelection();
|
||||||
|
|
||||||
|
updateGroupLimit(getChipCount());
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateGroupLimit(int chipCount) {
|
||||||
|
if (selectionLimit != NO_LIMIT) {
|
||||||
|
groupLimit.setText(String.format(Locale.getDefault(), "%d/%d", currentSelection.size() + chipCount, selectionLimit));
|
||||||
|
groupLimit.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
groupLimit.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull List<SelectedContact> getSelectedContacts() {
|
||||||
|
if (cursorRecyclerViewAdapter == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursorRecyclerViewAdapter.getSelectedContacts();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSelectedContactsCount() {
|
||||||
|
if (cursorRecyclerViewAdapter == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursorRecyclerViewAdapter.getSelectedContactsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<RecipientId> getCurrentSelection() {
|
||||||
|
List<RecipientId> currentSelection = requireActivity().getIntent().getParcelableArrayListExtra(CURRENT_SELECTION);
|
||||||
|
|
||||||
|
return currentSelection == null ? Collections.emptySet()
|
||||||
|
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMulti() {
|
||||||
|
return requireActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeCursor() {
|
||||||
|
glideRequests = GlideApp.with(this);
|
||||||
|
|
||||||
|
cursorRecyclerViewAdapter = new ContactSelectionListAdapter(requireContext(),
|
||||||
|
glideRequests,
|
||||||
|
null,
|
||||||
|
new ListClickListener(),
|
||||||
|
isMulti(),
|
||||||
|
currentSelection);
|
||||||
|
|
||||||
|
RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader();
|
||||||
|
|
||||||
|
if (listCallback != null) {
|
||||||
|
headerAdapter = new FixedViewsAdapter(createNewGroupItem(listCallback));
|
||||||
|
headerAdapter.hide();
|
||||||
|
concatenateAdapter.addAdapter(headerAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
concatenateAdapter.addAdapter(cursorRecyclerViewAdapter);
|
||||||
|
|
||||||
|
if (listCallback != null) {
|
||||||
|
footerAdapter = new FixedViewsAdapter(createInviteActionView(listCallback));
|
||||||
|
footerAdapter.hide();
|
||||||
|
concatenateAdapter.addAdapter(footerAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
recyclerView.setAdapter(concatenateAdapter);
|
||||||
|
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
|
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||||
|
if (scrollCallback != null) {
|
||||||
|
scrollCallback.onBeginScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private View createInviteActionView(@NonNull ListCallback listCallback) {
|
||||||
|
View view = LayoutInflater.from(requireContext())
|
||||||
|
.inflate(R.layout.contact_selection_invite_action_item, (ViewGroup) requireView(), false);
|
||||||
|
view.setOnClickListener(v -> listCallback.onInvite());
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View createNewGroupItem(@NonNull ListCallback listCallback) {
|
||||||
|
View view = LayoutInflater.from(requireContext())
|
||||||
|
.inflate(R.layout.contact_selection_new_group_item, (ViewGroup) requireView(), false);
|
||||||
|
view.setOnClickListener(v -> listCallback.onNewGroup(false));
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeNoContactsPermission() {
|
||||||
|
swipeRefresh.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
showContactsLayout.setVisibility(View.VISIBLE);
|
||||||
|
showContactsProgress.setVisibility(View.INVISIBLE);
|
||||||
|
showContactsDescription.setText(R.string.contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them);
|
||||||
|
showContactsButton.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
showContactsButton.setOnClickListener(v -> {
|
||||||
|
Permissions.with(this)
|
||||||
|
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
|
||||||
|
.ifNecessary()
|
||||||
|
.withPermanentDenialDialog(getString(R.string.ContactSelectionListFragment_signal_requires_the_contacts_permission_in_order_to_display_your_contacts))
|
||||||
|
.onSomeGranted(permissions -> {
|
||||||
|
if (permissions.contains(Manifest.permission.WRITE_CONTACTS)) {
|
||||||
|
handleContactPermissionGranted();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQueryFilter(String filter) {
|
||||||
|
this.cursorFilter = filter;
|
||||||
|
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetQueryFilter() {
|
||||||
|
setQueryFilter(null);
|
||||||
|
swipeRefresh.setRefreshing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasQueryFilter() {
|
||||||
|
return !TextUtils.isEmpty(cursorFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefreshing(boolean refreshing) {
|
||||||
|
swipeRefresh.setRefreshing(refreshing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
cursorRecyclerViewAdapter.clearSelectedContacts();
|
||||||
|
|
||||||
|
if (!isDetached() && !isRemoving() && getActivity() != null && !getActivity().isFinishing()) {
|
||||||
|
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
FragmentActivity activity = requireActivity();
|
||||||
|
return new ContactsCursorLoader(activity,
|
||||||
|
activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL),
|
||||||
|
cursorFilter, activity.getIntent().getBooleanExtra(RECENTS, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(@NonNull Loader<Cursor> loader, @Nullable Cursor data) {
|
||||||
|
swipeRefresh.setVisibility(View.VISIBLE);
|
||||||
|
showContactsLayout.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
cursorRecyclerViewAdapter.changeCursor(data);
|
||||||
|
|
||||||
|
if (footerAdapter != null) {
|
||||||
|
footerAdapter.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerAdapter != null) {
|
||||||
|
if (TextUtils.isEmpty(cursorFilter)) {
|
||||||
|
headerAdapter.show();
|
||||||
|
} else {
|
||||||
|
headerAdapter.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
|
||||||
|
boolean useFastScroller = data != null && data.getCount() > 20;
|
||||||
|
recyclerView.setVerticalScrollBarEnabled(!useFastScroller);
|
||||||
|
if (useFastScroller) {
|
||||||
|
fastScroller.setVisibility(View.VISIBLE);
|
||||||
|
fastScroller.setRecyclerView(recyclerView);
|
||||||
|
} else {
|
||||||
|
fastScroller.setRecyclerView(null);
|
||||||
|
fastScroller.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
|
||||||
|
cursorRecyclerViewAdapter.changeCursor(null);
|
||||||
|
fastScroller.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
private void handleContactPermissionGranted() {
|
||||||
|
final Context context = requireContext();
|
||||||
|
|
||||||
|
new AsyncTask<Void, Void, Boolean>() {
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
swipeRefresh.setVisibility(View.GONE);
|
||||||
|
showContactsLayout.setVisibility(View.VISIBLE);
|
||||||
|
showContactsButton.setVisibility(View.INVISIBLE);
|
||||||
|
showContactsDescription.setText(R.string.ConversationListFragment_loading);
|
||||||
|
showContactsProgress.setVisibility(View.VISIBLE);
|
||||||
|
showContactsProgress.spin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... voids) {
|
||||||
|
try {
|
||||||
|
DirectoryHelper.refreshDirectory(context, false);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
if (result) {
|
||||||
|
showContactsLayout.setVisibility(View.GONE);
|
||||||
|
swipeRefresh.setVisibility(View.VISIBLE);
|
||||||
|
reset();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
|
||||||
|
initializeNoContactsPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(ContactSelectionListItem contact) {
|
||||||
|
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
||||||
|
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
||||||
|
|
||||||
|
if (isMulti() && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||||
|
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMulti() || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) {
|
||||||
|
if (selectionLimitReached()) {
|
||||||
|
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_the_group_is_full, Toast.LENGTH_SHORT).show();
|
||||||
|
groupLimit.animate().scaleX(1.3f).scaleY(1.3f).setInterpolator(new CycleInterpolator(0.5f)).start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contact.isUsernameType()) {
|
||||||
|
AlertDialog loadingDialog = SimpleProgressDialog.show(requireContext());
|
||||||
|
|
||||||
|
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
|
||||||
|
return UsernameUtil.fetchUuidForUsername(requireContext(), contact.getNumber());
|
||||||
|
}, uuid -> {
|
||||||
|
loadingDialog.dismiss();
|
||||||
|
if (uuid.isPresent()) {
|
||||||
|
Recipient recipient = Recipient.externalUsername(requireContext(), uuid.get(), contact.getNumber());
|
||||||
|
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), contact.getNumber());
|
||||||
|
|
||||||
|
if (onContactSelectedListener != null) {
|
||||||
|
if (onContactSelectedListener.onContactSelected(Optional.of(recipient.getId()), null)) {
|
||||||
|
markContactSelected(selected);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markContactSelected(selected);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.ContactSelectionListFragment_username_not_found)
|
||||||
|
.setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, contact.getNumber()))
|
||||||
|
.setPositiveButton(R.string.ContactSelectionListFragment_okay, (dialog, which) -> dialog.dismiss())
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (onContactSelectedListener != null) {
|
||||||
|
if (onContactSelectedListener.onContactSelected(contact.getRecipientId(), contact.getNumber())) {
|
||||||
|
markContactSelected(selectedContact);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markContactSelected(selectedContact);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markContactUnselected(selectedContact);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
|
||||||
|
if (onContactSelectedListener != null) {
|
||||||
|
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean selectionLimitReached() {
|
||||||
|
return getChipCount() >= selectionLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markContactSelected(@NonNull SelectedContact selectedContact) {
|
||||||
|
cursorRecyclerViewAdapter.addSelectedContact(selectedContact);
|
||||||
|
if (isMulti()) {
|
||||||
|
addChipForSelectedContact(selectedContact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markContactUnselected(@NonNull SelectedContact selectedContact) {
|
||||||
|
cursorRecyclerViewAdapter.removeFromSelectedContacts(selectedContact);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
removeChipForContact(selectedContact);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeChipForContact(@NonNull SelectedContact contact) {
|
||||||
|
for (int i = chipGroup.getChildCount() - 1; i >= 0; i--) {
|
||||||
|
View v = chipGroup.getChildAt(i);
|
||||||
|
if (v instanceof ContactChip && contact.matches(((ContactChip) v).getContact())) {
|
||||||
|
chipGroup.removeView(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGroupLimit(getChipCount());
|
||||||
|
|
||||||
|
if (getChipCount() == 0) {
|
||||||
|
setChipGroupVisibility(ConstraintSet.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addChipForSelectedContact(@NonNull SelectedContact selectedContact) {
|
||||||
|
SimpleTask.run(getViewLifecycleOwner().getLifecycle(),
|
||||||
|
() -> Recipient.resolved(selectedContact.getOrCreateRecipientId(requireContext())),
|
||||||
|
resolved -> addChipForRecipient(resolved, selectedContact));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addChipForRecipient(@NonNull Recipient recipient, @NonNull SelectedContact selectedContact) {
|
||||||
|
final ContactChip chip = new ContactChip(requireContext());
|
||||||
|
|
||||||
|
if (getChipCount() == 0) {
|
||||||
|
setChipGroupVisibility(ConstraintSet.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
chip.setText(recipient.getShortDisplayName(requireContext()));
|
||||||
|
chip.setContact(selectedContact);
|
||||||
|
chip.setCloseIconVisible(true);
|
||||||
|
chip.setOnCloseIconClickListener(view -> {
|
||||||
|
markContactUnselected(selectedContact);
|
||||||
|
|
||||||
|
if (onContactSelectedListener != null) {
|
||||||
|
onContactSelectedListener.onContactDeselected(Optional.of(recipient.getId()), recipient.getE164().orNull());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chipGroup.getLayoutTransition().addTransitionListener(new LayoutTransition.TransitionListener() {
|
||||||
|
@Override
|
||||||
|
public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
|
||||||
|
if (view == chip && transitionType == LayoutTransition.APPEARING) {
|
||||||
|
chipGroup.getLayoutTransition().removeTransitionListener(this);
|
||||||
|
registerChipRecipientObserver(chip, recipient.live());
|
||||||
|
chipGroup.post(ContactSelectionListFragment.this::smoothScrollChipsToEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chip.setAvatar(glideRequests, recipient, () -> addChip(chip));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addChip(@NonNull ContactChip chip) {
|
||||||
|
chipGroup.addView(chip);
|
||||||
|
updateGroupLimit(getChipCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getChipCount() {
|
||||||
|
int count = chipGroup.getChildCount() - CHIP_GROUP_EMPTY_CHILD_COUNT;
|
||||||
|
if (count < 0) throw new AssertionError();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerChipRecipientObserver(@NonNull ContactChip chip, @Nullable LiveRecipient recipient) {
|
||||||
|
if (recipient != null) {
|
||||||
|
recipient.observe(getViewLifecycleOwner(), resolved -> {
|
||||||
|
if (chip.isAttachedToWindow()) {
|
||||||
|
chip.setAvatar(glideRequests, resolved, null);
|
||||||
|
chip.setText(resolved.getShortDisplayName(chip.getContext()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setChipGroupVisibility(int visibility) {
|
||||||
|
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
|
||||||
|
|
||||||
|
ConstraintSet constraintSet = new ConstraintSet();
|
||||||
|
constraintSet.clone(constraintLayout);
|
||||||
|
constraintSet.setVisibility(R.id.chipGroupScrollContainer, visibility);
|
||||||
|
constraintSet.applyTo(constraintLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
||||||
|
this.onContactSelectedListener = onContactSelectedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
|
||||||
|
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void smoothScrollChipsToEnd() {
|
||||||
|
int x = chipGroupScrollContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? chipGroup.getWidth() : 0;
|
||||||
|
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnContactSelectedListener {
|
||||||
|
/** @return True if the contact is allowed to be selected, otherwise false. */
|
||||||
|
boolean onContactSelected(Optional<RecipientId> recipientId, String number);
|
||||||
|
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ListCallback {
|
||||||
|
void onInvite();
|
||||||
|
void onNewGroup(boolean forceV1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ScrollCallback {
|
||||||
|
void onBeginScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
|
|||||||
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
|
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
|
||||||
import org.thoughtcrime.securesms.service.ApplicationMigrationService.ImportState;
|
import org.thoughtcrime.securesms.service.ApplicationMigrationService.ImportState;
|
||||||
|
|
||||||
public class DatabaseMigrationActivity extends PassphraseRequiredActionBarActivity {
|
public class DatabaseMigrationActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
private final ImportServiceConnection serviceConnection = new ImportServiceConnection();
|
private final ImportServiceConnection serviceConnection = new ImportServiceConnection();
|
||||||
private final ImportStateHandler importStateHandler = new ImportStateHandler();
|
private final ImportStateHandler importStateHandler = new ImportStateHandler();
|
||||||
@@ -23,7 +23,6 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
|||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
|
||||||
import org.thoughtcrime.securesms.qr.ScanListener;
|
import org.thoughtcrime.securesms.qr.ScanListener;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
@@ -42,7 +41,7 @@ import org.whispersystems.signalservice.internal.push.DeviceLimitExceededExcepti
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
public class DeviceActivity extends PassphraseRequiredActivity
|
||||||
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
|
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ import android.content.res.Configuration;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewAnimationUtils;
|
import android.view.ViewAnimationUtils;
|
||||||
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.qr.ScanListener;
|
|||||||
import org.thoughtcrime.securesms.qr.ScanningThread;
|
import org.thoughtcrime.securesms.qr.ScanningThread;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
public class DeviceAddFragment extends Fragment {
|
public class DeviceAddFragment extends LoggingFragment {
|
||||||
|
|
||||||
private ViewGroup container;
|
private ViewGroup container;
|
||||||
private LinearLayout overlay;
|
private LinearLayout overlay;
|
||||||
@@ -53,7 +53,7 @@ public class DeviceListFragment extends ListFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA);
|
this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActivity.LOCALE_EXTRA);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -164,25 +164,29 @@ public class DeviceListFragment extends ListFragment
|
|||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private void handleDisconnectDevice(final long deviceId) {
|
private void handleDisconnectDevice(final long deviceId) {
|
||||||
new ProgressDialogAsyncTask<Void, Void, Void>(getActivity(),
|
new ProgressDialogAsyncTask<Void, Void, Boolean>(getActivity(),
|
||||||
R.string.DeviceListActivity_unlinking_device_no_ellipsis,
|
R.string.DeviceListActivity_unlinking_device_no_ellipsis,
|
||||||
R.string.DeviceListActivity_unlinking_device)
|
R.string.DeviceListActivity_unlinking_device)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
try {
|
try {
|
||||||
accountManager.removeDevice(deviceId);
|
accountManager.removeDevice(deviceId);
|
||||||
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
|
return false;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Void result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
if (result) {
|
||||||
|
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import android.os.Bundle;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
|
||||||
public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActivity {
|
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = DeviceProvisioningActivity.class.getSimpleName();
|
private static final String TAG = DeviceProvisioningActivity.class.getSimpleName();
|
||||||
@@ -17,9 +17,6 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle, boolean ready) {
|
protected void onCreate(Bundle bundle, boolean ready) {
|
||||||
assert getSupportActionBar() != null;
|
|
||||||
getSupportActionBar().hide();
|
|
||||||
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
|
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
|
||||||
.setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner))
|
.setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner))
|
||||||
@@ -29,7 +26,7 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.DeviceProvisioningActivity_cancel, (dialog12, which) -> {
|
.setNegativeButton(android.R.string.cancel, (dialog12, which) -> {
|
||||||
dialog12.dismiss();
|
dialog12.dismiss();
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import cn.carbswang.android.numberpickerview.library.NumberPickerView;
|
import cn.carbswang.android.numberpickerview.library.NumberPickerView;
|
||||||
|
|
||||||
public class ExpirationDialog extends AlertDialog {
|
public class ExpirationDialog extends AlertDialog {
|
||||||
@@ -36,7 +39,7 @@ public class ExpirationDialog extends AlertDialog {
|
|||||||
builder.setView(view);
|
builder.setView(view);
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
int selected = ((NumberPickerView)view.findViewById(R.id.expiration_number_picker)).getValue();
|
int selected = ((NumberPickerView)view.findViewById(R.id.expiration_number_picker)).getValue();
|
||||||
listener.onClick(context.getResources().getIntArray(R.array.expiration_times)[selected]);
|
listener.onClick(getExpirationTimes(context, currentExpiration)[selected]);
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
builder.show();
|
builder.show();
|
||||||
@@ -47,7 +50,7 @@ public class ExpirationDialog extends AlertDialog {
|
|||||||
final View view = inflater.inflate(R.layout.expiration_dialog, null);
|
final View view = inflater.inflate(R.layout.expiration_dialog, null);
|
||||||
final NumberPickerView numberPickerView = view.findViewById(R.id.expiration_number_picker);
|
final NumberPickerView numberPickerView = view.findViewById(R.id.expiration_number_picker);
|
||||||
final TextView textView = view.findViewById(R.id.expiration_details);
|
final TextView textView = view.findViewById(R.id.expiration_details);
|
||||||
final int[] expirationTimes = context.getResources().getIntArray(R.array.expiration_times);
|
final int[] expirationTimes = getExpirationTimes(context, currentExpiration);
|
||||||
final String[] expirationDisplayValues = new String[expirationTimes.length];
|
final String[] expirationDisplayValues = new String[expirationTimes.length];
|
||||||
|
|
||||||
int selectedIndex = expirationTimes.length - 1;
|
int selectedIndex = expirationTimes.length - 1;
|
||||||
@@ -80,6 +83,19 @@ public class ExpirationDialog extends AlertDialog {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int[] getExpirationTimes(Context context, int currentExpiration) {
|
||||||
|
int[] expirationTimes = context.getResources().getIntArray(R.array.expiration_times);
|
||||||
|
int location = Arrays.binarySearch(expirationTimes, currentExpiration);
|
||||||
|
if (location < 0) {
|
||||||
|
int[] temp = Arrays.copyOf(expirationTimes, expirationTimes.length + 1);
|
||||||
|
temp[temp.length - 1] = currentExpiration;
|
||||||
|
Arrays.sort(temp);
|
||||||
|
expirationTimes = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expirationTimes;
|
||||||
|
}
|
||||||
|
|
||||||
public interface OnClickListener {
|
public interface OnClickListener {
|
||||||
public void onClick(int expirationTime);
|
public void onClick(int expirationTime);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class GroupMembersDialog {
|
||||||
|
|
||||||
|
private final FragmentActivity fragmentActivity;
|
||||||
|
private final Recipient groupRecipient;
|
||||||
|
|
||||||
|
public GroupMembersDialog(@NonNull FragmentActivity activity,
|
||||||
|
@NonNull Recipient groupRecipient)
|
||||||
|
{
|
||||||
|
this.fragmentActivity = activity;
|
||||||
|
this.groupRecipient = groupRecipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void display() {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
|
||||||
|
.setTitle(R.string.ConversationActivity_group_members)
|
||||||
|
.setIconAttribute(R.attr.group_members_dialog_icon)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setView(R.layout.dialog_group_members)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
|
||||||
|
GroupMemberListView memberListView = dialog.findViewById(R.id.list_members);
|
||||||
|
|
||||||
|
LiveGroup liveGroup = new LiveGroup(groupRecipient.requireGroupId());
|
||||||
|
LiveData<List<GroupMemberEntry.FullMember>> fullMembers = liveGroup.getFullMembers();
|
||||||
|
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
fullMembers.observe(fragmentActivity, memberListView::setMembers);
|
||||||
|
|
||||||
|
dialog.setOnDismissListener(d -> fullMembers.removeObservers(fragmentActivity));
|
||||||
|
|
||||||
|
memberListView.setRecipientClickListener(recipient -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
contactClick(recipient);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void contactClick(@NonNull Recipient recipient) {
|
||||||
|
RecipientBottomSheetDialogFragment.create(recipient.getId(), groupRecipient.requireGroupId())
|
||||||
|
.show(fragmentActivity.getSupportFragmentManager(), "BOTTOM");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,8 +22,6 @@ import androidx.appcompat.widget.Toolbar;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
|
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
@@ -42,9 +40,10 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
|||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class InviteActivity extends PassphraseRequiredActionBarActivity implements ContactSelectionListFragment.OnContactSelectedListener {
|
public class InviteActivity extends PassphraseRequiredActivity implements ContactSelectionListFragment.OnContactSelectedListener {
|
||||||
|
|
||||||
private ContactSelectionListFragment contactsFragment;
|
private ContactSelectionListFragment contactsFragment;
|
||||||
private EditText inviteText;
|
private EditText inviteText;
|
||||||
@@ -122,8 +121,9 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
|
public boolean onContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||||
updateSmsButtonText();
|
updateSmsButtonText();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -135,14 +135,15 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
|
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
|
||||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||||
contactsFragment.getSelectedContacts()
|
contactsFragment.getSelectedContacts()
|
||||||
.toArray(new SelectedContact[contactsFragment.getSelectedContacts().size()]));
|
.toArray(new SelectedContact[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSmsButtonText() {
|
private void updateSmsButtonText() {
|
||||||
|
List<SelectedContact> selectedContacts = contactsFragment.getSelectedContacts();
|
||||||
smsSendButton.setText(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_to_friends,
|
smsSendButton.setText(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_to_friends,
|
||||||
contactsFragment.getSelectedContacts().size(),
|
selectedContacts.size(),
|
||||||
contactsFragment.getSelectedContacts().size()));
|
selectedContacts.size()));
|
||||||
smsSendButton.setEnabled(!contactsFragment.getSelectedContacts().isEmpty());
|
smsSendButton.setEnabled(!selectedContacts.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onBackPressed() {
|
@Override public void onBackPressed() {
|
||||||
@@ -175,17 +176,17 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setPrimaryColorsToolbarForSms() {
|
private void setPrimaryColorsToolbarForSms() {
|
||||||
primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.signal_primary));
|
primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.core_ultramarine));
|
||||||
primaryToolbar.getNavigationIcon().setColorFilter(ThemeUtil.getThemedColor(this, R.attr.conversation_subtitle_color), PorterDuff.Mode.SRC_IN);
|
primaryToolbar.getNavigationIcon().setColorFilter(ThemeUtil.getThemedColor(this, R.attr.conversation_subtitle_color), PorterDuff.Mode.SRC_IN);
|
||||||
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
|
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.signal_primary));
|
getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.core_ultramarine));
|
||||||
WindowUtil.clearLightStatusBar(getWindow());
|
WindowUtil.clearLightStatusBar(getWindow());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 27) {
|
if (Build.VERSION.SDK_INT >= 27) {
|
||||||
getWindow().setNavigationBarColor(ContextCompat.getColor(this, R.color.signal_primary));
|
getWindow().setNavigationBarColor(ContextCompat.getColor(this, R.color.core_ultramarine));
|
||||||
WindowUtil.clearLightNavigationBar(getWindow());
|
WindowUtil.clearLightNavigationBar(getWindow());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply logs out lifecycle events.
|
||||||
|
*/
|
||||||
|
public abstract class LoggingFragment extends Fragment {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(LoggingFragment.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
logEvent("onCreate()");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
logEvent("onStart()");
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
logEvent("onStop()");
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
logEvent("onDestroy()");
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logEvent(@NonNull String event) {
|
||||||
|
Log.d(TAG, "[" + Log.tag(getClass()) + "] " + event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import androidx.annotation.NonNull;
|
|||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
|
||||||
public class MainActivity extends PassphraseRequiredActionBarActivity {
|
public class MainActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||||
private final MainNavigator navigator = new MainNavigator(this);
|
private final MainNavigator navigator = new MainNavigator(this);
|
||||||
@@ -3,9 +3,8 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
public class MainFragment extends Fragment {
|
public class MainFragment extends LoggingFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
@@ -12,6 +12,7 @@ import androidx.fragment.app.FragmentManager;
|
|||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||||
import org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment;
|
import org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment;
|
||||||
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
|
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
||||||
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
|
||||||
@@ -55,8 +56,8 @@ public class MainNavigator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, long lastSeen, int startingPosition) {
|
public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, int startingPosition) {
|
||||||
Intent intent = ConversationActivity.buildIntent(activity, recipientId, threadId, distributionType, lastSeen, startingPosition);
|
Intent intent = ConversationActivity.buildIntent(activity, recipientId, threadId, distributionType, startingPosition);
|
||||||
|
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
|
activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
|
||||||
@@ -77,8 +78,7 @@ public class MainNavigator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void goToGroupCreation() {
|
public void goToGroupCreation() {
|
||||||
Intent intent = new Intent(activity, GroupCreateActivity.class);
|
activity.startActivity(CreateGroupActivity.newIntent(activity));
|
||||||
activity.startActivity(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void goToInvite() {
|
public void goToInvite() {
|
||||||
@@ -20,10 +20,12 @@ import android.Manifest;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.ContentObserver;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -64,6 +66,7 @@ import org.thoughtcrime.securesms.mms.GlideApp;
|
|||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.sharing.ShareActivity;
|
||||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
@@ -72,11 +75,12 @@ import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for displaying media attachments in-app
|
* Activity for displaying media attachments in-app
|
||||||
*/
|
*/
|
||||||
public final class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||||
implements LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>,
|
implements LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>,
|
||||||
MediaRailAdapter.RailItemListener,
|
MediaRailAdapter.RailItemListener,
|
||||||
MediaPreviewFragment.Events
|
MediaPreviewFragment.Events
|
||||||
@@ -116,6 +120,23 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
private boolean showThread;
|
private boolean showThread;
|
||||||
private MediaDatabase.Sorting sorting;
|
private MediaDatabase.Sorting sorting;
|
||||||
|
|
||||||
|
private @Nullable Cursor cursor = null;
|
||||||
|
|
||||||
|
public static @NonNull Intent intentFromMediaRecord(@NonNull Context context,
|
||||||
|
@NonNull MediaRecord mediaRecord,
|
||||||
|
boolean leftIsRecent)
|
||||||
|
{
|
||||||
|
DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment());
|
||||||
|
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
||||||
|
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, mediaRecord.getThreadId());
|
||||||
|
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate());
|
||||||
|
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, attachment.getSize());
|
||||||
|
intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, attachment.getCaption());
|
||||||
|
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, leftIsRecent);
|
||||||
|
intent.setDataAndType(attachment.getDataUri(), mediaRecord.getContentType());
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle, boolean ready) {
|
protected void onCreate(Bundle bundle, boolean ready) {
|
||||||
@@ -166,7 +187,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
private @NonNull String getTitleText(@NonNull MediaItem mediaItem) {
|
private @NonNull String getTitleText(@NonNull MediaItem mediaItem) {
|
||||||
String from;
|
String from;
|
||||||
if (mediaItem.outgoing) from = getString(R.string.MediaPreviewActivity_you);
|
if (mediaItem.outgoing) from = getString(R.string.MediaPreviewActivity_you);
|
||||||
else if (mediaItem.recipient != null) from = mediaItem.recipient.toShortString(this);
|
else if (mediaItem.recipient != null) from = mediaItem.recipient.getDisplayName(this);
|
||||||
else from = "";
|
else from = "";
|
||||||
|
|
||||||
if (showThread) {
|
if (showThread) {
|
||||||
@@ -178,7 +199,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
if (threadRecipient.isLocalNumber()) {
|
if (threadRecipient.isLocalNumber()) {
|
||||||
from = getString(R.string.note_to_self);
|
from = getString(R.string.note_to_self);
|
||||||
} else {
|
} else {
|
||||||
to = threadRecipient.toShortString(this);
|
to = threadRecipient.getDisplayName(this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
to = getString(R.string.MediaPreviewActivity_you);
|
to = getString(R.string.MediaPreviewActivity_you);
|
||||||
@@ -213,6 +234,15 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
restartItem = cleanupMedia();
|
restartItem = cleanupMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
cursor = null;
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
@@ -329,6 +359,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
|
|
||||||
mediaPager.removeAllViews();
|
mediaPager.removeAllViews();
|
||||||
mediaPager.setAdapter(null);
|
mediaPager.setAdapter(null);
|
||||||
|
viewModel.setCursor(this, null, leftIsRecent);
|
||||||
|
|
||||||
return restartItem;
|
return restartItem;
|
||||||
}
|
}
|
||||||
@@ -460,19 +491,46 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(@NonNull Loader<Pair<Cursor, Integer>> loader, @Nullable Pair<Cursor, Integer> data) {
|
public void onLoadFinished(@NonNull Loader<Pair<Cursor, Integer>> loader, @Nullable Pair<Cursor, Integer> data) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
@SuppressWarnings("ConstantConditions")
|
if (data.first == cursor) {
|
||||||
CursorPagerAdapter adapter = new CursorPagerAdapter(getSupportFragmentManager(),this, data.first, data.second, leftIsRecent);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
cursor = Objects.requireNonNull(data.first);
|
||||||
|
|
||||||
|
int mediaPosition = Objects.requireNonNull(data.second);
|
||||||
|
|
||||||
|
CursorPagerAdapter adapter = new CursorPagerAdapter(getSupportFragmentManager(),this, cursor, mediaPosition, leftIsRecent);
|
||||||
mediaPager.setAdapter(adapter);
|
mediaPager.setAdapter(adapter);
|
||||||
adapter.setActive(true);
|
adapter.setActive(true);
|
||||||
|
|
||||||
viewModel.setCursor(this, data.first, leftIsRecent);
|
viewModel.setCursor(this, cursor, leftIsRecent);
|
||||||
|
|
||||||
int item = restartItem >= 0 ? restartItem : data.second;
|
int item = restartItem >= 0 ? restartItem : mediaPosition;
|
||||||
mediaPager.setCurrentItem(item);
|
mediaPager.setCurrentItem(item);
|
||||||
|
|
||||||
if (item == 0) {
|
if (item == 0) {
|
||||||
viewPagerListener.onPageSelected(0);
|
viewPagerListener.onPageSelected(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cursor.registerContentObserver(new ContentObserver(new Handler(getMainLooper())) {
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
onMediaChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mediaNotAvailable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMediaChange() {
|
||||||
|
MediaItemAdapter adapter = (MediaItemAdapter) mediaPager.getAdapter();
|
||||||
|
|
||||||
|
if (adapter != null) {
|
||||||
|
adapter.checkMedia(mediaPager.getCurrentItem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,6 +545,12 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mediaNotAvailable() {
|
||||||
|
Toast.makeText(this, R.string.MediaPreviewActivity_media_no_longer_available, Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
private void toggleUiVisibility() {
|
private void toggleUiVisibility() {
|
||||||
int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
|
int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
|
||||||
if ((systemUiVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
|
if ((systemUiVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
|
||||||
@@ -606,6 +670,11 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
public boolean hasFragmentFor(int position) {
|
public boolean hasFragmentFor(int position) {
|
||||||
return mediaPreviewFragment != null;
|
return mediaPreviewFragment != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkMedia(int currentItem) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void anchorMarginsToBottomInsets(@NonNull View viewToAnchor) {
|
private static void anchorMarginsToBottomInsets(@NonNull View viewToAnchor) {
|
||||||
@@ -697,7 +766,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
cursor.moveToPosition(cursorPosition);
|
cursor.moveToPosition(cursorPosition);
|
||||||
|
|
||||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(context, cursor);
|
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(context, cursor);
|
||||||
DatabaseAttachment attachment = mediaRecord.getAttachment();
|
DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment());
|
||||||
MediaPreviewFragment fragment = MediaPreviewFragment.newInstance(attachment, autoPlay);
|
MediaPreviewFragment fragment = MediaPreviewFragment.newInstance(attachment, autoPlay);
|
||||||
|
|
||||||
mediaFragments.put(position, fragment);
|
mediaFragments.put(position, fragment);
|
||||||
@@ -719,16 +788,15 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
public MediaItem getMediaItemFor(int position) {
|
public MediaItem getMediaItemFor(int position) {
|
||||||
cursor.moveToPosition(getCursorPosition(position));
|
cursor.moveToPosition(getCursorPosition(position));
|
||||||
|
|
||||||
MediaRecord mediaRecord = MediaRecord.from(context, cursor);
|
MediaRecord mediaRecord = MediaRecord.from(context, cursor);
|
||||||
RecipientId recipientId = mediaRecord.getRecipientId();
|
DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment());
|
||||||
RecipientId threadRecipientId = mediaRecord.getThreadRecipientId();
|
RecipientId recipientId = mediaRecord.getRecipientId();
|
||||||
|
RecipientId threadRecipientId = mediaRecord.getThreadRecipientId();
|
||||||
if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError();
|
|
||||||
|
|
||||||
return new MediaItem(Recipient.live(recipientId).get(),
|
return new MediaItem(Recipient.live(recipientId).get(),
|
||||||
Recipient.live(threadRecipientId).get(),
|
Recipient.live(threadRecipientId).get(),
|
||||||
mediaRecord.getAttachment(),
|
attachment,
|
||||||
mediaRecord.getAttachment().getDataUri(),
|
Objects.requireNonNull(attachment.getDataUri()),
|
||||||
mediaRecord.getContentType(),
|
mediaRecord.getContentType(),
|
||||||
mediaRecord.getDate(),
|
mediaRecord.getDate(),
|
||||||
mediaRecord.isOutgoing());
|
mediaRecord.isOutgoing());
|
||||||
@@ -752,6 +820,14 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
return mediaFragments.containsKey(position);
|
return mediaFragments.containsKey(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkMedia(int position) {
|
||||||
|
MediaPreviewFragment fragment = mediaFragments.get(position);
|
||||||
|
if (fragment != null) {
|
||||||
|
fragment.checkMediaStillAvailable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int getCursorPosition(int position) {
|
private int getCursorPosition(int position) {
|
||||||
if (leftIsRecent) return position;
|
if (leftIsRecent) return position;
|
||||||
else return cursor.getCount() - 1 - position;
|
else return cursor.getCount() - 1 - position;
|
||||||
@@ -790,5 +866,6 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
|
|||||||
void pause(int position);
|
void pause(int position);
|
||||||
@Nullable View getPlaybackControls(int position);
|
@Nullable View getPlaybackControls(int position);
|
||||||
boolean hasFragmentFor(int position);
|
boolean hasFragmentFor(int position);
|
||||||
|
void checkMedia(int currentItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -23,6 +25,10 @@ public class MuteDialog extends AlertDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void show(final Context context, final @NonNull MuteSelectionListener listener) {
|
public static void show(final Context context, final @NonNull MuteSelectionListener listener) {
|
||||||
|
show(context, listener, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void show(final Context context, final @NonNull MuteSelectionListener listener, @Nullable Runnable cancelListener) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
builder.setTitle(R.string.MuteDialog_mute_notifications);
|
builder.setTitle(R.string.MuteDialog_mute_notifications);
|
||||||
builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
|
builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
|
||||||
@@ -43,6 +49,13 @@ public class MuteDialog extends AlertDialog {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (cancelListener != null) {
|
||||||
|
builder.setOnCancelListener(dialog -> {
|
||||||
|
cancelListener.run();
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
builder.show();
|
builder.show();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
|
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity container for starting a new conversation.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class NewConversationActivity extends ContactSelectionActivity
|
||||||
|
implements ContactSelectionListFragment.ListCallback
|
||||||
|
{
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static final String TAG = NewConversationActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
|
super.onCreate(bundle, ready);
|
||||||
|
assert getSupportActionBar() != null;
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||||
|
if (recipientId.isPresent()) {
|
||||||
|
launch(Recipient.resolved(recipientId.get()));
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.");
|
||||||
|
if (FeatureFlags.cds() && NetworkConstraint.isMet(this)) {
|
||||||
|
Log.i(TAG, "[onContactSelected] CDS enabled. Doing contact refresh.");
|
||||||
|
|
||||||
|
AlertDialog progress = SimpleProgressDialog.show(this);
|
||||||
|
|
||||||
|
SimpleTask.run(getLifecycle(), () -> {
|
||||||
|
Recipient resolved = Recipient.external(this, number);
|
||||||
|
|
||||||
|
if (!resolved.isRegistered()) {
|
||||||
|
Log.i(TAG, "[onContactSelected] Not registered. Doing a directory refresh.");
|
||||||
|
try {
|
||||||
|
DirectoryHelper.refreshDirectoryFor(this, resolved, false);
|
||||||
|
resolved = Recipient.resolved(resolved.getId());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}, resolved -> {
|
||||||
|
progress.dismiss();
|
||||||
|
launch(resolved);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
launch(Recipient.external(this, number));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void launch(Recipient recipient) {
|
||||||
|
Intent intent = new Intent(this, ConversationActivity.class);
|
||||||
|
intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId());
|
||||||
|
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA));
|
||||||
|
intent.setDataAndType(getIntent().getData(), getIntent().getType());
|
||||||
|
|
||||||
|
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
|
||||||
|
|
||||||
|
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread);
|
||||||
|
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
super.onOptionsItemSelected(item);
|
||||||
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home: super.onBackPressed(); return true;
|
||||||
|
case R.id.menu_refresh: handleManualRefresh(); return true;
|
||||||
|
case R.id.menu_new_group: handleCreateGroup(); return true;
|
||||||
|
case R.id.menu_invite: handleInvite(); return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleManualRefresh() {
|
||||||
|
contactsFragment.setRefreshing(true);
|
||||||
|
onRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCreateGroup() {
|
||||||
|
startActivity(CreateGroupActivity.newIntent(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleInvite() {
|
||||||
|
startActivity(new Intent(this, InviteActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
|
menu.clear();
|
||||||
|
getMenuInflater().inflate(R.menu.new_conversation_activity, menu);
|
||||||
|
|
||||||
|
super.onPrepareOptionsMenu(menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInvite() {
|
||||||
|
handleInvite();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewGroup(boolean forceV1) {
|
||||||
|
handleCreateGroup();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
|||||||
*
|
*
|
||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
public abstract class PassphraseActivity extends BaseActionBarActivity {
|
public abstract class PassphraseActivity extends BaseActivity {
|
||||||
|
|
||||||
private static final String TAG = PassphraseActivity.class.getSimpleName();
|
private static final String TAG = PassphraseActivity.class.getSimpleName();
|
||||||
|
|
||||||
@@ -41,6 +41,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||||||
|
|
||||||
public class PassphraseChangeActivity extends PassphraseActivity {
|
public class PassphraseChangeActivity extends PassphraseActivity {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(PassphraseChangeActivity.class);
|
||||||
|
|
||||||
private DynamicTheme dynamicTheme = new DynamicTheme();
|
private DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
@@ -145,7 +147,7 @@ public class PassphraseChangeActivity extends PassphraseActivity {
|
|||||||
return masterSecret;
|
return masterSecret;
|
||||||
|
|
||||||
} catch (InvalidPassphraseException e) {
|
} catch (InvalidPassphraseException e) {
|
||||||
Log.w(PassphraseChangeActivity.class.getSimpleName(), e);
|
Log.w(TAG, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,7 @@ import org.thoughtcrime.securesms.components.AnimatingToggle;
|
|||||||
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||||
|
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity;
|
||||||
import org.thoughtcrime.securesms.util.DynamicIntroTheme;
|
import org.thoughtcrime.securesms.util.DynamicIntroTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@@ -164,7 +165,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleLogSubmit() {
|
private void handleLogSubmit() {
|
||||||
Intent intent = new Intent(this, LogSubmitActivity.class);
|
Intent intent = new Intent(this, SubmitDebugLogActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +238,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
EditorInfo.IME_ACTION_DONE);
|
EditorInfo.IME_ACTION_DONE);
|
||||||
|
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
|
||||||
|
|
||||||
lockScreenButton.setOnClickListener(v -> resumeScreenLock());
|
lockScreenButton.setOnClickListener(v -> resumeScreenLock());
|
||||||
}
|
}
|
||||||
@@ -357,7 +358,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
handleAuthenticated();
|
handleAuthenticated();
|
||||||
|
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
@@ -380,7 +381,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animation animation) {
|
public void onAnimationEnd(Animation animation) {
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
|
||||||
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
|
import org.thoughtcrime.securesms.pin.PinRestoreActivity;
|
||||||
|
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||||
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||||
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public abstract class PassphraseRequiredActivity extends BaseActivity implements MasterSecretListener {
|
||||||
|
private static final String TAG = PassphraseRequiredActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String LOCALE_EXTRA = "locale_extra";
|
||||||
|
public static final String NEXT_INTENT_EXTRA = "next_intent";
|
||||||
|
|
||||||
|
private static final int STATE_NORMAL = 0;
|
||||||
|
private static final int STATE_CREATE_PASSPHRASE = 1;
|
||||||
|
private static final int STATE_PROMPT_PASSPHRASE = 2;
|
||||||
|
private static final int STATE_UI_BLOCKING_UPGRADE = 3;
|
||||||
|
private static final int STATE_WELCOME_PUSH_SCREEN = 4;
|
||||||
|
private static final int STATE_ENTER_SIGNAL_PIN = 5;
|
||||||
|
private static final int STATE_CREATE_PROFILE_NAME = 6;
|
||||||
|
private static final int STATE_CREATE_SIGNAL_PIN = 7;
|
||||||
|
|
||||||
|
private SignalServiceNetworkAccess networkAccess;
|
||||||
|
private BroadcastReceiver clearKeyReceiver;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final void onCreate(Bundle savedInstanceState) {
|
||||||
|
this.networkAccess = new SignalServiceNetworkAccess(this);
|
||||||
|
onPreCreate();
|
||||||
|
|
||||||
|
final boolean locked = KeyCachingService.isLocked(this);
|
||||||
|
routeApplicationState(locked);
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (!isFinishing()) {
|
||||||
|
initializeClearKeyReceiver();
|
||||||
|
onCreate(savedInstanceState, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreCreate() {}
|
||||||
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
if (networkAccess.isCensored(this)) {
|
||||||
|
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
removeClearKeyReceiver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMasterSecretCleared() {
|
||||||
|
Log.d(TAG, "onMasterSecretCleared()");
|
||||||
|
if (ApplicationContext.getInstance(this).isAppVisible()) routeApplicationState(true);
|
||||||
|
else finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||||
|
@NonNull T fragment)
|
||||||
|
{
|
||||||
|
return initFragment(target, fragment, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||||
|
@NonNull T fragment,
|
||||||
|
@Nullable Locale locale)
|
||||||
|
{
|
||||||
|
return initFragment(target, fragment, locale, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||||
|
@NonNull T fragment,
|
||||||
|
@Nullable Locale locale,
|
||||||
|
@Nullable Bundle extras)
|
||||||
|
{
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putSerializable(LOCALE_EXTRA, locale);
|
||||||
|
|
||||||
|
if (extras != null) {
|
||||||
|
args.putAll(extras);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment.setArguments(args);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(target, fragment)
|
||||||
|
.commitAllowingStateLoss();
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void routeApplicationState(boolean locked) {
|
||||||
|
Intent intent = getIntentForState(getApplicationState(locked));
|
||||||
|
if (intent != null) {
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getIntentForState(int state) {
|
||||||
|
Log.d(TAG, "routeApplicationState(), state: " + state);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
|
||||||
|
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
|
||||||
|
case STATE_UI_BLOCKING_UPGRADE: return getUiBlockingUpgradeIntent();
|
||||||
|
case STATE_WELCOME_PUSH_SCREEN: return getPushRegistrationIntent();
|
||||||
|
case STATE_ENTER_SIGNAL_PIN: return getEnterSignalPinIntent();
|
||||||
|
case STATE_CREATE_SIGNAL_PIN: return getCreateSignalPinIntent();
|
||||||
|
case STATE_CREATE_PROFILE_NAME: return getCreateProfileNameIntent();
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getApplicationState(boolean locked) {
|
||||||
|
if (!MasterSecretUtil.isPassphraseInitialized(this)) {
|
||||||
|
return STATE_CREATE_PASSPHRASE;
|
||||||
|
} else if (locked) {
|
||||||
|
return STATE_PROMPT_PASSPHRASE;
|
||||||
|
} else if (ApplicationMigrations.isUpdate(this) && ApplicationMigrations.isUiBlockingMigrationRunning()) {
|
||||||
|
return STATE_UI_BLOCKING_UPGRADE;
|
||||||
|
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
|
||||||
|
return STATE_WELCOME_PUSH_SCREEN;
|
||||||
|
} else if (SignalStore.storageServiceValues().needsAccountRestore()) {
|
||||||
|
return STATE_ENTER_SIGNAL_PIN;
|
||||||
|
} else if (userMustSetProfileName()) {
|
||||||
|
return STATE_CREATE_PROFILE_NAME;
|
||||||
|
} else if (userMustCreateSignalPin()) {
|
||||||
|
return STATE_CREATE_SIGNAL_PIN;
|
||||||
|
} else {
|
||||||
|
return STATE_NORMAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean userMustCreateSignalPin() {
|
||||||
|
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().lastPinCreateFailed() && !SignalStore.kbsValues().hasOptedOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean userMustSetProfileName() {
|
||||||
|
return !SignalStore.registrationValues().isRegistrationComplete() && Recipient.self().getProfileName().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getCreatePassphraseIntent() {
|
||||||
|
return getRoutedIntent(PassphraseCreateActivity.class, getIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getPromptPassphraseIntent() {
|
||||||
|
return getRoutedIntent(PassphrasePromptActivity.class, getIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getUiBlockingUpgradeIntent() {
|
||||||
|
return getRoutedIntent(ApplicationMigrationActivity.class,
|
||||||
|
TextSecurePreferences.hasPromptedPushRegistration(this)
|
||||||
|
? getConversationListIntent()
|
||||||
|
: getPushRegistrationIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getPushRegistrationIntent() {
|
||||||
|
return RegistrationNavigationActivity.newIntentForNewRegistration(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getEnterSignalPinIntent() {
|
||||||
|
return getRoutedIntent(PinRestoreActivity.class, getIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getCreateSignalPinIntent() {
|
||||||
|
|
||||||
|
final Intent intent;
|
||||||
|
if (userMustSetProfileName()) {
|
||||||
|
intent = getCreateProfileNameIntent();
|
||||||
|
} else {
|
||||||
|
intent = getIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getRoutedIntent(CreateKbsPinActivity.class, intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getCreateProfileNameIntent() {
|
||||||
|
return getRoutedIntent(EditProfileActivity.class, getIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
|
||||||
|
final Intent intent = new Intent(this, destination);
|
||||||
|
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getConversationListIntent() {
|
||||||
|
// TODO [greyson] Navigation
|
||||||
|
return new Intent(this, MainActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeClearKeyReceiver() {
|
||||||
|
this.clearKeyReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Log.i(TAG, "onReceive() for clear key event");
|
||||||
|
onMasterSecretCleared();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IntentFilter filter = new IntentFilter(KeyCachingService.CLEAR_KEY_EVENT);
|
||||||
|
registerReceiver(clearKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeClearKeyReceiver(Context context) {
|
||||||
|
if (clearKeyReceiver != null) {
|
||||||
|
context.unregisterReceiver(clearKeyReceiver);
|
||||||
|
clearKeyReceiver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts an extra in {@code intent} so that {@code nextIntent} will be shown after it.
|
||||||
|
*/
|
||||||
|
public static @NonNull Intent chainIntent(@NonNull Intent intent, @NonNull Intent nextIntent) {
|
||||||
|
intent.putExtra(NEXT_INTENT_EXTRA, nextIntent);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import android.widget.Button;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.preferences.MmsPreferencesActivity;
|
import org.thoughtcrime.securesms.preferences.MmsPreferencesActivity;
|
||||||
|
|
||||||
public class PromptMmsActivity extends PassphraseRequiredActionBarActivity {
|
public class PromptMmsActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle, boolean ready) {
|
protected void onCreate(Bundle bundle, boolean ready) {
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity container for selecting a list of contacts.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class PushContactSelectionActivity extends ContactSelectionActivity {
|
||||||
|
|
||||||
|
public static final String KEY_SELECTED_RECIPIENTS = "recipients";
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final static String TAG = PushContactSelectionActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle icicle, boolean ready) {
|
||||||
|
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
|
||||||
|
super.onCreate(icicle, ready);
|
||||||
|
|
||||||
|
initializeToolbar();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initializeToolbar() {
|
||||||
|
getToolbar().setNavigationIcon(R.drawable.ic_check_24);
|
||||||
|
getToolbar().setNavigationOnClickListener(v -> {
|
||||||
|
onFinishedSelection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void onFinishedSelection() {
|
||||||
|
Intent resultIntent = getIntent();
|
||||||
|
List<SelectedContact> selectedContacts = contactsFragment.getSelectedContacts();
|
||||||
|
List<RecipientId> recipients = Stream.of(selectedContacts).map(sc -> sc.getOrCreateRecipientId(this)).toList();
|
||||||
|
|
||||||
|
resultIntent.putParcelableArrayListExtra(KEY_SELECTED_RECIPIENTS, new ArrayList<>(recipients));
|
||||||
|
|
||||||
|
setResult(RESULT_OK, resultIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -109,7 +109,7 @@ public class TransportOptions {
|
|||||||
public static @NonNull TransportOption getPushTransportOption(@NonNull Context context) {
|
public static @NonNull TransportOption getPushTransportOption(@NonNull Context context) {
|
||||||
return new TransportOption(Type.TEXTSECURE,
|
return new TransportOption(Type.TEXTSECURE,
|
||||||
R.drawable.ic_send_lock_24,
|
R.drawable.ic_send_lock_24,
|
||||||
context.getResources().getColor(R.color.textsecure_primary),
|
context.getResources().getColor(R.color.core_ultramarine),
|
||||||
context.getString(R.string.ConversationActivity_transport_signal),
|
context.getString(R.string.ConversationActivity_transport_signal),
|
||||||
context.getString(R.string.conversation_activity__type_message_push),
|
context.getString(R.string.conversation_activity__type_message_push),
|
||||||
new PushCharacterCalculator());
|
new PushCharacterCalculator());
|
||||||
@@ -34,17 +34,9 @@ import android.os.AsyncTask;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
import androidx.annotation.DrawableRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
import androidx.appcompat.widget.SwitchCompat;
|
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -62,13 +54,22 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.widget.SwitchCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView;
|
import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.qr.QrCode;
|
import org.thoughtcrime.securesms.qr.QrCode;
|
||||||
import org.thoughtcrime.securesms.qr.ScanListener;
|
import org.thoughtcrime.securesms.qr.ScanListener;
|
||||||
@@ -76,8 +77,8 @@ import org.thoughtcrime.securesms.qr.ScanningThread;
|
|||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
import org.thoughtcrime.securesms.util.DynamicDarkActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicDarkActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
@@ -103,24 +104,55 @@ import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
|||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity implements ScanListener, View.OnClickListener {
|
public class VerifyIdentityActivity extends PassphraseRequiredActivity implements ScanListener, View.OnClickListener {
|
||||||
|
|
||||||
private static final String TAG = VerifyIdentityActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(VerifyIdentityActivity.class);
|
||||||
|
|
||||||
public static final String RECIPIENT_EXTRA = "recipient_id";
|
private static final String RECIPIENT_EXTRA = "recipient_id";
|
||||||
public static final String IDENTITY_EXTRA = "recipient_identity";
|
private static final String IDENTITY_EXTRA = "recipient_identity";
|
||||||
public static final String VERIFIED_EXTRA = "verified_state";
|
private static final String VERIFIED_EXTRA = "verified_state";
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicDarkActionBarTheme();
|
private final DynamicTheme dynamicTheme = new DynamicDarkActionBarTheme();
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private VerifyDisplayFragment displayFragment = new VerifyDisplayFragment();
|
private final VerifyDisplayFragment displayFragment = new VerifyDisplayFragment();
|
||||||
private VerifyScanFragment scanFragment = new VerifyScanFragment();
|
private final VerifyScanFragment scanFragment = new VerifyScanFragment();
|
||||||
|
|
||||||
|
public static Intent newIntent(@NonNull Context context,
|
||||||
|
@NonNull IdentityDatabase.IdentityRecord identityRecord)
|
||||||
|
{
|
||||||
|
return newIntent(context,
|
||||||
|
identityRecord.getRecipientId(),
|
||||||
|
identityRecord.getIdentityKey(),
|
||||||
|
identityRecord.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent newIntent(@NonNull Context context,
|
||||||
|
@NonNull IdentityDatabase.IdentityRecord identityRecord,
|
||||||
|
boolean verified)
|
||||||
|
{
|
||||||
|
return newIntent(context,
|
||||||
|
identityRecord.getRecipientId(),
|
||||||
|
identityRecord.getIdentityKey(),
|
||||||
|
verified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent newIntent(@NonNull Context context,
|
||||||
|
@NonNull RecipientId recipientId,
|
||||||
|
@NonNull IdentityKey identityKey,
|
||||||
|
boolean verified)
|
||||||
|
{
|
||||||
|
Intent intent = new Intent(context, VerifyIdentityActivity.class);
|
||||||
|
|
||||||
|
intent.putExtra(RECIPIENT_EXTRA, recipientId);
|
||||||
|
intent.putExtra(IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey));
|
||||||
|
intent.putExtra(VERIFIED_EXTRA, verified);
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPreCreate() {
|
public void onPreCreate() {
|
||||||
dynamicTheme.onCreate(this);
|
dynamicTheme.onCreate(this);
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -143,7 +175,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
|||||||
scanFragment.setScanListener(this);
|
scanFragment.setScanListener(this);
|
||||||
displayFragment.setClickListener(this);
|
displayFragment.setClickListener(this);
|
||||||
|
|
||||||
initFragment(android.R.id.content, displayFragment, dynamicLanguage.getCurrentLocale(), extras);
|
initFragment(android.R.id.content, displayFragment, Locale.getDefault(), extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -275,7 +307,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
|||||||
byte[] localId;
|
byte[] localId;
|
||||||
byte[] remoteId;
|
byte[] remoteId;
|
||||||
|
|
||||||
if (FeatureFlags.UUIDS && recipient.resolve().getUuid().isPresent()) {
|
if (FeatureFlags.cds() && recipient.resolve().getUuid().isPresent()) {
|
||||||
Log.i(TAG, "Using UUID (version 2).");
|
Log.i(TAG, "Using UUID (version 2).");
|
||||||
version = 2;
|
version = 2;
|
||||||
localId = UuidUtil.toByteArray(TextSecurePreferences.getLocalUuid(requireContext()));
|
localId = UuidUtil.toByteArray(TextSecurePreferences.getLocalUuid(requireContext()));
|
||||||
@@ -454,7 +486,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setRecipientText(Recipient recipient) {
|
private void setRecipientText(Recipient recipient) {
|
||||||
description.setText(Html.fromHtml(String.format(getActivity().getString(R.string.verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s), recipient.toShortString(getContext()))));
|
description.setText(Html.fromHtml(String.format(getActivity().getString(R.string.verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s), recipient.getDisplayName(getContext()))));
|
||||||
description.setMovementMethod(LinkMovementMethod.getInstance());
|
description.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,6 +636,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
|||||||
remoteIdentity,
|
remoteIdentity,
|
||||||
isChecked ? VerifiedStatus.VERIFIED :
|
isChecked ? VerifiedStatus.VERIFIED :
|
||||||
VerifiedStatus.DEFAULT));
|
VerifiedStatus.DEFAULT));
|
||||||
|
StorageSyncHelper.scheduleSyncForDataChange();
|
||||||
|
|
||||||
IdentityUtil.markIdentityVerified(getActivity(), recipient.get(), isChecked, false);
|
IdentityUtil.markIdentityVerified(getActivity(), recipient.get(), isChecked, false);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,626 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.PictureInPictureParams;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.util.Rational;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||||
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
|
||||||
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||||
|
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||||
|
import org.thoughtcrime.securesms.util.EllapsedTimeFormatter;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.VerifySpan;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||||
|
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||||
|
|
||||||
|
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||||
|
|
||||||
|
public class WebRtcCallActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
|
||||||
|
private static final String TAG = WebRtcCallActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int STANDARD_DELAY_FINISH = 1000;
|
||||||
|
public static final int BUSY_SIGNAL_DELAY_FINISH = 5500;
|
||||||
|
|
||||||
|
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
|
||||||
|
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
|
||||||
|
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
|
||||||
|
|
||||||
|
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
||||||
|
|
||||||
|
private WebRtcCallView callScreen;
|
||||||
|
private TooltipPopup videoTooltip;
|
||||||
|
private WebRtcCallViewModel viewModel;
|
||||||
|
private boolean enableVideoIfAvailable;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.i(TAG, "onCreate()");
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
setContentView(R.layout.webrtc_call_activity);
|
||||||
|
getSupportActionBar().hide();
|
||||||
|
|
||||||
|
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
|
||||||
|
|
||||||
|
initializeResources();
|
||||||
|
initializeViewModel();
|
||||||
|
|
||||||
|
processIntent(getIntent());
|
||||||
|
|
||||||
|
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
|
||||||
|
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
Log.i(TAG, "onResume()");
|
||||||
|
super.onResume();
|
||||||
|
initializeScreenshotSecurity();
|
||||||
|
|
||||||
|
if (!EventBus.getDefault().isRegistered(this)) {
|
||||||
|
EventBus.getDefault().register(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(Intent intent){
|
||||||
|
Log.i(TAG, "onNewIntent");
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
processIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
Log.i(TAG, "onPause");
|
||||||
|
super.onPause();
|
||||||
|
|
||||||
|
if (!isInPipMode()) {
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
Log.i(TAG, "onStop");
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfiguration) {
|
||||||
|
super.onConfigurationChanged(newConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onUserLeaveHint() {
|
||||||
|
enterPipModeIfPossible();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (!enterPipModeIfPossible()) {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
|
||||||
|
viewModel.setIsInPipMode(isInPictureInPictureMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean enterPipModeIfPossible() {
|
||||||
|
if (isSystemPipEnabledAndAvailable()) {
|
||||||
|
PictureInPictureParams params = new PictureInPictureParams.Builder()
|
||||||
|
.setAspectRatio(new Rational(9, 16))
|
||||||
|
.build();
|
||||||
|
enterPictureInPictureMode(params);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInPipMode() {
|
||||||
|
return isSystemPipEnabledAndAvailable() && isInPictureInPictureMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processIntent(@NonNull Intent intent) {
|
||||||
|
if (ANSWER_ACTION.equals(intent.getAction())) {
|
||||||
|
viewModel.setRecipient(EventBus.getDefault().getStickyEvent(WebRtcViewModel.class).getRecipient());
|
||||||
|
handleAnswerWithAudio();
|
||||||
|
} else if (DENY_ACTION.equals(intent.getAction())) {
|
||||||
|
handleDenyCall();
|
||||||
|
} else if (END_CALL_ACTION.equals(intent.getAction())) {
|
||||||
|
handleEndCall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeScreenshotSecurity() {
|
||||||
|
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
|
} else {
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeResources() {
|
||||||
|
callScreen = ViewUtil.findById(this, R.id.callScreen);
|
||||||
|
callScreen.setControlsListener(new ControlsListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViewModel() {
|
||||||
|
viewModel = ViewModelProviders.of(this).get(WebRtcCallViewModel.class);
|
||||||
|
viewModel.setIsInPipMode(isInPipMode());
|
||||||
|
viewModel.getRemoteVideoEnabled().observe(this,callScreen::setRemoteVideoEnabled);
|
||||||
|
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
|
||||||
|
viewModel.getCameraDirection().observe(this, callScreen::setCameraDirection);
|
||||||
|
viewModel.getLocalRenderState().observe(this, callScreen::setLocalRenderState);
|
||||||
|
viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls);
|
||||||
|
viewModel.getEvents().observe(this, this::handleViewModelEvent);
|
||||||
|
viewModel.getCallTime().observe(this, this::handleCallTime);
|
||||||
|
viewModel.displaySquareCallCard().observe(this, callScreen::showCallCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
|
||||||
|
if (isInPipMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case SHOW_VIDEO_TOOLTIP:
|
||||||
|
if (videoTooltip == null) {
|
||||||
|
videoTooltip = TooltipPopup.forTarget(callScreen.getVideoTooltipTarget())
|
||||||
|
.setBackgroundTint(ContextCompat.getColor(this, R.color.core_ultramarine))
|
||||||
|
.setTextColor(ContextCompat.getColor(this, R.color.core_white))
|
||||||
|
.setText(R.string.WebRtcCallActivity__tap_here_to_turn_on_your_video)
|
||||||
|
.setOnDismissListener(() -> viewModel.onDismissedVideoTooltip())
|
||||||
|
.show(TooltipPopup.POSITION_ABOVE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DISMISS_VIDEO_TOOLTIP:
|
||||||
|
if (videoTooltip != null) {
|
||||||
|
videoTooltip.dismiss();
|
||||||
|
videoTooltip = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown event: " + event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCallTime(long callTime) {
|
||||||
|
EllapsedTimeFormatter ellapsedTimeFormatter = EllapsedTimeFormatter.fromDurationMillis(callTime);
|
||||||
|
|
||||||
|
if (ellapsedTimeFormatter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callScreen.setStatus(getString(R.string.WebRtcCallActivity__signal_s, ellapsedTimeFormatter.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetAudioHandset() {
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER);
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetAudioSpeaker() {
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER);
|
||||||
|
intent.putExtra(WebRtcCallService.EXTRA_SPEAKER, true);
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetAudioBluetooth() {
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_BLUETOOTH);
|
||||||
|
intent.putExtra(WebRtcCallService.EXTRA_BLUETOOTH, true);
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetMuteAudio(boolean enabled) {
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_AUDIO);
|
||||||
|
intent.putExtra(WebRtcCallService.EXTRA_MUTE, enabled);
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetMuteVideo(boolean muted) {
|
||||||
|
Recipient recipient = viewModel.getRecipient().get();
|
||||||
|
|
||||||
|
if (!recipient.equals(Recipient.UNKNOWN)) {
|
||||||
|
String recipientDisplayName = recipient.getDisplayName(this);
|
||||||
|
|
||||||
|
Permissions.with(this)
|
||||||
|
.request(Manifest.permission.CAMERA)
|
||||||
|
.ifNecessary()
|
||||||
|
.withRationaleDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName), R.drawable.ic_video_solid_24_tinted)
|
||||||
|
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName))
|
||||||
|
.onAllGranted(() -> {
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_SET_ENABLE_VIDEO);
|
||||||
|
intent.putExtra(WebRtcCallService.EXTRA_ENABLE, !muted);
|
||||||
|
startService(intent);
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFlipCamera() {
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_FLIP_CAMERA);
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAnswerWithAudio() {
|
||||||
|
Recipient recipient = viewModel.getRecipient().get();
|
||||||
|
|
||||||
|
if (!recipient.equals(Recipient.UNKNOWN)) {
|
||||||
|
Permissions.with(this)
|
||||||
|
.request(Manifest.permission.RECORD_AUDIO)
|
||||||
|
.ifNecessary()
|
||||||
|
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, recipient.getDisplayName(this)),
|
||||||
|
R.drawable.ic_mic_solid_24)
|
||||||
|
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
|
||||||
|
.onAllGranted(() -> {
|
||||||
|
callScreen.setRecipient(recipient);
|
||||||
|
callScreen.setStatus(getString(R.string.RedPhone_answering));
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL);
|
||||||
|
startService(intent);
|
||||||
|
})
|
||||||
|
.onAnyDenied(this::handleDenyCall)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAnswerWithVideo() {
|
||||||
|
Recipient recipient = viewModel.getRecipient().get();
|
||||||
|
|
||||||
|
if (!recipient.equals(Recipient.UNKNOWN)) {
|
||||||
|
Permissions.with(this)
|
||||||
|
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
|
||||||
|
.ifNecessary()
|
||||||
|
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, recipient.getDisplayName(this)),
|
||||||
|
R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
|
||||||
|
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
|
||||||
|
.onAllGranted(() -> {
|
||||||
|
callScreen.setRecipient(recipient);
|
||||||
|
callScreen.setStatus(getString(R.string.RedPhone_answering));
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL);
|
||||||
|
intent.putExtra(WebRtcCallService.EXTRA_ANSWER_WITH_VIDEO, true);
|
||||||
|
startService(intent);
|
||||||
|
|
||||||
|
handleSetMuteVideo(false);
|
||||||
|
})
|
||||||
|
.onAnyDenied(this::handleDenyCall)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDenyCall() {
|
||||||
|
Recipient recipient = viewModel.getRecipient().get();
|
||||||
|
|
||||||
|
if (!recipient.equals(Recipient.UNKNOWN)) {
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_DENY_CALL);
|
||||||
|
startService(intent);
|
||||||
|
|
||||||
|
callScreen.setRecipient(recipient);
|
||||||
|
callScreen.setStatus(getString(R.string.RedPhone_ending_call));
|
||||||
|
delayedFinish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEndCall() {
|
||||||
|
Log.i(TAG, "Hangup pressed, handling termination now...");
|
||||||
|
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_LOCAL_HANGUP);
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleIncomingCall(@NonNull WebRtcViewModel event) {
|
||||||
|
callScreen.setRecipient(event.getRecipient());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleOutgoingCall(@NonNull WebRtcViewModel event) {
|
||||||
|
callScreen.setRecipient(event.getRecipient());
|
||||||
|
callScreen.setStatus(getString(R.string.WebRtcCallActivity__calling));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTerminate(@NonNull Recipient recipient, @NonNull HangupMessage.Type hangupType) {
|
||||||
|
Log.i(TAG, "handleTerminate called: " + hangupType.name());
|
||||||
|
|
||||||
|
callScreen.setRecipient(recipient);
|
||||||
|
callScreen.setStatusFromHangupType(hangupType);
|
||||||
|
|
||||||
|
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||||
|
|
||||||
|
if (hangupType == HangupMessage.Type.NEED_PERMISSION) {
|
||||||
|
startActivity(CalleeMustAcceptMessageRequestActivity.createIntent(this, recipient.getId()));
|
||||||
|
}
|
||||||
|
delayedFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCallRinging(@NonNull WebRtcViewModel event) {
|
||||||
|
callScreen.setRecipient(event.getRecipient());
|
||||||
|
callScreen.setStatus(getString(R.string.RedPhone_ringing));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCallBusy(@NonNull WebRtcViewModel event) {
|
||||||
|
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||||
|
callScreen.setRecipient(event.getRecipient());
|
||||||
|
callScreen.setStatus(getString(R.string.RedPhone_busy));
|
||||||
|
|
||||||
|
delayedFinish(BUSY_SIGNAL_DELAY_FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCallConnected(@NonNull WebRtcViewModel event) {
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
|
||||||
|
callScreen.setRecipient(event.getRecipient());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRecipientUnavailable(@NonNull WebRtcViewModel event) {
|
||||||
|
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||||
|
callScreen.setRecipient(event.getRecipient());
|
||||||
|
callScreen.setStatus(getString(R.string.RedPhone_recipient_unavailable));
|
||||||
|
delayedFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleServerFailure(@NonNull WebRtcViewModel event) {
|
||||||
|
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||||
|
callScreen.setRecipient(event.getRecipient());
|
||||||
|
callScreen.setStatus(getString(R.string.RedPhone_network_failed));
|
||||||
|
delayedFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) {
|
||||||
|
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
|
||||||
|
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
|
||||||
|
dialog.setTitle(R.string.RedPhone_number_not_registered);
|
||||||
|
dialog.setIconAttribute(R.attr.dialog_alert_icon);
|
||||||
|
dialog.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice);
|
||||||
|
dialog.setCancelable(true);
|
||||||
|
dialog.setPositiveButton(R.string.RedPhone_got_it, new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
WebRtcCallActivity.this.handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
WebRtcCallActivity.this.handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUntrustedIdentity(@NonNull WebRtcViewModel event) {
|
||||||
|
final IdentityKey theirKey = event.getIdentityKey();
|
||||||
|
final Recipient recipient = event.getRecipient();
|
||||||
|
|
||||||
|
if (theirKey == null) {
|
||||||
|
handleTerminate(recipient, HangupMessage.Type.NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = recipient.getDisplayName(this);
|
||||||
|
String introduction = getString(R.string.WebRtcCallScreen_new_safety_numbers, name, name);
|
||||||
|
SpannableString spannableString = new SpannableString(introduction + " " + getString(R.string.WebRtcCallScreen_you_may_wish_to_verify_this_contact));
|
||||||
|
|
||||||
|
spannableString.setSpan(new VerifySpan(this, recipient.getId(), theirKey), introduction.length() + 1, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
|
||||||
|
AppCompatTextView untrustedIdentityExplanation = new AppCompatTextView(this);
|
||||||
|
untrustedIdentityExplanation.setText(spannableString);
|
||||||
|
untrustedIdentityExplanation.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setView(untrustedIdentityExplanation)
|
||||||
|
.setPositiveButton(R.string.WebRtcCallScreen_accept, (d, w) -> {
|
||||||
|
synchronized (SESSION_LOCK) {
|
||||||
|
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(WebRtcCallActivity.this);
|
||||||
|
identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.requireServiceId(), 1), theirKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
d.dismiss();
|
||||||
|
|
||||||
|
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
|
||||||
|
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(recipient.getId()));
|
||||||
|
|
||||||
|
startService(intent);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.WebRtcCallScreen_end_call, (d, w) -> {
|
||||||
|
d.dismiss();
|
||||||
|
handleTerminate(recipient, HangupMessage.Type.NORMAL);
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSystemPipEnabledAndAvailable() {
|
||||||
|
return Build.VERSION.SDK_INT >= 26 &&
|
||||||
|
getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delayedFinish() {
|
||||||
|
delayedFinish(STANDARD_DELAY_FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delayedFinish(int delayMillis) {
|
||||||
|
callScreen.postDelayed(WebRtcCallActivity.this::finish, delayMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
|
public void onEventMainThread(final WebRtcViewModel event) {
|
||||||
|
Log.i(TAG, "Got message from service: " + event);
|
||||||
|
|
||||||
|
viewModel.setRecipient(event.getRecipient());
|
||||||
|
|
||||||
|
switch (event.getState()) {
|
||||||
|
case CALL_CONNECTED: handleCallConnected(event); break;
|
||||||
|
case NETWORK_FAILURE: handleServerFailure(event); break;
|
||||||
|
case CALL_RINGING: handleCallRinging(event); break;
|
||||||
|
case CALL_DISCONNECTED: handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break;
|
||||||
|
case CALL_ACCEPTED_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.ACCEPTED); break;
|
||||||
|
case CALL_DECLINED_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.DECLINED); break;
|
||||||
|
case CALL_ONGOING_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.BUSY); break;
|
||||||
|
case CALL_NEEDS_PERMISSION: handleTerminate(event.getRecipient(), HangupMessage.Type.NEED_PERMISSION); break;
|
||||||
|
case NO_SUCH_USER: handleNoSuchUser(event); break;
|
||||||
|
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(event); break;
|
||||||
|
case CALL_INCOMING: handleIncomingCall(event); break;
|
||||||
|
case CALL_OUTGOING: handleOutgoingCall(event); break;
|
||||||
|
case CALL_BUSY: handleCallBusy(event); break;
|
||||||
|
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
callScreen.setLocalRenderer(event.getLocalRenderer());
|
||||||
|
callScreen.setRemoteRenderer(event.getRemoteRenderer());
|
||||||
|
|
||||||
|
boolean enableVideo = event.getLocalCameraState().getCameraCount() > 0 && enableVideoIfAvailable;
|
||||||
|
|
||||||
|
viewModel.updateFromWebRtcViewModel(event, enableVideo);
|
||||||
|
|
||||||
|
if (enableVideo) {
|
||||||
|
enableVideoIfAvailable = false;
|
||||||
|
handleSetMuteVideo(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ControlsListener implements WebRtcCallView.ControlsListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onControlsFadeOut() {
|
||||||
|
if (videoTooltip != null) {
|
||||||
|
videoTooltip.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput) {
|
||||||
|
switch (audioOutput) {
|
||||||
|
case HANDSET:
|
||||||
|
handleSetAudioHandset();
|
||||||
|
break;
|
||||||
|
case HEADSET:
|
||||||
|
handleSetAudioBluetooth();
|
||||||
|
break;
|
||||||
|
case SPEAKER:
|
||||||
|
handleSetAudioSpeaker();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unknown output: " + audioOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoChanged(boolean isVideoEnabled) {
|
||||||
|
handleSetMuteVideo(!isVideoEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMicChanged(boolean isMicEnabled) {
|
||||||
|
handleSetMuteAudio(!isMicEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCameraDirectionChanged() {
|
||||||
|
handleFlipCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEndCallPressed() {
|
||||||
|
handleEndCall();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDenyCallPressed() {
|
||||||
|
handleDenyCall();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAcceptCallWithVoiceOnlyPressed() {
|
||||||
|
handleAnswerWithAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAcceptCallPressed() {
|
||||||
|
if (viewModel.isAnswerWithVideoAvailable()) {
|
||||||
|
handleAnswerWithVideo();
|
||||||
|
} else {
|
||||||
|
handleAnswerWithAudio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownCaretPressed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.util.Consumer;
|
||||||
|
|
||||||
|
public final class AnimationRepeatListener implements Animator.AnimatorListener {
|
||||||
|
|
||||||
|
private final Consumer<Animator> animationConsumer;
|
||||||
|
|
||||||
|
public AnimationRepeatListener(@NonNull Consumer<Animator> animationConsumer) {
|
||||||
|
this.animationConsumer = animationConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onAnimationStart(Animator animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onAnimationEnd(Animator animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onAnimationCancel(Animator animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onAnimationRepeat(Animator animation) {
|
||||||
|
this.animationConsumer.accept(animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation.transitions;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.transition.Transition;
|
||||||
|
import android.transition.TransitionValues;
|
||||||
|
import android.util.Property;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
||||||
|
|
||||||
|
@TargetApi(21)
|
||||||
|
abstract class CircleSquareImageViewTransition extends Transition {
|
||||||
|
|
||||||
|
private static final String CIRCLE_RATIO = "CIRCLE_RATIO";
|
||||||
|
|
||||||
|
private final boolean toCircle;
|
||||||
|
|
||||||
|
CircleSquareImageViewTransition(boolean toCircle) {
|
||||||
|
this.toCircle = toCircle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void captureStartValues(TransitionValues transitionValues) {
|
||||||
|
View view = transitionValues.view;
|
||||||
|
if (view instanceof ImageView) {
|
||||||
|
transitionValues.values.put(CIRCLE_RATIO, toCircle ? 0f : 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void captureEndValues(TransitionValues transitionValues) {
|
||||||
|
View view = transitionValues.view;
|
||||||
|
if (view instanceof ImageView) {
|
||||||
|
transitionValues.values.put(CIRCLE_RATIO, toCircle ? 1f : 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
|
||||||
|
if (startValues == null || endValues == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageView endImageView = (ImageView) endValues.view;
|
||||||
|
float start = (float) startValues.values.get(CIRCLE_RATIO);
|
||||||
|
float end = (float) endValues.values.get(CIRCLE_RATIO);
|
||||||
|
|
||||||
|
return ObjectAnimator.ofFloat(endImageView, new RadiusRatioProperty(), start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class RadiusRatioProperty extends Property<ImageView, Float> {
|
||||||
|
|
||||||
|
private float ratio;
|
||||||
|
|
||||||
|
RadiusRatioProperty() {
|
||||||
|
super(Float.class, "circle_ratio");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public void set(ImageView imageView, Float ratio) {
|
||||||
|
this.ratio = ratio;
|
||||||
|
Drawable imageViewDrawable = imageView.getDrawable();
|
||||||
|
if (imageViewDrawable instanceof RoundedBitmapDrawable) {
|
||||||
|
RoundedBitmapDrawable drawable = (RoundedBitmapDrawable) imageViewDrawable;
|
||||||
|
if (ratio > 0.95) {
|
||||||
|
drawable.setCircular(true);
|
||||||
|
} else {
|
||||||
|
drawable.setCornerRadius(Math.min(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()) * ratio * 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(ImageView object) {
|
||||||
|
return ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation.transitions;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will only transition {@link android.widget.ImageView}s that contain a {@link androidx.core.graphics.drawable.RoundedBitmapDrawable}.
|
||||||
|
*/
|
||||||
|
@TargetApi(21)
|
||||||
|
public final class CircleToSquareImageViewTransition extends CircleSquareImageViewTransition {
|
||||||
|
public CircleToSquareImageViewTransition(Context context, AttributeSet attrs) {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation.transitions;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will only transition {@link android.widget.ImageView}s that contain a {@link androidx.core.graphics.drawable.RoundedBitmapDrawable}.
|
||||||
|
*/
|
||||||
|
@TargetApi(21)
|
||||||
|
public final class SquareToCircleImageViewTransition extends CircleSquareImageViewTransition {
|
||||||
|
public SquareToCircleImageViewTransition(Context context, AttributeSet attrs) {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
|
|
||||||
|
public abstract class Attachment {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final String contentType;
|
||||||
|
private final int transferState;
|
||||||
|
private final long size;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String fileName;
|
||||||
|
|
||||||
|
private final int cdnNumber;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String location;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String relay;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final byte[] digest;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String fastPreflightId;
|
||||||
|
|
||||||
|
private final boolean voiceNote;
|
||||||
|
private final boolean borderless;
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
private final boolean quote;
|
||||||
|
private final long uploadTimestamp;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String caption;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final StickerLocator stickerLocator;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final BlurHash blurHash;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final AudioHash audioHash;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final TransformProperties transformProperties;
|
||||||
|
|
||||||
|
public Attachment(@NonNull String contentType,
|
||||||
|
int transferState,
|
||||||
|
long size,
|
||||||
|
@Nullable String fileName,
|
||||||
|
int cdnNumber,
|
||||||
|
@Nullable String location,
|
||||||
|
@Nullable String key,
|
||||||
|
@Nullable String relay,
|
||||||
|
@Nullable byte[] digest,
|
||||||
|
@Nullable String fastPreflightId,
|
||||||
|
boolean voiceNote,
|
||||||
|
boolean borderless,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
boolean quote,
|
||||||
|
long uploadTimestamp,
|
||||||
|
@Nullable String caption,
|
||||||
|
@Nullable StickerLocator stickerLocator,
|
||||||
|
@Nullable BlurHash blurHash,
|
||||||
|
@Nullable AudioHash audioHash,
|
||||||
|
@Nullable TransformProperties transformProperties)
|
||||||
|
{
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.transferState = transferState;
|
||||||
|
this.size = size;
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.cdnNumber = cdnNumber;
|
||||||
|
this.location = location;
|
||||||
|
this.key = key;
|
||||||
|
this.relay = relay;
|
||||||
|
this.digest = digest;
|
||||||
|
this.fastPreflightId = fastPreflightId;
|
||||||
|
this.voiceNote = voiceNote;
|
||||||
|
this.borderless = borderless;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.quote = quote;
|
||||||
|
this.uploadTimestamp = uploadTimestamp;
|
||||||
|
this.stickerLocator = stickerLocator;
|
||||||
|
this.caption = caption;
|
||||||
|
this.blurHash = blurHash;
|
||||||
|
this.audioHash = audioHash;
|
||||||
|
this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract Uri getDataUri();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract Uri getThumbnailUri();
|
||||||
|
|
||||||
|
public int getTransferState() {
|
||||||
|
return transferState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInProgress() {
|
||||||
|
return transferState != AttachmentDatabase.TRANSFER_PROGRESS_DONE &&
|
||||||
|
transferState != AttachmentDatabase.TRANSFER_PROGRESS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCdnNumber() {
|
||||||
|
return cdnNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getRelay() {
|
||||||
|
return relay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public byte[] getDigest() {
|
||||||
|
return digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getFastPreflightId() {
|
||||||
|
return fastPreflightId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVoiceNote() {
|
||||||
|
return voiceNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBorderless() {
|
||||||
|
return borderless;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQuote() {
|
||||||
|
return quote;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUploadTimestamp() {
|
||||||
|
return uploadTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSticker() {
|
||||||
|
return stickerLocator != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable StickerLocator getSticker() {
|
||||||
|
return stickerLocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable BlurHash getBlurHash() {
|
||||||
|
return blurHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable AudioHash getAudioHash() {
|
||||||
|
return audioHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getCaption() {
|
||||||
|
return caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull TransformProperties getTransformProperties() {
|
||||||
|
return transformProperties;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
public class AttachmentId implements Parcelable {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final long rowId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final long uniqueId;
|
||||||
|
|
||||||
|
public AttachmentId(@JsonProperty("rowId") long rowId, @JsonProperty("uniqueId") long uniqueId) {
|
||||||
|
this.rowId = rowId;
|
||||||
|
this.uniqueId = uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttachmentId(Parcel in) {
|
||||||
|
this.rowId = in.readLong();
|
||||||
|
this.uniqueId = in.readLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRowId() {
|
||||||
|
return rowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUniqueId() {
|
||||||
|
return uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] toStrings() {
|
||||||
|
return new String[] {String.valueOf(rowId), String.valueOf(uniqueId)};
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String toString() {
|
||||||
|
return "AttachmentId::(" + rowId + ", " + uniqueId + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return rowId >= 0 && uniqueId >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
AttachmentId attachmentId = (AttachmentId)o;
|
||||||
|
|
||||||
|
if (rowId != attachmentId.rowId) return false;
|
||||||
|
return uniqueId == attachmentId.uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Util.hashCode(rowId, uniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeLong(rowId);
|
||||||
|
dest.writeLong(uniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<AttachmentId> CREATOR = new Creator<AttachmentId>() {
|
||||||
|
@Override
|
||||||
|
public AttachmentId createFromParcel(Parcel in) {
|
||||||
|
return new AttachmentId(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttachmentId[] newArray(int size) {
|
||||||
|
return new AttachmentId[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
|
||||||
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public class DatabaseAttachment extends Attachment {
|
||||||
|
|
||||||
|
private final AttachmentId attachmentId;
|
||||||
|
private final long mmsId;
|
||||||
|
private final boolean hasData;
|
||||||
|
private final boolean hasThumbnail;
|
||||||
|
private final int displayOrder;
|
||||||
|
|
||||||
|
public DatabaseAttachment(AttachmentId attachmentId,
|
||||||
|
long mmsId,
|
||||||
|
boolean hasData,
|
||||||
|
boolean hasThumbnail,
|
||||||
|
String contentType,
|
||||||
|
int transferProgress,
|
||||||
|
long size,
|
||||||
|
String fileName,
|
||||||
|
int cdnNumber,
|
||||||
|
String location,
|
||||||
|
String key,
|
||||||
|
String relay,
|
||||||
|
byte[] digest,
|
||||||
|
String fastPreflightId,
|
||||||
|
boolean voiceNote,
|
||||||
|
boolean borderless,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
boolean quote,
|
||||||
|
@Nullable String caption,
|
||||||
|
@Nullable StickerLocator stickerLocator,
|
||||||
|
@Nullable BlurHash blurHash,
|
||||||
|
@Nullable AudioHash audioHash,
|
||||||
|
@Nullable TransformProperties transformProperties,
|
||||||
|
int displayOrder,
|
||||||
|
long uploadTimestamp)
|
||||||
|
{
|
||||||
|
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, borderless, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
||||||
|
this.attachmentId = attachmentId;
|
||||||
|
this.hasData = hasData;
|
||||||
|
this.hasThumbnail = hasThumbnail;
|
||||||
|
this.mmsId = mmsId;
|
||||||
|
this.displayOrder = displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Uri getDataUri() {
|
||||||
|
if (hasData) {
|
||||||
|
return PartAuthority.getAttachmentDataUri(attachmentId);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Uri getThumbnailUri() {
|
||||||
|
if (hasThumbnail) {
|
||||||
|
return PartAuthority.getAttachmentThumbnailUri(attachmentId);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttachmentId getAttachmentId() {
|
||||||
|
return attachmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDisplayOrder() {
|
||||||
|
return displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return other != null &&
|
||||||
|
other instanceof DatabaseAttachment &&
|
||||||
|
((DatabaseAttachment) other).attachmentId.equals(this.attachmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return attachmentId.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMmsId() {
|
||||||
|
return mmsId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasData() {
|
||||||
|
return hasData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasThumbnail() {
|
||||||
|
return hasThumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DisplayOrderComparator implements Comparator<DatabaseAttachment> {
|
||||||
|
@Override
|
||||||
|
public int compare(DatabaseAttachment lhs, DatabaseAttachment rhs) {
|
||||||
|
return Integer.compare(lhs.getDisplayOrder(), rhs.getDisplayOrder());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
|
|||||||
public class MmsNotificationAttachment extends Attachment {
|
public class MmsNotificationAttachment extends Attachment {
|
||||||
|
|
||||||
public MmsNotificationAttachment(int status, long size) {
|
public MmsNotificationAttachment(int status, long size) {
|
||||||
super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false, null, null, null, null);
|
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, false, 0, 0, false, 0, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package org.thoughtcrime.securesms.attachments;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PointerAttachment extends Attachment {
|
||||||
|
|
||||||
|
private PointerAttachment(@NonNull String contentType,
|
||||||
|
int transferState,
|
||||||
|
long size,
|
||||||
|
@Nullable String fileName,
|
||||||
|
int cdnNumber,
|
||||||
|
@NonNull String location,
|
||||||
|
@Nullable String key,
|
||||||
|
@Nullable String relay,
|
||||||
|
@Nullable byte[] digest,
|
||||||
|
@Nullable String fastPreflightId,
|
||||||
|
boolean voiceNote,
|
||||||
|
boolean borderless,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
long uploadTimestamp,
|
||||||
|
@Nullable String caption,
|
||||||
|
@Nullable StickerLocator stickerLocator,
|
||||||
|
@Nullable BlurHash blurHash)
|
||||||
|
{
|
||||||
|
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, borderless, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Uri getDataUri() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Uri getThumbnailUri() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<Attachment> forPointers(Optional<List<SignalServiceAttachment>> pointers) {
|
||||||
|
List<Attachment> results = new LinkedList<>();
|
||||||
|
|
||||||
|
if (pointers.isPresent()) {
|
||||||
|
for (SignalServiceAttachment pointer : pointers.get()) {
|
||||||
|
Optional<Attachment> result = forPointer(Optional.of(pointer));
|
||||||
|
|
||||||
|
if (result.isPresent()) {
|
||||||
|
results.add(result.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Attachment> forPointers(List<SignalServiceDataMessage.Quote.QuotedAttachment> pointers) {
|
||||||
|
List<Attachment> results = new LinkedList<>();
|
||||||
|
|
||||||
|
if (pointers != null) {
|
||||||
|
for (SignalServiceDataMessage.Quote.QuotedAttachment pointer : pointers) {
|
||||||
|
Optional<Attachment> result = forPointer(pointer);
|
||||||
|
|
||||||
|
if (result.isPresent()) {
|
||||||
|
results.add(result.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer) {
|
||||||
|
return forPointer(pointer, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer, @Nullable StickerLocator stickerLocator) {
|
||||||
|
return forPointer(pointer, stickerLocator, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<Attachment> forPointer(Optional<SignalServiceAttachment> pointer, @Nullable StickerLocator stickerLocator, @Nullable String fastPreflightId) {
|
||||||
|
if (!pointer.isPresent() || !pointer.get().isPointer()) return Optional.absent();
|
||||||
|
|
||||||
|
String encodedKey = null;
|
||||||
|
|
||||||
|
if (pointer.get().asPointer().getKey() != null) {
|
||||||
|
encodedKey = Base64.encodeBytes(pointer.get().asPointer().getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(new PointerAttachment(pointer.get().getContentType(),
|
||||||
|
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||||
|
pointer.get().asPointer().getSize().or(0),
|
||||||
|
pointer.get().asPointer().getFileName().orNull(),
|
||||||
|
pointer.get().asPointer().getCdnNumber(),
|
||||||
|
pointer.get().asPointer().getRemoteId().toString(),
|
||||||
|
encodedKey, null,
|
||||||
|
pointer.get().asPointer().getDigest().orNull(),
|
||||||
|
fastPreflightId,
|
||||||
|
pointer.get().asPointer().getVoiceNote(),
|
||||||
|
pointer.get().asPointer().isBorderless(),
|
||||||
|
pointer.get().asPointer().getWidth(),
|
||||||
|
pointer.get().asPointer().getHeight(),
|
||||||
|
pointer.get().asPointer().getUploadTimestamp(),
|
||||||
|
pointer.get().asPointer().getCaption().orNull(),
|
||||||
|
stickerLocator,
|
||||||
|
BlurHash.parseOrNull(pointer.get().asPointer().getBlurHash().orNull())));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<Attachment> forPointer(SignalServiceDataMessage.Quote.QuotedAttachment pointer) {
|
||||||
|
SignalServiceAttachment thumbnail = pointer.getThumbnail();
|
||||||
|
|
||||||
|
return Optional.of(new PointerAttachment(pointer.getContentType(),
|
||||||
|
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||||
|
thumbnail != null ? thumbnail.asPointer().getSize().or(0) : 0,
|
||||||
|
pointer.getFileName(),
|
||||||
|
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
|
||||||
|
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
|
||||||
|
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
|
||||||
|
null,
|
||||||
|
thumbnail != null ? thumbnail.asPointer().getDigest().orNull() : null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
|
||||||
|
thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
|
||||||
|
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
|
||||||
|
thumbnail != null ? thumbnail.asPointer().getCaption().orNull() : null,
|
||||||
|
null,
|
||||||
|
null));
|
||||||
|
}
|
||||||
|
}
|
||||||