Compare commits
1167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2f5fa6962 | ||
|
|
74336041ea | ||
|
|
0dea5eb779 | ||
|
|
267f759452 | ||
|
|
2779d7efc5 | ||
|
|
04b7cb15cc | ||
|
|
eba04eb75b | ||
|
|
d924fc92ab | ||
|
|
69de830a10 | ||
|
|
60b9811e08 | ||
|
|
01418c0e36 | ||
|
|
3f374eebc2 | ||
|
|
502a2ead00 | ||
|
|
0d9490e1fb | ||
|
|
4afb459b30 | ||
|
|
37f4557fef | ||
|
|
424979d91f | ||
|
|
527fbee41e | ||
|
|
1935b0ebdd | ||
|
|
f6aaef1434 | ||
|
|
050b59f09d | ||
|
|
db8dcf6073 | ||
|
|
c45002d5b6 | ||
|
|
3cd9f0ffef | ||
|
|
d065a6f563 | ||
|
|
49d7a032fb | ||
|
|
80f3504098 | ||
|
|
37d971859b | ||
|
|
2a5bed1d21 | ||
|
|
0927914c57 | ||
|
|
dda0e0393e | ||
|
|
cc9be7b61e | ||
|
|
2751076089 | ||
|
|
2a3f85008b | ||
|
|
432a732e7c | ||
|
|
dc6045ca8b | ||
|
|
b58b0fd7a8 | ||
|
|
5e122353e1 | ||
|
|
fc41fb5014 | ||
|
|
99477c8eef | ||
|
|
f50466f779 | ||
|
|
eb79300fe2 | ||
|
|
b35c96b0b6 | ||
|
|
2282cd12d7 | ||
|
|
0cbe992912 | ||
|
|
98cb6b457c | ||
|
|
29d66f2b92 | ||
|
|
763a12dbc6 | ||
|
|
158f3d898f | ||
|
|
b935999548 | ||
|
|
2cca6a5afb | ||
|
|
2954c31b5f | ||
|
|
6c2d21125e | ||
|
|
59d69192c6 | ||
|
|
937a288cee | ||
|
|
236e1ba885 | ||
|
|
3bdf2e7e2c | ||
|
|
a160af2d11 | ||
|
|
341a31da00 | ||
|
|
e0128e7e31 | ||
|
|
8f51bdcb78 | ||
|
|
53dc5bab43 | ||
|
|
8f86de1764 | ||
|
|
133a7d2576 | ||
|
|
8b7506ed2d | ||
|
|
c378e4413e | ||
|
|
f3182ddbc6 | ||
|
|
951d4ad06f | ||
|
|
2678a00781 | ||
|
|
e6e8786d86 | ||
|
|
1ec3782f64 | ||
|
|
a4ec31eebe | ||
|
|
94b631ccfe | ||
|
|
26d8df5ea9 | ||
|
|
0569d0555f | ||
|
|
3e2349c4ff | ||
|
|
af7e736de9 | ||
|
|
51879a9c46 | ||
|
|
524f3d6d08 | ||
|
|
64fe78ff9a | ||
|
|
e798f3f276 | ||
|
|
ddb04c6ea3 | ||
|
|
213ffdab62 | ||
|
|
60354b2f1f | ||
|
|
4bb214cb2a | ||
|
|
cfd4399685 | ||
|
|
30563ed3e5 | ||
|
|
46344776a4 | ||
|
|
0d215d609b | ||
|
|
c15ea8c0b4 | ||
|
|
d6061fb699 | ||
|
|
7f2b6178d5 | ||
|
|
53177bf40e | ||
|
|
857b945410 | ||
|
|
904593c103 | ||
|
|
dcfa7e3b36 | ||
|
|
589f345825 | ||
|
|
0b7c22886d | ||
|
|
e9e2846532 | ||
|
|
e0fc191883 | ||
|
|
b2ecd89a71 | ||
|
|
9ed95a6081 | ||
|
|
3f51f89d86 | ||
|
|
01778f718a | ||
|
|
7d5ddd8eac | ||
|
|
2447601219 | ||
|
|
701e43c13d | ||
|
|
bbbccccf47 | ||
|
|
1e9ca0a9bf | ||
|
|
0b62bb8168 | ||
|
|
4f9f62992f | ||
|
|
1938d6cae0 | ||
|
|
13e8c55781 | ||
|
|
6264f9b585 | ||
|
|
4482bfcabb | ||
|
|
015088a53f | ||
|
|
ef7d707432 | ||
|
|
d1f6a924fb | ||
|
|
f312757daf | ||
|
|
1d83729e6c | ||
|
|
6a45858b4a | ||
|
|
1b448c2bdf | ||
|
|
f6cd190245 | ||
|
|
23303e5407 | ||
|
|
b5237848e9 | ||
|
|
7cac0c9a7c | ||
|
|
9dbbe4675f | ||
|
|
95978f16e9 | ||
|
|
d055bba452 | ||
|
|
8ef809a02b | ||
|
|
458941f952 | ||
|
|
5852a508aa | ||
|
|
e2b4995fbb | ||
|
|
a3556d9f68 | ||
|
|
fe890a1a41 | ||
|
|
c06bb18249 | ||
|
|
9099969b41 | ||
|
|
6358f59f67 | ||
|
|
073034dd3c | ||
|
|
17fb815805 | ||
|
|
409e7c41b4 | ||
|
|
b9a1a5027c | ||
|
|
49535f6378 | ||
|
|
c058452605 | ||
|
|
b3511dba77 | ||
|
|
afbe27c55f | ||
|
|
41d227207d | ||
|
|
92b586c061 | ||
|
|
acbc17c909 | ||
|
|
15f17747ee | ||
|
|
781054fc9d | ||
|
|
b59769a30a | ||
|
|
26e0e09e24 | ||
|
|
3a2990a911 | ||
|
|
d8060b3041 | ||
|
|
f42ec5318f | ||
|
|
bd0d425cbf | ||
|
|
b3d5d7c33e | ||
|
|
1746869dc3 | ||
|
|
633f4cbbe5 | ||
|
|
0944e2f758 | ||
|
|
b49e4004ab | ||
|
|
68381f8b64 | ||
|
|
f180066058 | ||
|
|
6b7de2e85e | ||
|
|
c650a978e9 | ||
|
|
e05cadafe6 | ||
|
|
c6008a4f90 | ||
|
|
5624855eba | ||
|
|
799ff86fc0 | ||
|
|
798fc84e82 | ||
|
|
cc363a3c88 | ||
|
|
6fdaef1f58 | ||
|
|
6db6c93295 | ||
|
|
4fb0f30d32 | ||
|
|
7fa4eb079b | ||
|
|
c5392b8844 | ||
|
|
e460973957 | ||
|
|
e1c6311a18 | ||
|
|
ed11e2f05a | ||
|
|
3182e5af88 | ||
|
|
5cfdf626fe | ||
|
|
e55834d523 | ||
|
|
9d5a52a980 | ||
|
|
5649c906a5 | ||
|
|
ee548d27e5 | ||
|
|
1dc737b5e5 | ||
|
|
427869d4ca | ||
|
|
36395ced89 | ||
|
|
52a9f2c893 | ||
|
|
bd88be2513 | ||
|
|
7107c1d6b2 | ||
|
|
51f4a343c9 | ||
|
|
a12ee1b78b | ||
|
|
4bf59a55da | ||
|
|
dbac9bf9f6 | ||
|
|
b95083fe92 | ||
|
|
e2d297eb8a | ||
|
|
a3176bbb67 | ||
|
|
4203dde151 | ||
|
|
880661710f | ||
|
|
d844fa0fb5 | ||
|
|
18ede2e900 | ||
|
|
06cc96bee7 | ||
|
|
43a12d2a81 | ||
|
|
0a29ffcf4c | ||
|
|
9c88532c21 | ||
|
|
f3450b8f10 | ||
|
|
a4d56e376f | ||
|
|
24b5bac589 | ||
|
|
762f17f1c1 | ||
|
|
105c8c9745 | ||
|
|
93d99287eb | ||
|
|
ce156c3450 | ||
|
|
7db16e6156 | ||
|
|
e80033c287 | ||
|
|
1553f9b75d | ||
|
|
c244a98962 | ||
|
|
a8ad1e718e | ||
|
|
b5712f4bd1 | ||
|
|
6bcb0de43d | ||
|
|
80651d2425 | ||
|
|
46492b8238 | ||
|
|
1be561543c | ||
|
|
e430a46e20 | ||
|
|
8d187c8ba1 | ||
|
|
eacf03768f | ||
|
|
b077c9b4f3 | ||
|
|
893749fcab | ||
|
|
848ead5e78 | ||
|
|
9c47acb004 | ||
|
|
8ca54bcc7b | ||
|
|
7e64d57ba8 | ||
|
|
a517fc4e15 | ||
|
|
4f4aea22ce | ||
|
|
e0ea2bdde4 | ||
|
|
d40dc1d90b | ||
|
|
4571151e3c | ||
|
|
3e43963f67 | ||
|
|
fe71d6ac41 | ||
|
|
0514950333 | ||
|
|
a2dc781840 | ||
|
|
2c1c6fab35 | ||
|
|
3c2e428c54 | ||
|
|
8f7fe5c3ee | ||
|
|
93e9dd6425 | ||
|
|
c95f0fce6e | ||
|
|
a3c7e7e552 | ||
|
|
1e2590af49 | ||
|
|
562e608e1f | ||
|
|
417d5a2804 | ||
|
|
c0c8d2caa7 | ||
|
|
727175e4f4 | ||
|
|
577d2b13ca | ||
|
|
6ac2f922e2 | ||
|
|
98297e55c1 | ||
|
|
aa2094a2cc | ||
|
|
f8c053cc96 | ||
|
|
790f8426ac | ||
|
|
ff11609a82 | ||
|
|
94346033a8 | ||
|
|
cb1401f556 | ||
|
|
ae676d7486 | ||
|
|
2d39e43677 | ||
|
|
0ccc7e3c06 | ||
|
|
2d20ceea01 | ||
|
|
cee2702fdf | ||
|
|
6c94be70dc | ||
|
|
f24020e7b7 | ||
|
|
728f1707b6 | ||
|
|
adea15df10 | ||
|
|
be91f2396c | ||
|
|
8724d904b7 | ||
|
|
ef95479157 | ||
|
|
710cd23537 | ||
|
|
0af313a81f | ||
|
|
71be388989 | ||
|
|
db3098f633 | ||
|
|
ac197f42f2 | ||
|
|
d82882ba28 | ||
|
|
957a12875d | ||
|
|
796eb5043c | ||
|
|
4f8d86828f | ||
|
|
5370605815 | ||
|
|
d5fb71b63f | ||
|
|
2455c291d8 | ||
|
|
80ad28e9cc | ||
|
|
74552ba545 | ||
|
|
141cab1105 | ||
|
|
f012a41345 | ||
|
|
1f95df60d4 | ||
|
|
560c8c8cac | ||
|
|
7cd79f8a94 | ||
|
|
667304c81e | ||
|
|
2dd95c6ef6 | ||
|
|
29e66e1d47 | ||
|
|
5eb5af2f87 | ||
|
|
e47b62805b | ||
|
|
57adc73e95 | ||
|
|
8f4d64d37a | ||
|
|
9ce3813044 | ||
|
|
6436e2836d | ||
|
|
77c83019d0 | ||
|
|
e6dfe96569 | ||
|
|
5d515198e6 | ||
|
|
1d912c0db2 | ||
|
|
bac04dea8d | ||
|
|
3b39d13412 | ||
|
|
9838b2cf0a | ||
|
|
0ac56ca571 | ||
|
|
12321bc2f0 | ||
|
|
3a55dfa32f | ||
|
|
373972f5dc | ||
|
|
60a701f84f | ||
|
|
14f7c01fcb | ||
|
|
caf4f1a7ba | ||
|
|
eb55ac9a97 | ||
|
|
b9d8868aab | ||
|
|
bec03534ef | ||
|
|
565eab9dc1 | ||
|
|
4d229862b6 | ||
|
|
3739eb7731 | ||
|
|
ae5f9fb8ac | ||
|
|
4320a81846 | ||
|
|
9fcf40fdc4 | ||
|
|
79d6ac100c | ||
|
|
a3e3153ee3 | ||
|
|
0f525d2b07 | ||
|
|
8de3f5045b | ||
|
|
fba4ae91e3 | ||
|
|
dda68d6c95 | ||
|
|
25af25cd19 | ||
|
|
dfd5b2c225 | ||
|
|
e850d8e917 | ||
|
|
677cf725a1 | ||
|
|
e95bb9cb0f | ||
|
|
2c223a5826 | ||
|
|
bbc346bd7a | ||
|
|
cf32b93269 | ||
|
|
84f1da76ad | ||
|
|
e845fba8b3 | ||
|
|
01152ead61 | ||
|
|
198281aa47 | ||
|
|
8e8d86606b | ||
|
|
b4c2e21415 | ||
|
|
6080e1f338 | ||
|
|
6dd3fdaa55 | ||
|
|
64312f9c7f | ||
|
|
86542febf9 | ||
|
|
9da49f9f8a | ||
|
|
ce3872ce1a | ||
|
|
c466dba8c4 | ||
|
|
46d412a6c3 | ||
|
|
e2872d9af8 | ||
|
|
3474b26f61 | ||
|
|
740e934e5d | ||
|
|
61c5fc1057 | ||
|
|
7ef77bf16c | ||
|
|
aa3eb78956 | ||
|
|
cdd7b2deb9 | ||
|
|
c27300c19d | ||
|
|
8927971a19 | ||
|
|
1ced115b54 | ||
|
|
fcbd594def | ||
|
|
4b8d02fdba | ||
|
|
e10284bd13 | ||
|
|
4b5f1d64e6 | ||
|
|
b7477d287b | ||
|
|
6bab6c2454 | ||
|
|
586c45616c | ||
|
|
ccd405fdce | ||
|
|
dbf78d1b69 | ||
|
|
5f947ea2d6 | ||
|
|
73afa82147 | ||
|
|
744b79419b | ||
|
|
ce20dd97ff | ||
|
|
3983d5aca4 | ||
|
|
7b0de2d2a9 | ||
|
|
2b65482abd | ||
|
|
fe01e80af5 | ||
|
|
fc43a0d8e9 | ||
|
|
e709cdc9d5 | ||
|
|
d2d698f64e | ||
|
|
7f1e33be32 | ||
|
|
443f1a1554 | ||
|
|
ebb025c40a | ||
|
|
f3ce582fa5 | ||
|
|
372744178e | ||
|
|
fc3aa96b5a | ||
|
|
f4c723cc60 | ||
|
|
7864c8ceb4 | ||
|
|
4c80aac4d6 | ||
|
|
e2b6e85431 | ||
|
|
8587153ddd | ||
|
|
21956e400f | ||
|
|
fa7346f79b | ||
|
|
7227b43bbe | ||
|
|
e8c75249f1 | ||
|
|
cc5628cbce | ||
|
|
441808b1df | ||
|
|
42b0fe7853 | ||
|
|
7877f5db2f | ||
|
|
b972e05660 | ||
|
|
23579a9b1d | ||
|
|
af99753d47 | ||
|
|
4b2366e537 | ||
|
|
bea72c2ee3 | ||
|
|
32a50fcfad | ||
|
|
30fa741365 | ||
|
|
bed2544ff4 | ||
|
|
5a773de3b1 | ||
|
|
924405c8ba | ||
|
|
93e9de3932 | ||
|
|
a8dd81eace | ||
|
|
ec8793c6fe | ||
|
|
ffc0a230be | ||
|
|
5d4922ed8d | ||
|
|
974c33fe37 | ||
|
|
3f2b4d60fd | ||
|
|
ca633b13af | ||
|
|
a671e152bd | ||
|
|
a564aae80a | ||
|
|
9f8e31db78 | ||
|
|
84e9282f87 | ||
|
|
3949f4fd45 | ||
|
|
944a180b68 | ||
|
|
9cd1a12b6a | ||
|
|
a4a2d2fc0d | ||
|
|
6df839612d | ||
|
|
dd630abd0e | ||
|
|
6826c0ded5 | ||
|
|
f1d0d4f81b | ||
|
|
bfa56f771d | ||
|
|
167b9c13e5 | ||
|
|
4b7d9a3b9d | ||
|
|
c7585c5594 | ||
|
|
c3d7b88cf6 | ||
|
|
dc4ce234b7 | ||
|
|
12330b0aff | ||
|
|
edb2a17bcb | ||
|
|
00b6416583 | ||
|
|
62297f1f98 | ||
|
|
c00b0727e3 | ||
|
|
13616b9820 | ||
|
|
6530e1d937 | ||
|
|
aff00615cb | ||
|
|
be53bfa88f | ||
|
|
5de50f1a8b | ||
|
|
61886ea10a | ||
|
|
ea94f6bc91 | ||
|
|
6080c18c90 | ||
|
|
595d5dddbe | ||
|
|
9b81e7f71b | ||
|
|
bdc6c8c65a | ||
|
|
2dcc7d284f | ||
|
|
234e4be924 | ||
|
|
1083e022cc | ||
|
|
cb1b4ec0b9 | ||
|
|
40c46351e6 | ||
|
|
3f75e4aeb3 | ||
|
|
4321fabf0b | ||
|
|
8e93bf9075 | ||
|
|
831cd2f297 | ||
|
|
42d61518b3 | ||
|
|
112782ccaf | ||
|
|
67a3a30d4c | ||
|
|
898d92ba54 | ||
|
|
323a405004 | ||
|
|
3f25609561 | ||
|
|
97047bccde | ||
|
|
31960b53a0 | ||
|
|
ac41f3d662 | ||
|
|
82eebbc3b0 | ||
|
|
b1d74e21e2 | ||
|
|
7868c3094b | ||
|
|
ebaa4cee65 | ||
|
|
141b22765e | ||
|
|
050fad3114 | ||
|
|
01f143667f | ||
|
|
2729eb9f5f | ||
|
|
0a8e0d7889 | ||
|
|
25bffa6d56 | ||
|
|
cf7fb7e1a2 | ||
|
|
4b7017580c | ||
|
|
90852b5715 | ||
|
|
2103fd016b | ||
|
|
973ad55dfe | ||
|
|
c3dea97857 | ||
|
|
0e37381179 | ||
|
|
f7bc975534 | ||
|
|
fab24bcd1e | ||
|
|
9be2e6b815 | ||
|
|
4037170b4a | ||
|
|
b1974f31a9 | ||
|
|
e6bf8f078d | ||
|
|
cea4ee4ea9 | ||
|
|
283ff44da9 | ||
|
|
adee104899 | ||
|
|
1a844abcec | ||
|
|
5f30745908 | ||
|
|
4ae0f3999c | ||
|
|
dcb16378c8 | ||
|
|
b59a5c8609 | ||
|
|
55c9124c54 | ||
|
|
1376b4c0b8 | ||
|
|
9333e4fb68 | ||
|
|
04d3faf057 | ||
|
|
bcfbed9b3f | ||
|
|
dda51bf367 | ||
|
|
a324288d97 | ||
|
|
f21d2a2187 | ||
|
|
5272fec948 | ||
|
|
fe11ebce55 | ||
|
|
221cf56ddc | ||
|
|
7efd8be238 | ||
|
|
105862b524 | ||
|
|
cce8cdc7bf | ||
|
|
834c2c2495 | ||
|
|
59f7ee6682 | ||
|
|
6cbd68fe9f | ||
|
|
e1bf23251f | ||
|
|
3aebadd90d | ||
|
|
e57a35ab3e | ||
|
|
13c014215d | ||
|
|
02931f1826 | ||
|
|
a640d9e298 | ||
|
|
ce68da1613 | ||
|
|
3599122ca6 | ||
|
|
0003830a42 | ||
|
|
3804a89619 | ||
|
|
d4748efd42 | ||
|
|
0bda1d46a2 | ||
|
|
43e3ef2bee | ||
|
|
ce44e3949c | ||
|
|
7bb1262571 | ||
|
|
39f1aea8e3 | ||
|
|
bda19d01ed | ||
|
|
1f5364f01d | ||
|
|
65e88d2d1c | ||
|
|
cef8aa67dd | ||
|
|
5941b22eb6 | ||
|
|
9e7c55847e | ||
|
|
5209b74605 | ||
|
|
b90a74d26a | ||
|
|
8c1737e597 | ||
|
|
2ea5bd2d44 | ||
|
|
4166e7931e | ||
|
|
89f2c25d73 | ||
|
|
abb1ca2afe | ||
|
|
f7befd1593 | ||
|
|
28511de23c | ||
|
|
2ff3d1b7c5 | ||
|
|
fe6ae7e142 | ||
|
|
0da6c83ce4 | ||
|
|
184b7db43c | ||
|
|
e442e34c1b | ||
|
|
011efb0ce7 | ||
|
|
63d00f87d8 | ||
|
|
0323858145 | ||
|
|
a70e8ec7a7 | ||
|
|
b306a3ef41 | ||
|
|
ccd3467a61 | ||
|
|
40338afe7a | ||
|
|
ff97f6af56 | ||
|
|
6e7858e00f | ||
|
|
95468c85a8 | ||
|
|
f59e10d82c | ||
|
|
930370783e | ||
|
|
75062ada8a | ||
|
|
23618923d8 | ||
|
|
f1d3a2f322 | ||
|
|
3b7fbbaf6e | ||
|
|
725d793b20 | ||
|
|
5c3baca055 | ||
|
|
6e5abc92a0 | ||
|
|
8df6e95781 | ||
|
|
2290a6c0df | ||
|
|
907e8d93a3 | ||
|
|
918497fb94 | ||
|
|
3ccd6304c7 | ||
|
|
51d47adf57 | ||
|
|
f1e5206f56 | ||
|
|
f410635e2c | ||
|
|
302d57bf19 | ||
|
|
4c301a49b4 | ||
|
|
4ecfee292e | ||
|
|
2a193ef455 | ||
|
|
96e241ef9c | ||
|
|
e294a895e8 | ||
|
|
003b9b1551 | ||
|
|
a4e4af502e | ||
|
|
06aada20c1 | ||
|
|
2dace38d43 | ||
|
|
554aa1ddf0 | ||
|
|
3b2a5f1ce3 | ||
|
|
3fc4b098e8 | ||
|
|
a7d672f6b4 | ||
|
|
7e347f5cce | ||
|
|
4eaa6ebb47 | ||
|
|
e85ef6881d | ||
|
|
b1f6786392 | ||
|
|
696fffb603 | ||
|
|
3bb366ee04 | ||
|
|
6a59974f89 | ||
|
|
8e39267c42 | ||
|
|
b937534ce5 | ||
|
|
f5b46f7356 | ||
|
|
cd58c09be3 | ||
|
|
e8f0038c36 | ||
|
|
0b77b33902 | ||
|
|
c3b5323010 | ||
|
|
81eaae4070 | ||
|
|
65461ce86f | ||
|
|
536e3139a2 | ||
|
|
e9c7b120a0 | ||
|
|
d6a230a235 | ||
|
|
6bf300ada8 | ||
|
|
d307db8a95 | ||
|
|
c4c32d80b2 | ||
|
|
f4c1e34402 | ||
|
|
0068d62122 | ||
|
|
3f1fa59e09 | ||
|
|
df5114c62c | ||
|
|
956e3924ff | ||
|
|
20ad166e0f | ||
|
|
12ea88f409 | ||
|
|
0c5648bfb1 | ||
|
|
91ca19f294 | ||
|
|
71250afd2c | ||
|
|
cfdef7bca7 | ||
|
|
872f935fd5 | ||
|
|
0ed1f73990 | ||
|
|
349a2f72cb | ||
|
|
2b4a4d6109 | ||
|
|
9f882d2fbb | ||
|
|
cb4a9730aa | ||
|
|
e0657d09d8 | ||
|
|
01b9cb13b4 | ||
|
|
2c7260557c | ||
|
|
9e5156ab73 | ||
|
|
3dc1614fbc | ||
|
|
2f69a9c38e | ||
|
|
5e536c3fa5 | ||
|
|
6bb9d27d4e | ||
|
|
2d1bf33902 | ||
|
|
985a220fca | ||
|
|
31e137cf6d | ||
|
|
f796447815 | ||
|
|
936e772ba0 | ||
|
|
ecee797d00 | ||
|
|
357a8fc124 | ||
|
|
1233af0ddd | ||
|
|
a264d10685 | ||
|
|
ed17701a0a | ||
|
|
49e1ccea28 | ||
|
|
4c43b0d1e3 | ||
|
|
5ce09defca | ||
|
|
da9064b714 | ||
|
|
7bb53e4b06 | ||
|
|
6a4ce1b658 | ||
|
|
f84595e1e8 | ||
|
|
41d5c54033 | ||
|
|
b9d6b63c09 | ||
|
|
506ad0b3f1 | ||
|
|
c8302174a9 | ||
|
|
39cebfbb4e | ||
|
|
d36ec9af47 | ||
|
|
5f6d971bf7 | ||
|
|
7a722d92a3 | ||
|
|
0bf0eba450 | ||
|
|
d40783f794 | ||
|
|
88733473e2 | ||
|
|
7b65533095 | ||
|
|
52b533c121 | ||
|
|
a4fa2e14fb | ||
|
|
6933f1d818 | ||
|
|
b5d6cb2a8d | ||
|
|
d1478c5ce0 | ||
|
|
fbe62f0f3e | ||
|
|
f84705b756 | ||
|
|
cf2189c11a | ||
|
|
dfc4178252 | ||
|
|
07952f2146 | ||
|
|
a90dad22a9 | ||
|
|
64f7330609 | ||
|
|
5e382c120b | ||
|
|
3eea568f5f | ||
|
|
0077b29d6e | ||
|
|
dfa6306b61 | ||
|
|
a4bf075a1a | ||
|
|
373d622535 | ||
|
|
ba1df58eb3 | ||
|
|
9fb85f7c76 | ||
|
|
5e58f0a212 | ||
|
|
8fa01f13e9 | ||
|
|
4ce136be17 | ||
|
|
4099154dc0 | ||
|
|
3f983a5c82 | ||
|
|
9743e3689a | ||
|
|
1363f55f77 | ||
|
|
f1d98f6c7b | ||
|
|
9279a54d28 | ||
|
|
81889d8130 | ||
|
|
6aecb8fbc1 | ||
|
|
8aa413032d | ||
|
|
5bc4686eb8 | ||
|
|
f676d1c61c | ||
|
|
ac54b5cbdf | ||
|
|
b4b1e5b605 | ||
|
|
5eace49739 | ||
|
|
e93d7518f3 | ||
|
|
9c97cd8816 | ||
|
|
90f20c36c5 | ||
|
|
9f8dd7992a | ||
|
|
f4d3fe9176 | ||
|
|
ffc7c13717 | ||
|
|
daf93c473b | ||
|
|
d21782696a | ||
|
|
3357475fc4 | ||
|
|
ead64d92a5 | ||
|
|
5eaac6cb17 | ||
|
|
b3f0a44f10 | ||
|
|
e4d0e2f730 | ||
|
|
492a42883e | ||
|
|
b182f73415 | ||
|
|
e766b9737e | ||
|
|
2335f93579 | ||
|
|
1730260343 | ||
|
|
27506e9ed8 | ||
|
|
dc64a186d5 | ||
|
|
3163e09b98 | ||
|
|
dcb9978bb1 | ||
|
|
4a94a0a5c5 | ||
|
|
8a2d20403e | ||
|
|
ec706e95cc | ||
|
|
bd3b14a27f | ||
|
|
082d9e852c | ||
|
|
36da519b26 | ||
|
|
06ffdde892 | ||
|
|
1ec57c080c | ||
|
|
a635f27c68 | ||
|
|
ee3d7a9a35 | ||
|
|
9a1c869efe | ||
|
|
837ed76f85 | ||
|
|
b46589cd14 | ||
|
|
d04e4606d2 | ||
|
|
385bd0eb8a | ||
|
|
089656e5c4 | ||
|
|
84ec6dd458 | ||
|
|
322c139c26 | ||
|
|
babe1833bb | ||
|
|
9effa47dd8 | ||
|
|
7ef57cc0cf | ||
|
|
97420aae1b | ||
|
|
415e6309f9 | ||
|
|
83e63ff854 | ||
|
|
de7f103130 | ||
|
|
2cb912681d | ||
|
|
04bdf94b78 | ||
|
|
c7389ddaa7 | ||
|
|
e778ab2e3a | ||
|
|
533d86607f | ||
|
|
cb2096670f | ||
|
|
284f221a9d | ||
|
|
bc639dd438 | ||
|
|
1baddbb40e | ||
|
|
f784dab868 | ||
|
|
85192aaa21 | ||
|
|
054c705fe2 | ||
|
|
07b0d8cf6e | ||
|
|
597d16f566 | ||
|
|
0ca2c781c3 | ||
|
|
f642de9c41 | ||
|
|
8965388d05 | ||
|
|
58c4582f15 | ||
|
|
44bc1b5cc0 | ||
|
|
714ebb3e08 | ||
|
|
8f871c2e3a | ||
|
|
5cdc5bc441 | ||
|
|
8d060837ad | ||
|
|
1d230d4cd6 | ||
|
|
3636ae7667 | ||
|
|
9ffb5112c6 | ||
|
|
ca5d574cd7 | ||
|
|
c80283dbcc | ||
|
|
3fcaddf2d3 | ||
|
|
6ecff5bce9 | ||
|
|
a103c7dcb6 | ||
|
|
63746bbb47 | ||
|
|
ed0be6fc9a | ||
|
|
26404ff5d7 | ||
|
|
adf1674877 | ||
|
|
ab2235fc88 | ||
|
|
441a6d3fe7 | ||
|
|
e00397620a | ||
|
|
38fa58c0a3 | ||
|
|
b40fd7b243 | ||
|
|
ae34877496 | ||
|
|
599cf1e5cb | ||
|
|
474963dcf1 | ||
|
|
e22384b6b4 | ||
|
|
fb00652396 | ||
|
|
a5dbb5d91f | ||
|
|
e75a03b6f8 | ||
|
|
eb7fe7f3e0 | ||
|
|
3179808f17 | ||
|
|
fde9f05bd0 | ||
|
|
8de4290c5b | ||
|
|
19c74c8872 | ||
|
|
50edb5d1f4 | ||
|
|
c6ccfd7e75 | ||
|
|
3796ce69e4 | ||
|
|
9835e31b46 | ||
|
|
a35040c909 | ||
|
|
a4c94638ca | ||
|
|
e70a8ae6a0 | ||
|
|
100359e38d | ||
|
|
cd995aca56 | ||
|
|
3a4bae88ca | ||
|
|
e60eae27fb | ||
|
|
cd6c01e230 | ||
|
|
0af264429f | ||
|
|
a6d3862350 | ||
|
|
3fca4850dd | ||
|
|
ba7e41d9a6 | ||
|
|
fe33ce3413 | ||
|
|
4e25e8aaa2 | ||
|
|
91be826c7d | ||
|
|
fdfe0cddb8 | ||
|
|
e8ef62116f | ||
|
|
caf8bb39d8 | ||
|
|
222ba6ee53 | ||
|
|
8dcda73072 | ||
|
|
810365d334 | ||
|
|
4b31510589 | ||
|
|
dfce9a34b8 | ||
|
|
dc9370c32b | ||
|
|
8dbc721c08 | ||
|
|
6448b84430 | ||
|
|
93d6ce40c3 | ||
|
|
ce5be2c1be | ||
|
|
20fe837022 | ||
|
|
e3ce18fa3e | ||
|
|
864a1d5e93 | ||
|
|
9cf7eec247 | ||
|
|
d9c15621f6 | ||
|
|
fea14218a9 | ||
|
|
dbbded5250 | ||
|
|
d65cfc7981 | ||
|
|
dc9124f291 | ||
|
|
4cd433b6bc | ||
|
|
f9a9ee6b0c | ||
|
|
1741f7ed58 | ||
|
|
d459c751be | ||
|
|
34ef8b52f6 | ||
|
|
5ae96905bb | ||
|
|
b1fdbc0151 | ||
|
|
a5ad27b5f2 | ||
|
|
efcd5052a2 | ||
|
|
f2b10c0ba8 | ||
|
|
f182be2d79 | ||
|
|
41b10630bb | ||
|
|
45915bed90 | ||
|
|
a2c2ab428a | ||
|
|
a05f74d302 | ||
|
|
74e94f3a97 | ||
|
|
15ee8c6cac | ||
|
|
18957b1f41 | ||
|
|
29930cac41 | ||
|
|
e3338dc3ff | ||
|
|
97b7b4a501 | ||
|
|
b471a72856 | ||
|
|
fed7d911a3 | ||
|
|
ca442970a3 | ||
|
|
9dbb77c10a | ||
|
|
1116502bc0 | ||
|
|
edaf17bdd4 | ||
|
|
c61d731358 | ||
|
|
a8415a3484 | ||
|
|
cd2467085e | ||
|
|
64efb3d2a4 | ||
|
|
e05f137bd8 | ||
|
|
0c73ddc08b | ||
|
|
19cc43c442 | ||
|
|
7108fc81a9 | ||
|
|
5943b9d7d6 | ||
|
|
0271e4c918 | ||
|
|
9dc33eff3a | ||
|
|
5aef1c8a68 | ||
|
|
c608a05270 | ||
|
|
e2cfd247c3 | ||
|
|
97eb9154b2 | ||
|
|
d7ff635445 | ||
|
|
aff57fb54e | ||
|
|
e89285a219 | ||
|
|
706f43caa8 | ||
|
|
dc4faf57cb | ||
|
|
7baf8052a2 | ||
|
|
d3c59585fd | ||
|
|
859bb8dc79 | ||
|
|
58cd2e07ba | ||
|
|
a5a6fb590a | ||
|
|
3619993e68 | ||
|
|
88e12c78fa | ||
|
|
5c285b4ac6 | ||
|
|
c6b729c470 | ||
|
|
890014759e | ||
|
|
68c1c43381 | ||
|
|
d0dfcaaad5 | ||
|
|
3cffaddc0a | ||
|
|
bf4cac0c82 | ||
|
|
f680749a00 | ||
|
|
13a67980d9 | ||
|
|
f110d595d2 | ||
|
|
9c8857352b | ||
|
|
c09a1fdba8 | ||
|
|
cdc7033a51 | ||
|
|
fa30c759d7 | ||
|
|
d040be2df0 | ||
|
|
935c831a7f | ||
|
|
867e95eef1 | ||
|
|
2ee04bd1b6 | ||
|
|
75d567e555 | ||
|
|
d8a489971c | ||
|
|
19ce5b5c76 | ||
|
|
7c70ea4d3e | ||
|
|
2784285d47 | ||
|
|
c946a7a1d5 | ||
|
|
3e60b49b8b | ||
|
|
4e7331bbb8 | ||
|
|
b8c7e86223 | ||
|
|
3b925f8674 | ||
|
|
f1f6d41c73 | ||
|
|
29ef1cb1be | ||
|
|
4296085d65 | ||
|
|
c797b09228 | ||
|
|
a870ef0030 | ||
|
|
43ed9e7310 | ||
|
|
bcd27355f9 | ||
|
|
6a14dc69c0 | ||
|
|
ed9acd25f9 | ||
|
|
7b24e66ed3 | ||
|
|
abd3d4b546 | ||
|
|
4040c4240a | ||
|
|
1ee747f3ef | ||
|
|
f88874bec8 | ||
|
|
ed440a2150 | ||
|
|
2fd46b196b | ||
|
|
12dfcaf7e7 | ||
|
|
f4a199f621 | ||
|
|
bb708e0aa3 | ||
|
|
d625740ca4 | ||
|
|
250402e9b9 | ||
|
|
1d2ffe56fb | ||
|
|
d16c0d2887 | ||
|
|
b3555f2f94 | ||
|
|
83a638fc6d | ||
|
|
f1534a710f | ||
|
|
a16845340b | ||
|
|
ffa4725f8e | ||
|
|
7792c66c64 | ||
|
|
1a3985d709 | ||
|
|
4714895c59 | ||
|
|
1e37951701 | ||
|
|
e8be1ad752 | ||
|
|
e316a70b6c | ||
|
|
40a8d21c15 | ||
|
|
28d5ca7ed9 | ||
|
|
110b18545f | ||
|
|
a478605da4 | ||
|
|
f5f1589813 | ||
|
|
0c332b6adb | ||
|
|
ba712ce357 | ||
|
|
2d2395accf | ||
|
|
8634289b7a | ||
|
|
45043fb9a8 | ||
|
|
0449795725 | ||
|
|
a96093f1b7 | ||
|
|
bd4f7691e9 | ||
|
|
e12acbae70 | ||
|
|
48dc4eac10 | ||
|
|
a869c92eee | ||
|
|
4fefd14538 | ||
|
|
c09dbfa47c | ||
|
|
d3c9f66de6 | ||
|
|
01d7694108 | ||
|
|
1425b651d4 | ||
|
|
b1befbeefc | ||
|
|
3a9a84a0b1 | ||
|
|
368284cccc | ||
|
|
ef777f4db9 | ||
|
|
a8e4e8e882 | ||
|
|
cf93760d00 | ||
|
|
dd8b9ff8fb | ||
|
|
bfed03b7b5 | ||
|
|
860f06ec9e | ||
|
|
b58376920f | ||
|
|
4ace075ddf | ||
|
|
dda98a474d | ||
|
|
f1c0df7d87 | ||
|
|
c78e098cb4 | ||
|
|
a3438c4f8d | ||
|
|
92ecf2d5de | ||
|
|
f18b653725 | ||
|
|
5128438cfb | ||
|
|
f29f25822b | ||
|
|
ecfe218840 | ||
|
|
dd33d2b5d0 | ||
|
|
12a8d4e10b | ||
|
|
c5c2fb31b1 | ||
|
|
343b7faf98 | ||
|
|
18aa8bbf60 | ||
|
|
a358d1630f | ||
|
|
01375b321c | ||
|
|
d2739d52e0 | ||
|
|
4668510106 | ||
|
|
ffcd311c90 | ||
|
|
b94a636542 | ||
|
|
a7aec6bfbc | ||
|
|
190ca9eddd | ||
|
|
2cf9eb69eb | ||
|
|
ffcb90da52 | ||
|
|
878b0c9275 | ||
|
|
5505cb0dea | ||
|
|
7ac14dccda | ||
|
|
6cffd0a723 | ||
|
|
220ebf93c7 | ||
|
|
d0681a5592 | ||
|
|
09d167c16d | ||
|
|
477bb45df7 | ||
|
|
e006306036 | ||
|
|
065cbcf0f9 | ||
|
|
7a6b958bbe | ||
|
|
ef6a5b6599 | ||
|
|
cdae919b5e | ||
|
|
12889f4549 | ||
|
|
089d59b691 | ||
|
|
b3e247e9cc | ||
|
|
56392b87f7 | ||
|
|
1b1a4aeb38 | ||
|
|
16147e0c08 | ||
|
|
139317cf1b | ||
|
|
72b94127fb | ||
|
|
1f1fc94d22 | ||
|
|
a574fe026c | ||
|
|
aa82083d30 | ||
|
|
08d5df70c2 | ||
|
|
29b8fa5897 | ||
|
|
e96faf31d4 | ||
|
|
157a73aa99 | ||
|
|
bdd298c8a0 | ||
|
|
3f7dd21186 | ||
|
|
086b708cf7 | ||
|
|
57e0e57f48 | ||
|
|
4b7efbfdc0 | ||
|
|
7dc2653042 | ||
|
|
e428453835 | ||
|
|
f84c8229de | ||
|
|
a73427d68d | ||
|
|
e4456bb236 | ||
|
|
06eadd0c15 | ||
|
|
3c90dfa660 | ||
|
|
ace1b8ee71 | ||
|
|
676356e800 | ||
|
|
f732e54c22 | ||
|
|
cdc2e74f68 | ||
|
|
724f3e872b | ||
|
|
d63e5165eb | ||
|
|
9892c4392e | ||
|
|
5ced1a775c | ||
|
|
761de1318e | ||
|
|
02508512d5 | ||
|
|
6e6105af05 | ||
|
|
d569419e13 | ||
|
|
93f1641803 | ||
|
|
ff52bf93fa | ||
|
|
a039275a0c | ||
|
|
a98d10104d | ||
|
|
8924bc59b1 | ||
|
|
eefe60a9c9 | ||
|
|
fe1cb3d904 | ||
|
|
0448278a78 | ||
|
|
99c0c2ff4c | ||
|
|
b369b734ca | ||
|
|
57150a20fd | ||
|
|
1634d7d531 | ||
|
|
d563de4207 | ||
|
|
5cd4b82ed0 | ||
|
|
5f728d348c | ||
|
|
596c4b6e40 | ||
|
|
36d1e7c44a | ||
|
|
25c17082f2 | ||
|
|
810ccf8e94 | ||
|
|
c8ed0b19f0 | ||
|
|
9e09444c65 | ||
|
|
5923fa0cd5 | ||
|
|
b2d4c5d14b | ||
|
|
0bb9c1d650 | ||
|
|
fbfa3abffd | ||
|
|
b5656aa5dd | ||
|
|
d53fd6a109 | ||
|
|
b0650b926b | ||
|
|
845f6a0a93 | ||
|
|
d8daa83c79 | ||
|
|
7bb0199e83 | ||
|
|
f014dadf06 | ||
|
|
393e54ce91 | ||
|
|
fdf4ad9543 | ||
|
|
5f0d384c9e | ||
|
|
4271700046 | ||
|
|
e153b0ab78 | ||
|
|
26868ae668 | ||
|
|
17c0364eda | ||
|
|
b28ac7af8c | ||
|
|
2dcaa21a44 | ||
|
|
33cc8363f9 | ||
|
|
9b61e1c85c | ||
|
|
6f53fdc02d | ||
|
|
6f850f5a55 | ||
|
|
a482a4b1f4 | ||
|
|
3664e6f96d | ||
|
|
dda8808173 | ||
|
|
63a24c23cc | ||
|
|
1ec3a72f79 | ||
|
|
566285ec0e | ||
|
|
d5ba82338d | ||
|
|
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 |
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: https://signal.org/donate/
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: 🛠️ Bug report
|
||||||
|
about: Let us know that something isn't working as intended
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<!-- This is a bug report template. By following the instructions below and filling out the sections with your information, you will help the developers get all the necessary data to fix your issue.
|
<!-- This is a bug report template. By following the instructions below and filling out the sections with your information, you will help the developers get all the necessary data to fix your issue.
|
||||||
You can also preview your report before submitting it. You may remove sections that aren't relevant to your particular case.
|
You can also preview your report before submitting it. You may remove sections that aren't relevant to your particular case.
|
||||||
|
|
||||||
20
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 📃Support Center
|
||||||
|
url: https://support.signal.org/
|
||||||
|
about: Find answers to many common questions.
|
||||||
|
- name: ✨ Feature request
|
||||||
|
url: https://community.signalusers.org/c/feature-requests/
|
||||||
|
about: Missing something in Signal? Let us know.
|
||||||
|
- name: 💬 Community support
|
||||||
|
url: https://community.signalusers.org/c/support/
|
||||||
|
about: Feel free to ask anything.
|
||||||
|
- name: 📖 Developer documentation
|
||||||
|
url: https://signal.org/docs/
|
||||||
|
about: Official Signal developer documentation.
|
||||||
|
- name: 📚 Translation feedback.
|
||||||
|
url: https://community.signalusers.org/c/translation-feedback/
|
||||||
|
about: Share feedback on translations.
|
||||||
|
- name: ❓ Other issue?
|
||||||
|
url: https://community.signalusers.org/
|
||||||
|
about: Search on the community forums.
|
||||||
6
.github/workflows/android.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- '4.**'
|
- '4.**'
|
||||||
|
- '5.**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -13,16 +14,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- 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
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
|
|||||||
18
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Reproducible Build Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 5 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Build image
|
||||||
|
run: cd reproducible-builds && docker build -t signal-android . && cd ..
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: docker run --rm -v $(pwd):/project -w /project signal-android ./gradlew clean assembleRelease
|
||||||
4
.gitignore
vendored
@@ -1,6 +1,8 @@
|
|||||||
.classpath
|
.classpath
|
||||||
captures/
|
captures/
|
||||||
project.properties
|
project.properties
|
||||||
|
keystore.debug.properties
|
||||||
|
keystore.staging.properties
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
||||||
bin/
|
bin/
|
||||||
@@ -23,5 +25,5 @@ ffpr
|
|||||||
test/androidTestEspresso/res/values/arrays.xml
|
test/androidTestEspresso/res/values/arrays.xml
|
||||||
obj/
|
obj/
|
||||||
jni/libspeex/.deps/
|
jni/libspeex/.deps/
|
||||||
*.sh
|
|
||||||
pkcs11.password
|
pkcs11.password
|
||||||
|
dev.keystore
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ There are several other ways to get involved:
|
|||||||
* Try to reproduce issues and help with troubleshooting.
|
* Try to reproduce issues and help with troubleshooting.
|
||||||
* Discover solutions to open issues and post any relevant findings.
|
* Discover solutions to open issues and post any relevant findings.
|
||||||
* Test other people's pull requests.
|
* Test other people's pull requests.
|
||||||
* Contribute to Signal via the [Freedom of the Press Foundation's donation page](https://freedom.press/crowdfunding/signal/).
|
* [Donate to Signal.](https://signal.org/donate/)
|
||||||
* Share Signal with your friends and family.
|
* Share Signal with your friends and family.
|
||||||
|
|
||||||
Signal is made for you. Thank you for your feedback and support.
|
Signal is made for you. Thank you for your feedback and support.
|
||||||
|
|||||||
25
Dockerfile
@@ -1,25 +0,0 @@
|
|||||||
FROM ubuntu:17.10
|
|
||||||
|
|
||||||
RUN dpkg --add-architecture i386 && \
|
|
||||||
apt-get update -y && \
|
|
||||||
apt-get install -y software-properties-common && \
|
|
||||||
apt-get update -y && \
|
|
||||||
apt-get install -y libc6:i386=2.26-0ubuntu2.1 libncurses5:i386=6.0+20160625-1ubuntu1 libstdc++6:i386=7.2.0-8ubuntu3.2 lib32z1=1:1.2.11.dfsg-0ubuntu2 wget openjdk-8-jdk=8u171-b11-0ubuntu0.17.10.1 git unzip opensc pcscd && \
|
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
|
||||||
apt-get autoremove -y && \
|
|
||||||
apt-get clean
|
|
||||||
|
|
||||||
ENV ANDROID_SDK_FILENAME android-sdk_r24.4.1-linux.tgz
|
|
||||||
ENV ANDROID_SDK_URL https://dl.google.com/android/${ANDROID_SDK_FILENAME}
|
|
||||||
ENV ANDROID_API_LEVELS android-28
|
|
||||||
ENV ANDROID_BUILD_TOOLS_VERSION 28.0.3
|
|
||||||
ENV ANDROID_HOME /usr/local/android-sdk-linux
|
|
||||||
ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
|
||||||
RUN cd /usr/local/ && \
|
|
||||||
wget -q ${ANDROID_SDK_URL} && \
|
|
||||||
tar -xzf ${ANDROID_SDK_FILENAME} && \
|
|
||||||
rm ${ANDROID_SDK_FILENAME}
|
|
||||||
RUN echo y | android update sdk --no-ui -a --filter ${ANDROID_API_LEVELS}
|
|
||||||
RUN echo y | android update sdk --no-ui -a --filter extra-android-m2repository,extra-android-support,extra-google-google_play_services,extra-google-m2repository
|
|
||||||
RUN echo y | android update sdk --no-ui -a --filter tools,platform-tools,build-tools-${ANDROID_BUILD_TOOLS_VERSION}
|
|
||||||
RUN rm -rf ${ANDROID_HOME}/tools && unzip ${ANDROID_HOME}/temp/*.zip -d ${ANDROID_HOME}
|
|
||||||
249
app/build.gradle
@@ -2,25 +2,6 @@ import org.signal.signing.ApkSignerUtil
|
|||||||
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
maven {
|
|
||||||
url "https://repo1.maven.org/maven2"
|
|
||||||
}
|
|
||||||
jcenter {
|
|
||||||
content {
|
|
||||||
includeVersion 'org.jetbrains.trove4j', 'trove4j', '20160824'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:3.6.3'
|
|
||||||
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0'
|
|
||||||
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'com.google.protobuf'
|
apply plugin: 'com.google.protobuf'
|
||||||
apply plugin: 'androidx.navigation.safeargs'
|
apply plugin: 'androidx.navigation.safeargs'
|
||||||
@@ -80,38 +61,54 @@ protobuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 679
|
def canonicalVersionCode = 791
|
||||||
def canonicalVersionName = "4.67.2"
|
def canonicalVersionName = "5.4.5"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 100
|
||||||
def abiPostFix = ['universal' : 0,
|
def abiPostFix = ['universal' : 0,
|
||||||
'armeabi-v7a' : 1,
|
'armeabi-v7a' : 1,
|
||||||
'arm64-v8a' : 2,
|
'arm64-v8a' : 2,
|
||||||
'x86' : 3,
|
'x86' : 3,
|
||||||
'x86_64' : 4]
|
'x86_64' : 4]
|
||||||
|
|
||||||
|
def keystores = [ 'debug' : loadKeystoreProperties('keystore.debug.properties') ]
|
||||||
|
|
||||||
android {
|
android {
|
||||||
flavorDimensions "none"
|
buildToolsVersion BUILD_TOOL_VERSION
|
||||||
compileSdkVersion 28
|
compileSdkVersion COMPILE_SDK
|
||||||
buildToolsVersion '28.0.3'
|
|
||||||
|
flavorDimensions 'distribution', 'environment'
|
||||||
useLibrary 'org.apache.http.legacy'
|
useLibrary 'org.apache.http.legacy'
|
||||||
|
|
||||||
dexOptions {
|
dexOptions {
|
||||||
javaMaxHeapSize "4g"
|
javaMaxHeapSize "4g"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (keystores.debug != null) {
|
||||||
|
debug {
|
||||||
|
storeFile file("${project.rootDir}/${keystores.debug.storeFile}")
|
||||||
|
storePassword keystores.debug.storePassword
|
||||||
|
keyAlias keystores.debug.keyAlias
|
||||||
|
keyPassword keystores.debug.keyPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode canonicalVersionCode * postFixSize
|
versionCode canonicalVersionCode * postFixSize
|
||||||
versionName canonicalVersionName
|
versionName canonicalVersionName
|
||||||
|
|
||||||
minSdkVersion 19
|
minSdkVersion MINIMUM_SDK
|
||||||
targetSdkVersion 28
|
targetSdkVersion TARGET_SDK
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
project.ext.set("archivesBaseName", "Signal");
|
project.ext.set("archivesBaseName", "Signal");
|
||||||
|
|
||||||
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
|
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
|
||||||
|
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
|
||||||
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\""
|
||||||
@@ -119,12 +116,15 @@ android {
|
|||||||
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", "SIGNAL_SFU_URL", "\"https://sfu.voip.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", "SIGNAL_AGENT", "\"OWA\""
|
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
||||||
buildConfigField "String", "CDS_MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
||||||
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
|
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\"," +
|
||||||
buildConfigField "String", "KBS_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\""
|
"\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\", " +
|
||||||
|
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")";
|
||||||
|
buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]"
|
||||||
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", "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('", "') + '"}'
|
||||||
@@ -149,8 +149,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JAVA_VERSION
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JAVA_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
@@ -165,6 +165,10 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
|
if (keystores['debug'] != null) {
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
isDefault true
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||||
'proguard/proguard-firebase-messaging.pro',
|
'proguard/proguard-firebase-messaging.pro',
|
||||||
@@ -188,8 +192,57 @@ android {
|
|||||||
testProguardFiles 'proguard/proguard-automation.pro',
|
testProguardFiles 'proguard/proguard-automation.pro',
|
||||||
'proguard/proguard.cfg'
|
'proguard/proguard.cfg'
|
||||||
}
|
}
|
||||||
staging {
|
flipper {
|
||||||
initWith debug
|
initWith debug
|
||||||
|
isDefault false
|
||||||
|
minifyEnabled false
|
||||||
|
matchingFallbacks = ['debug']
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles = buildTypes.debug.proguardFiles
|
||||||
|
}
|
||||||
|
perf {
|
||||||
|
initWith debug
|
||||||
|
isDefault false
|
||||||
|
debuggable false
|
||||||
|
matchingFallbacks = ['debug']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
play {
|
||||||
|
dimension 'distribution'
|
||||||
|
isDefault true
|
||||||
|
ext.websiteUpdateUrl = "null"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
website {
|
||||||
|
dimension 'distribution'
|
||||||
|
ext.websiteUpdateUrl = "https://updates.signal.org/android"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
|
||||||
|
}
|
||||||
|
|
||||||
|
internal {
|
||||||
|
dimension 'distribution'
|
||||||
|
ext.websiteUpdateUrl = "null"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
prod {
|
||||||
|
dimension 'environment'
|
||||||
|
|
||||||
|
isDefault true
|
||||||
|
}
|
||||||
|
|
||||||
|
staging {
|
||||||
|
dimension 'environment'
|
||||||
|
|
||||||
|
applicationIdSuffix ".staging"
|
||||||
|
|
||||||
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\""
|
||||||
@@ -197,35 +250,14 @@ android {
|
|||||||
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-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", "CDS_MRENCLAVE", "\"b657cad56d518827b0938949bb1e5727a9a4db358dd6a88e55e710a89ffa50bd\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
||||||
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\""
|
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\", " +
|
||||||
|
"\"038c40bbbacdc873caa81ac793bb75afde6dfe436a99ab1f15e3f0cbb7434ced\", " +
|
||||||
|
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")"
|
||||||
|
buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]"
|
||||||
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=\""
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=\""
|
||||||
}
|
}
|
||||||
flipper {
|
|
||||||
initWith debug
|
|
||||||
minifyEnabled false
|
|
||||||
}
|
|
||||||
release {
|
|
||||||
minifyEnabled true
|
|
||||||
proguardFiles = buildTypes.debug.proguardFiles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
productFlavors {
|
|
||||||
play {
|
|
||||||
dimension "none"
|
|
||||||
ext.websiteUpdateUrl = "null"
|
|
||||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
|
||||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
|
||||||
}
|
|
||||||
|
|
||||||
website {
|
|
||||||
dimension "none"
|
|
||||||
ext.websiteUpdateUrl = "https://updates.signal.org/android"
|
|
||||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
|
|
||||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
@@ -256,33 +288,30 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
lintChecks project(':lintchecks')
|
lintChecks project(':lintchecks')
|
||||||
|
|
||||||
implementation('androidx.appcompat:appcompat:1.1.0-beta01') {
|
implementation ('androidx.appcompat:appcompat:1.2.0') {
|
||||||
force = true
|
force = true
|
||||||
}
|
}
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0'
|
implementation 'com.google.android.material:material:1.2.1'
|
||||||
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.preference:preference:1.0.0'
|
implementation 'androidx.preference:preference:1.0.0'
|
||||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.navigation:navigation-fragment:2.1.0'
|
implementation 'androidx.navigation:navigation-fragment:2.1.0'
|
||||||
implementation 'androidx.navigation:navigation-ui:2.1.0'
|
implementation 'androidx.navigation:navigation-ui:2.1.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions: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-viewmodel-savedstate:1.0.0-alpha05'
|
||||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
|
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
|
||||||
implementation "androidx.camera:camera-core:1.0.0-beta01"
|
implementation "androidx.camera:camera-core:1.0.0-beta11"
|
||||||
implementation "androidx.camera:camera-camera2:1.0.0-beta01"
|
implementation "androidx.camera:camera-camera2:1.0.0-beta11"
|
||||||
implementation "androidx.camera:camera-lifecycle:1.0.0-beta01"
|
implementation "androidx.camera:camera-lifecycle:1.0.0-beta11"
|
||||||
|
implementation "androidx.camera:camera-view:1.0.0-alpha18"
|
||||||
implementation "androidx.concurrent:concurrent-futures:1.0.0"
|
implementation "androidx.concurrent:concurrent-futures:1.0.0"
|
||||||
implementation "androidx.autofill:autofill: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') {
|
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
@@ -295,16 +324,22 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
||||||
|
implementation 'com.google.android.exoplayer:extension-mediasession:2.9.1'
|
||||||
|
|
||||||
implementation 'org.conscrypt:conscrypt-android:2.0.0'
|
implementation 'org.conscrypt:conscrypt-android:2.0.0'
|
||||||
implementation 'org.signal:aesgcmprovider:0.0.3'
|
implementation 'org.signal:aesgcmprovider:0.0.3'
|
||||||
|
|
||||||
implementation project(':libsignal-service')
|
implementation project(':libsignal-service')
|
||||||
implementation 'org.signal:zkgroup-android:0.7.0'
|
implementation project(':paging')
|
||||||
|
implementation project(':core-util')
|
||||||
|
implementation project(':video')
|
||||||
|
|
||||||
|
implementation 'org.signal:zkgroup-android:0.7.0'
|
||||||
|
implementation 'org.whispersystems:signal-client-android:0.1.5'
|
||||||
|
implementation 'com.google.protobuf:protobuf-javalite:3.10.0'
|
||||||
implementation 'org.signal:argon2:13.1@aar'
|
implementation 'org.signal:argon2:13.1@aar'
|
||||||
|
|
||||||
implementation 'org.signal:ringrtc-android:2.3.1'
|
implementation 'org.signal:ringrtc-android:2.9.2'
|
||||||
|
|
||||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||||
@@ -342,7 +377,7 @@ dependencies {
|
|||||||
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'com.airbnb.android:lottie:3.0.7'
|
implementation 'com.airbnb.android:lottie:3.6.0'
|
||||||
|
|
||||||
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
||||||
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
||||||
@@ -358,27 +393,27 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.assertj:assertj-core:3.11.1'
|
testImplementation 'org.assertj:assertj-core:3.11.1'
|
||||||
testImplementation 'org.mockito:mockito-core:1.9.5'
|
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||||
testImplementation 'org.powermock:powermock-api-mockito:1.6.1'
|
testImplementation 'org.powermock:powermock-api-mockito2:1.7.4'
|
||||||
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
|
testImplementation 'org.powermock:powermock-module-junit4:1.7.4'
|
||||||
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
|
testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.4'
|
||||||
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
|
testImplementation 'org.powermock:powermock-classloading-xstream:1.7.4'
|
||||||
|
|
||||||
testImplementation 'androidx.test:core:1.2.0'
|
testImplementation 'androidx.test:core:1.2.0'
|
||||||
testImplementation ('org.robolectric:robolectric:4.2') {
|
testImplementation ('org.robolectric:robolectric:4.4') {
|
||||||
exclude group: 'com.google.protobuf', module: 'protobuf-java'
|
exclude group: 'com.google.protobuf', module: 'protobuf-java'
|
||||||
}
|
}
|
||||||
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
||||||
|
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyVerification {
|
dependencyVerification {
|
||||||
configuration = '(play|website)(Debug|Release)RuntimeClasspath'
|
configuration = '(play|website)(Prod|Staging)(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");
|
||||||
@@ -422,28 +457,24 @@ def signProductionRelease = { variant ->
|
|||||||
|
|
||||||
task signProductionPlayRelease {
|
task signProductionPlayRelease {
|
||||||
doLast {
|
doLast {
|
||||||
signProductionRelease(android.applicationVariants.find { (it.name == 'playRelease') })
|
signProductionRelease(android.applicationVariants.find { (it.name == 'playProdRelease') })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task signProductionInternalRelease {
|
||||||
|
doLast {
|
||||||
|
signProductionRelease(android.applicationVariants.find { (it.name == 'internalProdRelease') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task signProductionWebsiteRelease {
|
task signProductionWebsiteRelease {
|
||||||
doLast {
|
doLast {
|
||||||
def variant = android.applicationVariants.find { (it.name == 'websiteRelease') }
|
def variant = android.applicationVariants.find { (it.name == 'websiteProdRelease') }
|
||||||
File signedRelease = signProductionRelease(variant).find { it.name.contains('universal') }
|
File signedRelease = signProductionRelease(variant).find { it.name.contains('universal') }
|
||||||
assembleWebsiteDescriptor(variant, signedRelease)
|
assembleWebsiteDescriptor(variant, signedRelease)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.whenTaskAdded { task ->
|
|
||||||
if (task.name.equals("assemblePlayRelease")) {
|
|
||||||
task.finalizedBy signProductionPlayRelease
|
|
||||||
}
|
|
||||||
|
|
||||||
if (task.name.equals("assembleWebsiteRelease")) {
|
|
||||||
task.finalizedBy signProductionWebsiteRelease
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getLastCommitTimestamp() {
|
def getLastCommitTimestamp() {
|
||||||
new ByteArrayOutputStream().withStream { os ->
|
new ByteArrayOutputStream().withStream { os ->
|
||||||
def result = exec {
|
def result = exec {
|
||||||
@@ -455,3 +486,33 @@ def getLastCommitTimestamp() {
|
|||||||
return os.toString() + "000"
|
return os.toString() + "000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getGitHash() {
|
||||||
|
def stdout = new ByteArrayOutputStream()
|
||||||
|
exec {
|
||||||
|
commandLine 'git', 'rev-parse', '--short', 'HEAD'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
return stdout.toString().trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Test) {
|
||||||
|
testLogging {
|
||||||
|
events "failed"
|
||||||
|
exceptionFormat "full"
|
||||||
|
showCauses true
|
||||||
|
showExceptions true
|
||||||
|
showStackTraces true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def loadKeystoreProperties(filename) {
|
||||||
|
def keystorePropertiesFile = file("${project.rootDir}/${filename}")
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
def keystoreProperties = new Properties()
|
||||||
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
|
return keystoreProperties;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,10 +11,13 @@
|
|||||||
<!-- L10N warnings -->
|
<!-- L10N warnings -->
|
||||||
<issue id="MissingTranslation" severity="ignore" />
|
<issue id="MissingTranslation" severity="ignore" />
|
||||||
<issue id="MissingQuantity" severity="warning" />
|
<issue id="MissingQuantity" severity="warning" />
|
||||||
|
<issue id="MissingDefaultResource" severity="error">
|
||||||
|
<ignore path="*/res/values-*/strings.xml" /> <!-- Ignore for non-English, excludeNonTranslatables task will remove these -->
|
||||||
|
</issue>
|
||||||
<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" >
|
<issue id="TypographyDashes" severity="error" >
|
||||||
<ignore path="*/res/values-*" /> <!-- Ignore for non-English -->
|
<ignore path="*/res/values-*/strings.xml" /> <!-- Ignore for non-English -->
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue id="CanvasSize" severity="error" />
|
<issue id="CanvasSize" severity="error" />
|
||||||
|
|||||||
@@ -5,5 +5,12 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".FlipperApplicationContext"
|
android:name=".FlipperApplicationContext"
|
||||||
tools:replace="android:name"/>
|
tools:replace="android:name">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
|
||||||
|
android:exported="true" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -14,14 +15,17 @@ import net.sqlcipher.DatabaseUtils;
|
|||||||
import net.sqlcipher.database.SQLiteDatabase;
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
import net.sqlcipher.database.SQLiteStatement;
|
import net.sqlcipher.database.SQLiteStatement;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver}
|
* A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver}
|
||||||
@@ -29,13 +33,31 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdapter.Descriptor> {
|
public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdapter.Descriptor> {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(FlipperSqlCipherAdapter.class);
|
||||||
|
|
||||||
public FlipperSqlCipherAdapter(Context context) {
|
public FlipperSqlCipherAdapter(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Descriptor> getDatabases() {
|
public List<Descriptor> getDatabases() {
|
||||||
return Collections.singletonList(new Descriptor(DatabaseFactory.getRawDatabase(getContext())));
|
try {
|
||||||
|
Field databaseHelperField = DatabaseFactory.class.getDeclaredField("databaseHelper");
|
||||||
|
databaseHelperField.setAccessible(true);
|
||||||
|
|
||||||
|
SignalDatabase mainOpenHelper = Objects.requireNonNull((SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext())));
|
||||||
|
SignalDatabase keyValueOpenHelper = KeyValueDatabase.getInstance((Application) getContext());
|
||||||
|
SignalDatabase megaphoneOpenHelper = MegaphoneDatabase.getInstance((Application) getContext());
|
||||||
|
SignalDatabase jobManagerOpenHelper = JobDatabase.getInstance((Application) getContext());
|
||||||
|
|
||||||
|
return Arrays.asList(new Descriptor(mainOpenHelper),
|
||||||
|
new Descriptor(keyValueOpenHelper),
|
||||||
|
new Descriptor(megaphoneOpenHelper),
|
||||||
|
new Descriptor(jobManagerOpenHelper));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.i(TAG, "Unable to use reflection to access raw database.", e);
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -223,9 +245,9 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class Descriptor implements DatabaseDescriptor {
|
static class Descriptor implements DatabaseDescriptor {
|
||||||
private final SQLCipherOpenHelper sqlCipherOpenHelper;
|
private final SignalDatabase sqlCipherOpenHelper;
|
||||||
|
|
||||||
Descriptor(@NonNull SQLCipherOpenHelper sqlCipherOpenHelper) {
|
Descriptor(@NonNull SignalDatabase sqlCipherOpenHelper) {
|
||||||
this.sqlCipherOpenHelper = sqlCipherOpenHelper;
|
this.sqlCipherOpenHelper = sqlCipherOpenHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,11 +257,11 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull SQLiteDatabase getReadable() {
|
public @NonNull SQLiteDatabase getReadable() {
|
||||||
return sqlCipherOpenHelper.getReadableDatabase();
|
return sqlCipherOpenHelper.getSqlCipherDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull SQLiteDatabase getWritable() {
|
public @NonNull SQLiteDatabase getWritable() {
|
||||||
return sqlCipherOpenHelper.getWritableDatabase();
|
return sqlCipherOpenHelper.getSqlCipherDatabase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Signal (Flipper)</string>
|
|
||||||
</resources>
|
|
||||||
5
app/src/internal/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/core_red_shade"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.thoughtcrime.securesms">
|
package="org.thoughtcrime.securesms">
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle" />
|
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle,androidx.camera.view" />
|
||||||
|
|
||||||
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
|
<permission android:name="${applicationId}.ACCESS_SECRETS"
|
||||||
android:label="Access to TextSecure Secrets"
|
android:label="Access to TextSecure Secrets"
|
||||||
android:protectionLevel="signature" />
|
android:protectionLevel="signature" />
|
||||||
|
|
||||||
@@ -35,8 +35,10 @@
|
|||||||
<uses-permission android:name="android.permission.SEND_SMS"/>
|
<uses-permission android:name="android.permission.SEND_SMS"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="28" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
@@ -62,7 +64,6 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
<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' -->
|
<!-- So we can add a TextSecure 'Account' -->
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
@@ -113,8 +114,8 @@
|
|||||||
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
||||||
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
|
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
|
||||||
|
|
||||||
<activity android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
|
<activity android:name=".WebRtcCallActivity"
|
||||||
android:theme="@style/TextSecure.LightTheme.WebRTCCall"
|
android:theme="@style/TextSecure.DarkTheme.WebRTCCall"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
android:value=".MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".PromptMmsActivity"
|
<activity android:name=".PromptMmsActivity"
|
||||||
@@ -157,12 +158,15 @@
|
|||||||
<activity android:name=".preferences.MmsPreferencesActivity"
|
<activity android:name=".preferences.MmsPreferencesActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity android:name=".sharing.ShareActivity"
|
<activity android:name=".sharing.ShareActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:noHistory="true"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -230,6 +234,38 @@
|
|||||||
|
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity android:name=".deeplinks.DeepLinkEntryActivity"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:theme="@style/Signal.Transparent">
|
||||||
|
|
||||||
|
<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="signal.group" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter android:autoVerify="true"
|
||||||
|
tools:targetApi="23">
|
||||||
|
<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.group"/>
|
||||||
|
</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.tube" />
|
||||||
|
<data android:scheme="sgnl"
|
||||||
|
android:host="signal.tube" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".conversation.ConversationActivity"
|
<activity android:name=".conversation.ConversationActivity"
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
@@ -240,6 +276,11 @@
|
|||||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
android:value="org.thoughtcrime.securesms.MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".conversation.BubbleConversationActivity"
|
||||||
|
android:theme="@style/Signal.DayNight"
|
||||||
|
android:allowEmbedded="true"
|
||||||
|
android:resizeableActivity="true" />
|
||||||
|
|
||||||
<activity android:name=".longmessage.LongMessageActivity" />
|
<activity android:name=".longmessage.LongMessageActivity" />
|
||||||
|
|
||||||
<activity android:name=".conversation.ConversationPopupActivity"
|
<activity android:name=".conversation.ConversationPopupActivity"
|
||||||
@@ -251,13 +292,12 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
<activity android:name=".messagedetails.MessageDetailsActivity"
|
<activity android:name=".messagedetails.MessageDetailsActivity"
|
||||||
android:label="@string/AndroidManifest__message_details"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".groups.ui.pendingmemberinvites.PendingMemberInvitesActivity"
|
<activity android:name=".groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
<activity android:name=".groups.ui.managegroup.ManageGroupActivity"
|
<activity android:name=".groups.ui.managegroup.ManageGroupActivity"
|
||||||
@@ -319,13 +359,24 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".ApplicationPreferencesActivity"
|
<activity android:name=".ApplicationPreferencesActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".wallpaper.ChatWallpaperActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".wallpaper.ChatWallpaperPreviewActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden">
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".registration.RegistrationNavigationActivity"
|
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
@@ -350,7 +401,6 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".logsubmit.SubmitDebugLogActivity"
|
<activity android:name=".logsubmit.SubmitDebugLogActivity"
|
||||||
android:label="@string/AndroidManifest__log_submit"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -421,7 +471,7 @@
|
|||||||
android:theme="@style/TextSecure.FullScreenMedia"
|
android:theme="@style/TextSecure.FullScreenMedia"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".BlockedContactsActivity"
|
<activity android:name=".blocked.BlockedUsersActivity"
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -433,6 +483,10 @@
|
|||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
|
||||||
|
<activity android:name=".profiles.manage.ManageProfileActivity"
|
||||||
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
|
||||||
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
@@ -443,17 +497,14 @@
|
|||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".ClearProfileAvatarActivity"
|
<activity android:name=".ClearAvatarPromptActivity"
|
||||||
android:theme="@style/Theme.AppCompat.Dialog.Alert"
|
android:theme="@style/Theme.AppCompat.Dialog.Alert"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:icon="@drawable/clear_profile_avatar"
|
||||||
android:icon="@drawable/clear_profile_avatar"
|
android:label="@string/AndroidManifest_remove_photo"
|
||||||
android:label="@string/AndroidManifest_remove_photo">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<intent-filter>
|
<activity android:name=".contacts.TurnOffContactJoinedNotificationsActivity"
|
||||||
<action android:name="org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO"/>
|
android:theme="@style/Theme.AppCompat.Dialog.Alert" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".messagerequests.MessageRequestMegaphoneActivity"
|
<activity android:name=".messagerequests.MessageRequestMegaphoneActivity"
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
@@ -485,7 +536,6 @@
|
|||||||
|
|
||||||
<activity android:name=".MainActivity"
|
<activity android:name=".MainActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
<activity android:name=".pin.PinRestoreActivity"
|
<activity android:name=".pin.PinRestoreActivity"
|
||||||
@@ -507,11 +557,37 @@
|
|||||||
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
|
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
<activity android:name=".megaphone.ClientDeprecatedActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||||
|
android:launchMode="singleTask" />
|
||||||
|
|
||||||
|
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/TextSecure.FullScreenMedia" />
|
||||||
|
|
||||||
|
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.Signal.WallpaperCropper" />
|
||||||
|
|
||||||
|
<service android:enabled="true" android:name=".service.WebRtcCallService"/>
|
||||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||||
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
|
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
|
||||||
|
|
||||||
|
<service android:name=".components.voice.VoiceNotePlaybackService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service android:name=".service.QuickResponseService"
|
<service android:name=".service.QuickResponseService"
|
||||||
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
||||||
android:exported="true" >
|
android:exported="true" >
|
||||||
@@ -624,18 +700,25 @@
|
|||||||
|
|
||||||
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
||||||
|
|
||||||
|
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
|
||||||
|
|
||||||
<provider android:name=".providers.PartProvider"
|
<provider android:name=".providers.PartProvider"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:authorities="org.thoughtcrime.provider.securesms" />
|
android:authorities="${applicationId}.part" />
|
||||||
|
|
||||||
|
<provider android:name=".providers.BlobContentProvider"
|
||||||
|
android:authorities="${applicationId}.blob"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true" />
|
||||||
|
|
||||||
<provider android:name=".providers.MmsBodyProvider"
|
<provider android:name=".providers.MmsBodyProvider"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:authorities="org.thoughtcrime.provider.securesms.mms" />
|
android:authorities="${applicationId}.mms" />
|
||||||
|
|
||||||
<provider android:name="androidx.core.content.FileProvider"
|
<provider android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="org.thoughtcrime.securesms.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
|
|
||||||
@@ -644,23 +727,23 @@
|
|||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Conversation"
|
<provider android:name=".database.DatabaseContentProviders$Conversation"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.conversation"
|
android:authorities="${applicationId}.database.conversation"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$ConversationList"
|
<provider android:name=".database.DatabaseContentProviders$ConversationList"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.conversationlist"
|
android:authorities="${applicationId}.database.conversationlist"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Attachment"
|
<provider android:name=".database.DatabaseContentProviders$Attachment"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.attachment"
|
android:authorities="${applicationId}.database.attachment"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Sticker"
|
<provider android:name=".database.DatabaseContentProviders$Sticker"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.sticker"
|
android:authorities="${applicationId}.database.sticker"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$StickerPack"
|
<provider android:name=".database.DatabaseContentProviders$StickerPack"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.stickerpack"
|
android:authorities="${applicationId}.database.stickerpack"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver android:name=".service.BootReceiver">
|
<receiver android:name=".service.BootReceiver">
|
||||||
@@ -688,6 +771,13 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.LocalBackupListener">
|
<receiver android:name=".service.LocalBackupListener">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
@@ -753,12 +843,5 @@
|
|||||||
|
|
||||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
<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>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 421 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 365 KiB |
|
Before Width: | Height: | Size: 395 KiB After Width: | Height: | Size: 434 KiB |
|
Before Width: | Height: | Size: 622 KiB After Width: | Height: | Size: 664 KiB |
|
Before Width: | Height: | Size: 599 KiB After Width: | Height: | Size: 608 KiB |
|
Before Width: | Height: | Size: 559 KiB After Width: | Height: | Size: 552 KiB |
|
Before Width: | Height: | Size: 643 KiB After Width: | Height: | Size: 631 KiB |
|
Before Width: | Height: | Size: 647 KiB After Width: | Height: | Size: 653 KiB |
|
Before Width: | Height: | Size: 602 KiB After Width: | Height: | Size: 652 KiB |
|
Before Width: | Height: | Size: 589 KiB After Width: | Height: | Size: 531 KiB |
|
Before Width: | Height: | Size: 531 KiB After Width: | Height: | Size: 685 KiB |
BIN
app/src/main/assets/emoji/People_7.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 589 KiB After Width: | Height: | Size: 603 KiB |
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 391 KiB |
BIN
app/src/main/assets/sounds/state-change_confirm-down.ogg
Executable file
BIN
app/src/main/assets/sounds/state-change_confirm-up.ogg
Executable file
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.mediasend.camerax;
|
package androidx.camera.view;
|
||||||
|
|
||||||
import android.Manifest.permission;
|
import android.Manifest.permission;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@@ -45,43 +45,44 @@ import androidx.annotation.RestrictTo;
|
|||||||
import androidx.annotation.RestrictTo.Scope;
|
import androidx.annotation.RestrictTo.Scope;
|
||||||
import androidx.camera.core.Camera;
|
import androidx.camera.core.Camera;
|
||||||
import androidx.camera.core.CameraSelector;
|
import androidx.camera.core.CameraSelector;
|
||||||
import androidx.camera.core.DisplayOrientedMeteringPointFactory;
|
|
||||||
import androidx.camera.core.FocusMeteringAction;
|
import androidx.camera.core.FocusMeteringAction;
|
||||||
import androidx.camera.core.FocusMeteringResult;
|
import androidx.camera.core.FocusMeteringResult;
|
||||||
import androidx.camera.core.ImageCapture;
|
import androidx.camera.core.ImageCapture;
|
||||||
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
|
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
|
||||||
|
import androidx.camera.core.ImageCapture.OnImageSavedCallback;
|
||||||
import androidx.camera.core.ImageProxy;
|
import androidx.camera.core.ImageProxy;
|
||||||
|
import androidx.camera.core.Logger;
|
||||||
import androidx.camera.core.MeteringPoint;
|
import androidx.camera.core.MeteringPoint;
|
||||||
|
import androidx.camera.core.MeteringPointFactory;
|
||||||
|
import androidx.camera.core.VideoCapture;
|
||||||
|
import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
|
||||||
import androidx.camera.core.impl.LensFacingConverter;
|
import androidx.camera.core.impl.LensFacingConverter;
|
||||||
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
|
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
|
||||||
import androidx.camera.core.impl.utils.futures.FutureCallback;
|
import androidx.camera.core.impl.utils.futures.FutureCallback;
|
||||||
import androidx.camera.core.impl.utils.futures.Futures;
|
import androidx.camera.core.impl.utils.futures.Futures;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import java.io.File;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link View} that displays a preview of the camera with methods {@link
|
* A {@link View} that displays a preview of the camera with methods {@link
|
||||||
* #takePicture(Executor, OnImageCapturedCallback)},
|
* #takePicture(Executor, OnImageCapturedCallback)},
|
||||||
* {@link #startRecording(FileDescriptor, Executor, VideoCapture.OnVideoSavedCallback)} and {@link #stopRecording()}.
|
* {@link #takePicture(ImageCapture.OutputFileOptions, Executor, OnImageSavedCallback)},
|
||||||
|
* {@link #startRecording(File , Executor , OnVideoSavedCallback callback)}
|
||||||
|
* and {@link #stopRecording()}.
|
||||||
*
|
*
|
||||||
* <p>Because the Camera is a limited resource and consumes a high amount of power, CameraView must
|
* <p>Because the Camera is a limited resource and consumes a high amount of power, CameraView must
|
||||||
* be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
|
* be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
|
||||||
* LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
|
* LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
|
||||||
*/
|
*/
|
||||||
// Begin Signal Custom Code Block
|
|
||||||
@RequiresApi(21)
|
@RequiresApi(21)
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
// End Signal Custom Code Block
|
public final class SignalCameraView extends FrameLayout {
|
||||||
public final class CameraXView extends FrameLayout {
|
static final String TAG = SignalCameraView.class.getSimpleName();
|
||||||
static final String TAG = CameraXView.class.getSimpleName();
|
|
||||||
static final boolean DEBUG = false;
|
|
||||||
|
|
||||||
static final int INDEFINITE_VIDEO_DURATION = -1;
|
static final int INDEFINITE_VIDEO_DURATION = -1;
|
||||||
static final int INDEFINITE_VIDEO_SIZE = -1;
|
static final int INDEFINITE_VIDEO_SIZE = -1;
|
||||||
@@ -107,7 +108,7 @@ public final class CameraXView extends FrameLayout {
|
|||||||
// For pinch-to-zoom
|
// For pinch-to-zoom
|
||||||
private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
|
private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
|
||||||
private boolean mIsPinchToZoomEnabled = true;
|
private boolean mIsPinchToZoomEnabled = true;
|
||||||
CameraXModule mCameraModule;
|
SignalCameraXModule mCameraModule;
|
||||||
private final DisplayManager.DisplayListener mDisplayListener =
|
private final DisplayManager.DisplayListener mDisplayListener =
|
||||||
new DisplayListener() {
|
new DisplayListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -124,26 +125,25 @@ public final class CameraXView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
private PreviewView mPreviewView;
|
private PreviewView mPreviewView;
|
||||||
private ScaleType mScaleType = ScaleType.CENTER_CROP;
|
|
||||||
// For accessibility event
|
// For accessibility event
|
||||||
private MotionEvent mUpEvent;
|
private MotionEvent mUpEvent;
|
||||||
|
|
||||||
public CameraXView(@NonNull Context context) {
|
public SignalCameraView(@NonNull Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CameraXView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
this(context, attrs, 0);
|
this(context, attrs, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CameraXView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
|
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
init(context, attrs);
|
init(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(21)
|
@RequiresApi(21)
|
||||||
public CameraXView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
|
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
|
||||||
int defStyleRes) {
|
int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
init(context, attrs);
|
init(context, attrs);
|
||||||
}
|
}
|
||||||
@@ -172,23 +172,23 @@ public final class CameraXView extends FrameLayout {
|
|||||||
|
|
||||||
private void init(Context context, @Nullable AttributeSet attrs) {
|
private void init(Context context, @Nullable AttributeSet attrs) {
|
||||||
addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
|
addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
|
||||||
mCameraModule = new CameraXModule(this);
|
mCameraModule = new SignalCameraXModule(this);
|
||||||
|
|
||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraXView);
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
|
||||||
setScaleType(
|
setScaleType(
|
||||||
ScaleType.fromId(
|
PreviewView.ScaleType.fromId(
|
||||||
a.getInteger(R.styleable.CameraXView_scaleType,
|
a.getInteger(R.styleable.CameraView_scaleType,
|
||||||
getScaleType().getId())));
|
getScaleType().getId())));
|
||||||
setPinchToZoomEnabled(
|
setPinchToZoomEnabled(
|
||||||
a.getBoolean(
|
a.getBoolean(
|
||||||
R.styleable.CameraXView_pinchToZoomEnabled, isPinchToZoomEnabled()));
|
R.styleable.CameraView_pinchToZoomEnabled, isPinchToZoomEnabled()));
|
||||||
setCaptureMode(
|
setCaptureMode(
|
||||||
CaptureMode.fromId(
|
CaptureMode.fromId(
|
||||||
a.getInteger(R.styleable.CameraXView_captureMode,
|
a.getInteger(R.styleable.CameraView_captureMode,
|
||||||
getCaptureMode().getId())));
|
getCaptureMode().getId())));
|
||||||
|
|
||||||
int lensFacing = a.getInt(R.styleable.CameraXView_lensFacing, LENS_FACING_BACK);
|
int lensFacing = a.getInt(R.styleable.CameraView_lensFacing, LENS_FACING_BACK);
|
||||||
switch (lensFacing) {
|
switch (lensFacing) {
|
||||||
case LENS_FACING_NONE:
|
case LENS_FACING_NONE:
|
||||||
setCameraLensFacing(null);
|
setCameraLensFacing(null);
|
||||||
@@ -203,7 +203,7 @@ public final class CameraXView extends FrameLayout {
|
|||||||
// Unhandled event.
|
// Unhandled event.
|
||||||
}
|
}
|
||||||
|
|
||||||
int flashMode = a.getInt(R.styleable.CameraXView_flash, 0);
|
int flashMode = a.getInt(R.styleable.CameraView_flash, 0);
|
||||||
switch (flashMode) {
|
switch (flashMode) {
|
||||||
case FLASH_MODE_AUTO:
|
case FLASH_MODE_AUTO:
|
||||||
setFlash(ImageCapture.FLASH_MODE_AUTO);
|
setFlash(ImageCapture.FLASH_MODE_AUTO);
|
||||||
@@ -265,7 +265,7 @@ public final class CameraXView extends FrameLayout {
|
|||||||
if (savedState instanceof Bundle) {
|
if (savedState instanceof Bundle) {
|
||||||
Bundle state = (Bundle) savedState;
|
Bundle state = (Bundle) savedState;
|
||||||
super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
|
super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
|
||||||
setScaleType(ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
|
setScaleType(PreviewView.ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
|
||||||
setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
|
setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
|
||||||
setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
|
setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
|
||||||
setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
|
setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
|
||||||
@@ -298,6 +298,21 @@ public final class CameraXView extends FrameLayout {
|
|||||||
dpyMgr.unregisterDisplayListener(mDisplayListener);
|
dpyMgr.unregisterDisplayListener(mDisplayListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link LiveData} of the underlying {@link PreviewView}'s
|
||||||
|
* {@link PreviewView.StreamState}.
|
||||||
|
*
|
||||||
|
* @return A {@link LiveData} containing the {@link PreviewView.StreamState}. Apps can either
|
||||||
|
* get current value by {@link LiveData#getValue()} or register a observer by
|
||||||
|
* {@link LiveData#observe}.
|
||||||
|
* @see PreviewView#getPreviewStreamState()
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public LiveData<PreviewView.StreamState> getPreviewStreamState() {
|
||||||
|
return mPreviewView.getPreviewStreamState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
PreviewView getPreviewView() {
|
PreviewView getPreviewView() {
|
||||||
return mPreviewView;
|
return mPreviewView;
|
||||||
}
|
}
|
||||||
@@ -347,11 +362,11 @@ public final class CameraXView extends FrameLayout {
|
|||||||
/**
|
/**
|
||||||
* Returns the scale type used to scale the preview.
|
* Returns the scale type used to scale the preview.
|
||||||
*
|
*
|
||||||
* @return The current {@link ScaleType}.
|
* @return The current {@link PreviewView.ScaleType}.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public ScaleType getScaleType() {
|
public PreviewView.ScaleType getScaleType() {
|
||||||
return mScaleType;
|
return mPreviewView.getScaleType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -359,13 +374,10 @@ public final class CameraXView extends FrameLayout {
|
|||||||
*
|
*
|
||||||
* <p>This controls how the view finder should be scaled and positioned within the view.
|
* <p>This controls how the view finder should be scaled and positioned within the view.
|
||||||
*
|
*
|
||||||
* @param scaleType The desired {@link ScaleType}.
|
* @param scaleType The desired {@link PreviewView.ScaleType}.
|
||||||
*/
|
*/
|
||||||
public void setScaleType(@NonNull ScaleType scaleType) {
|
public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
|
||||||
if (scaleType != mScaleType) {
|
mPreviewView.setScaleType(scaleType);
|
||||||
mScaleType = scaleType;
|
|
||||||
requestLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -401,8 +413,10 @@ public final class CameraXView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the maximum video duration before {@link VideoCapture.OnVideoSavedCallback#onVideoSaved(FileDescriptor)} is
|
* Sets the maximum video duration before
|
||||||
* called automatically. Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
|
* {@link OnVideoSavedCallback#onVideoSaved(VideoCapture.OutputFileResults)} is called
|
||||||
|
* automatically.
|
||||||
|
* Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
|
||||||
*/
|
*/
|
||||||
private void setMaxVideoDuration(long duration) {
|
private void setMaxVideoDuration(long duration) {
|
||||||
mCameraModule.setMaxVideoDuration(duration);
|
mCameraModule.setMaxVideoDuration(duration);
|
||||||
@@ -417,7 +431,8 @@ public final class CameraXView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the maximum video size in bytes before {@link VideoCapture.OnVideoSavedCallback#onVideoSaved(FileDescriptor)}
|
* Sets the maximum video size in bytes before
|
||||||
|
* {@link OnVideoSavedCallback#onVideoSaved(VideoCapture.OutputFileResults)}
|
||||||
* is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
|
* is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
|
||||||
*/
|
*/
|
||||||
private void setMaxVideoSize(long size) {
|
private void setMaxVideoSize(long size) {
|
||||||
@@ -435,28 +450,38 @@ public final class CameraXView extends FrameLayout {
|
|||||||
mCameraModule.takePicture(executor, callback);
|
mCameraModule.takePicture(executor, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a picture and calls
|
||||||
|
* {@link OnImageSavedCallback#onImageSaved(ImageCapture.OutputFileResults)} when done.
|
||||||
|
*
|
||||||
|
* <p> The value of {@link ImageCapture.Metadata#isReversedHorizontal()} in the
|
||||||
|
* {@link ImageCapture.OutputFileOptions} will be overwritten based on camera direction. For
|
||||||
|
* front camera, it will be set to true; for back camera, it will be set to false.
|
||||||
|
*
|
||||||
|
* @param outputFileOptions Options to store the newly captured image.
|
||||||
|
* @param executor The executor in which the callback methods will be run.
|
||||||
|
* @param callback Callback which will receive success or failure.
|
||||||
|
*/
|
||||||
|
public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
|
||||||
|
@NonNull Executor executor,
|
||||||
|
@NonNull OnImageSavedCallback callback) {
|
||||||
|
mCameraModule.takePicture(outputFileOptions, executor, callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a video and calls the OnVideoSavedCallback when done.
|
* Takes a video and calls the OnVideoSavedCallback when done.
|
||||||
*
|
*
|
||||||
* @param file The destination.
|
* @param outputFileOptions Options to store the newly captured video.
|
||||||
* @param executor The executor in which the callback methods will be run.
|
* @param executor The executor in which the callback methods will be run.
|
||||||
* @param callback Callback which will receive success or failure.
|
* @param callback Callback which will receive success or failure.
|
||||||
*/
|
*/
|
||||||
// Begin Signal Custom Code Block
|
public void startRecording(@NonNull VideoCapture.OutputFileOptions outputFileOptions,
|
||||||
@RequiresApi(26)
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
public void startRecording(// Begin Signal Custom Code Block
|
|
||||||
@NonNull FileDescriptor file,
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
@NonNull Executor executor,
|
@NonNull Executor executor,
|
||||||
@NonNull VideoCapture.OnVideoSavedCallback callback) {
|
@NonNull OnVideoSavedCallback callback) {
|
||||||
mCameraModule.startRecording(file, executor, callback);
|
mCameraModule.startRecording(outputFileOptions, executor, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stops an in progress video. */
|
/** Stops an in progress video. */
|
||||||
// Begin Signal Custom Code Block
|
|
||||||
@RequiresApi(26)
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
public void stopRecording() {
|
public void stopRecording() {
|
||||||
mCameraModule.stopRecording();
|
mCameraModule.stopRecording();
|
||||||
}
|
}
|
||||||
@@ -554,7 +579,8 @@ public final class CameraXView extends FrameLayout {
|
|||||||
mDownEventTimestamp = System.currentTimeMillis();
|
mDownEventTimestamp = System.currentTimeMillis();
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
if (delta() < ViewConfiguration.getLongPressTimeout()) {
|
if (delta() < ViewConfiguration.getLongPressTimeout()
|
||||||
|
&& mCameraModule.isBoundToLifecycle()) {
|
||||||
mUpEvent = event;
|
mUpEvent = event;
|
||||||
performClick();
|
performClick();
|
||||||
}
|
}
|
||||||
@@ -578,19 +604,14 @@ public final class CameraXView extends FrameLayout {
|
|||||||
final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
|
final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
|
||||||
mUpEvent = null;
|
mUpEvent = null;
|
||||||
|
|
||||||
CameraSelector cameraSelector =
|
|
||||||
new CameraSelector.Builder().requireLensFacing(
|
|
||||||
mCameraModule.getLensFacing()).build();
|
|
||||||
|
|
||||||
DisplayOrientedMeteringPointFactory pointFactory = new DisplayOrientedMeteringPointFactory(
|
|
||||||
getDisplay(), cameraSelector, mPreviewView.getWidth(), mPreviewView.getHeight());
|
|
||||||
float afPointWidth = 1.0f / 6.0f; // 1/6 total area
|
|
||||||
float aePointWidth = afPointWidth * 1.5f;
|
|
||||||
MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
|
|
||||||
MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
|
|
||||||
|
|
||||||
Camera camera = mCameraModule.getCamera();
|
Camera camera = mCameraModule.getCamera();
|
||||||
if (camera != null) {
|
if (camera != null) {
|
||||||
|
MeteringPointFactory pointFactory = mPreviewView.getMeteringPointFactory();
|
||||||
|
float afPointWidth = 1.0f / 6.0f; // 1/6 total area
|
||||||
|
float aePointWidth = afPointWidth * 1.5f;
|
||||||
|
MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
|
||||||
|
MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
|
||||||
|
|
||||||
ListenableFuture<FocusMeteringResult> future =
|
ListenableFuture<FocusMeteringResult> future =
|
||||||
camera.getCameraControl().startFocusAndMetering(
|
camera.getCameraControl().startFocusAndMetering(
|
||||||
new FocusMeteringAction.Builder(afPoint,
|
new FocusMeteringAction.Builder(afPoint,
|
||||||
@@ -609,7 +630,7 @@ public final class CameraXView extends FrameLayout {
|
|||||||
}, CameraXExecutors.directExecutor());
|
}, CameraXExecutors.directExecutor());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "cannot access camera");
|
Logger.d(TAG, "cannot access camera");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -711,45 +732,11 @@ public final class CameraXView extends FrameLayout {
|
|||||||
return mCameraModule.isTorchOn();
|
return mCameraModule.isTorchOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Options for scaling the bounds of the view finder to the bounds of this view. */
|
|
||||||
public enum ScaleType {
|
|
||||||
/**
|
|
||||||
* Scale the view finder, maintaining the source aspect ratio, so the view finder fills the
|
|
||||||
* entire view. This will cause the view finder to crop the source image if the camera
|
|
||||||
* aspect ratio does not match the view aspect ratio.
|
|
||||||
*/
|
|
||||||
CENTER_CROP(0),
|
|
||||||
/**
|
|
||||||
* Scale the view finder, maintaining the source aspect ratio, so the view finder is
|
|
||||||
* entirely contained within the view.
|
|
||||||
*/
|
|
||||||
CENTER_INSIDE(1);
|
|
||||||
|
|
||||||
private final int mId;
|
|
||||||
|
|
||||||
int getId() {
|
|
||||||
return mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScaleType(int id) {
|
|
||||||
mId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ScaleType fromId(int id) {
|
|
||||||
for (ScaleType st : values()) {
|
|
||||||
if (st.mId == id) {
|
|
||||||
return st;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The capture mode used by CameraView.
|
* The capture mode used by CameraView.
|
||||||
*
|
*
|
||||||
* <p>This enum can be used to determine which capture mode will be enabled for {@link
|
* <p>This enum can be used to determine which capture mode will be enabled for {@link
|
||||||
* CameraXView}.
|
* SignalCameraView}.
|
||||||
*/
|
*/
|
||||||
public enum CaptureMode {
|
public enum CaptureMode {
|
||||||
/** A mode where image capture is enabled. */
|
/** A mode where image capture is enabled. */
|
||||||
@@ -832,4 +819,4 @@ public final class CameraXView extends FrameLayout {
|
|||||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.mediasend.camerax;
|
package androidx.camera.view;
|
||||||
|
|
||||||
import android.Manifest.permission;
|
import android.Manifest.permission;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@@ -28,16 +28,19 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.annotation.RequiresPermission;
|
import androidx.annotation.RequiresPermission;
|
||||||
import androidx.camera.core.Camera;
|
import androidx.camera.core.Camera;
|
||||||
|
import androidx.camera.core.CameraInfoUnavailableException;
|
||||||
import androidx.camera.core.CameraSelector;
|
import androidx.camera.core.CameraSelector;
|
||||||
import androidx.camera.core.CameraX;
|
|
||||||
import androidx.camera.core.ImageCapture;
|
import androidx.camera.core.ImageCapture;
|
||||||
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
|
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
|
||||||
|
import androidx.camera.core.ImageCapture.OnImageSavedCallback;
|
||||||
|
import androidx.camera.core.Logger;
|
||||||
import androidx.camera.core.Preview;
|
import androidx.camera.core.Preview;
|
||||||
import androidx.camera.core.TorchState;
|
import androidx.camera.core.TorchState;
|
||||||
import androidx.camera.core.UseCase;
|
import androidx.camera.core.UseCase;
|
||||||
|
import androidx.camera.core.VideoCapture;
|
||||||
|
import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
|
||||||
import androidx.camera.core.impl.CameraInternal;
|
import androidx.camera.core.impl.CameraInternal;
|
||||||
import androidx.camera.core.impl.LensFacingConverter;
|
import androidx.camera.core.impl.LensFacingConverter;
|
||||||
import androidx.camera.core.impl.VideoCaptureConfig;
|
|
||||||
import androidx.camera.core.impl.utils.CameraOrientationUtil;
|
import androidx.camera.core.impl.utils.CameraOrientationUtil;
|
||||||
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
|
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
|
||||||
import androidx.camera.core.impl.utils.futures.FutureCallback;
|
import androidx.camera.core.impl.utils.futures.FutureCallback;
|
||||||
@@ -51,11 +54,10 @@ import androidx.lifecycle.OnLifecycleEvent;
|
|||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
import org.thoughtcrime.securesms.video.VideoUtil;
|
import org.thoughtcrime.securesms.video.VideoUtil;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@@ -68,10 +70,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
|
import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
|
||||||
|
|
||||||
/** CameraX use case operation built on @{link androidx.camera.core}. */
|
/** CameraX use case operation built on @{link androidx.camera.core}. */
|
||||||
// Begin Signal Custom Code Block
|
|
||||||
@RequiresApi(21)
|
@RequiresApi(21)
|
||||||
// End Signal Custom Code Block
|
@SuppressLint("RestrictedApi")
|
||||||
final class CameraXModule {
|
final class SignalCameraXModule {
|
||||||
public static final String TAG = "CameraXModule";
|
public static final String TAG = "CameraXModule";
|
||||||
|
|
||||||
private static final float UNITY_ZOOM_SCALE = 1f;
|
private static final float UNITY_ZOOM_SCALE = 1f;
|
||||||
@@ -82,13 +83,13 @@ final class CameraXModule {
|
|||||||
private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
|
private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
|
||||||
|
|
||||||
private final Preview.Builder mPreviewBuilder;
|
private final Preview.Builder mPreviewBuilder;
|
||||||
private final VideoCaptureConfig.Builder mVideoCaptureConfigBuilder;
|
private final VideoCapture.Builder mVideoCaptureBuilder;
|
||||||
private final ImageCapture.Builder mImageCaptureBuilder;
|
private final ImageCapture.Builder mImageCaptureBuilder;
|
||||||
private final CameraXView mCameraXView;
|
private final SignalCameraView mCameraView;
|
||||||
final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
|
final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
|
||||||
private CameraXView.CaptureMode mCaptureMode = CameraXView.CaptureMode.IMAGE;
|
private SignalCameraView.CaptureMode mCaptureMode = SignalCameraView.CaptureMode.IMAGE;
|
||||||
private long mMaxVideoDuration = CameraXView.INDEFINITE_VIDEO_DURATION;
|
private long mMaxVideoDuration = SignalCameraView.INDEFINITE_VIDEO_DURATION;
|
||||||
private long mMaxVideoSize = CameraXView.INDEFINITE_VIDEO_SIZE;
|
private long mMaxVideoSize = SignalCameraView.INDEFINITE_VIDEO_SIZE;
|
||||||
@ImageCapture.FlashMode
|
@ImageCapture.FlashMode
|
||||||
private int mFlash = FLASH_MODE_OFF;
|
private int mFlash = FLASH_MODE_OFF;
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -110,7 +111,6 @@ final class CameraXModule {
|
|||||||
public void onDestroy(LifecycleOwner owner) {
|
public void onDestroy(LifecycleOwner owner) {
|
||||||
if (owner == mCurrentLifecycle) {
|
if (owner == mCurrentLifecycle) {
|
||||||
clearCurrentLifecycle();
|
clearCurrentLifecycle();
|
||||||
mPreview.setSurfaceProvider(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -123,8 +123,8 @@ final class CameraXModule {
|
|||||||
@Nullable
|
@Nullable
|
||||||
ProcessCameraProvider mCameraProvider;
|
ProcessCameraProvider mCameraProvider;
|
||||||
|
|
||||||
CameraXModule(CameraXView view) {
|
SignalCameraXModule(SignalCameraView view) {
|
||||||
mCameraXView = view;
|
mCameraView = view;
|
||||||
|
|
||||||
Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
|
Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
|
||||||
new FutureCallback<ProcessCameraProvider>() {
|
new FutureCallback<ProcessCameraProvider>() {
|
||||||
@@ -149,14 +149,12 @@ final class CameraXModule {
|
|||||||
|
|
||||||
mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
|
mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
mVideoCaptureBuilder = new VideoCapture.Builder().setTargetName("VideoCapture")
|
||||||
mVideoCaptureConfigBuilder =
|
.setAudioBitRate(VideoUtil.AUDIO_BIT_RATE)
|
||||||
new VideoCaptureConfig.Builder().setTargetName("VideoCapture")
|
.setVideoFrameRate(VideoUtil.VIDEO_FRAME_RATE)
|
||||||
.setAudioBitRate(VideoUtil.AUDIO_BIT_RATE)
|
.setBitRate(VideoUtil.VIDEO_BIT_RATE);
|
||||||
.setVideoFrameRate(VideoUtil.VIDEO_FRAME_RATE)
|
|
||||||
.setBitRate(VideoUtil.VIDEO_BIT_RATE);
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresPermission(permission.CAMERA)
|
@RequiresPermission(permission.CAMERA)
|
||||||
void bindToLifecycle(LifecycleOwner lifecycleOwner) {
|
void bindToLifecycle(LifecycleOwner lifecycleOwner) {
|
||||||
mNewLifecycle = lifecycleOwner;
|
mNewLifecycle = lifecycleOwner;
|
||||||
@@ -173,12 +171,15 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearCurrentLifecycle();
|
clearCurrentLifecycle();
|
||||||
|
if (mNewLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
|
||||||
|
// Lifecycle is already in a destroyed state. Since it may have been a valid
|
||||||
|
// lifecycle when bound, but became destroyed while waiting for layout, treat this as
|
||||||
|
// a no-op now that we have cleared the previous lifecycle.
|
||||||
|
mNewLifecycle = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
mCurrentLifecycle = mNewLifecycle;
|
mCurrentLifecycle = mNewLifecycle;
|
||||||
mNewLifecycle = null;
|
mNewLifecycle = null;
|
||||||
if (mCurrentLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
||||||
mCurrentLifecycle = null;
|
|
||||||
throw new IllegalArgumentException("Cannot bind to lifecycle in a destroyed state.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mCameraProvider == null) {
|
if (mCameraProvider == null) {
|
||||||
// try again once the camera provider is no longer null
|
// try again once the camera provider is no longer null
|
||||||
@@ -188,18 +189,18 @@ final class CameraXModule {
|
|||||||
Set<Integer> available = getAvailableCameraLensFacing();
|
Set<Integer> available = getAvailableCameraLensFacing();
|
||||||
|
|
||||||
if (available.isEmpty()) {
|
if (available.isEmpty()) {
|
||||||
Log.w(TAG, "Unable to bindToLifeCycle since no cameras available");
|
Logger.w(TAG, "Unable to bindToLifeCycle since no cameras available");
|
||||||
mCameraLensFacing = null;
|
mCameraLensFacing = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the current camera exists, or default to another camera
|
// Ensure the current camera exists, or default to another camera
|
||||||
if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
|
if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
|
||||||
Log.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
|
Logger.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
|
||||||
|
|
||||||
// Default to the first available camera direction
|
// Default to the first available camera direction
|
||||||
mCameraLensFacing = available.iterator().next();
|
mCameraLensFacing = available.iterator().next();
|
||||||
|
|
||||||
Log.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
|
Logger.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not attempt to create use cases for a null cameraLensFacing. This could occur if
|
// Do not attempt to create use cases for a null cameraLensFacing. This could occur if
|
||||||
@@ -216,14 +217,12 @@ final class CameraXModule {
|
|||||||
boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
|
boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
|
||||||
|| getDisplayRotationDegrees() == 180;
|
|| getDisplayRotationDegrees() == 180;
|
||||||
|
|
||||||
Rational targetAspectRatio;
|
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels);
|
int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels);
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
|
|
||||||
if (getCaptureMode() == CameraXView.CaptureMode.IMAGE) {
|
Rational targetAspectRatio;
|
||||||
// mImageCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_4_3);
|
if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
|
||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_4_3, isDisplayPortrait));
|
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_4_3, isDisplayPortrait));
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
@@ -232,7 +231,6 @@ final class CameraXModule {
|
|||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, isDisplayPortrait));
|
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, isDisplayPortrait));
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
// mImageCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_16_9);
|
|
||||||
targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
|
targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,15 +243,14 @@ final class CameraXModule {
|
|||||||
|
|
||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
Size size = VideoUtil.getVideoRecordingSize();
|
Size size = VideoUtil.getVideoRecordingSize();
|
||||||
mVideoCaptureConfigBuilder.setTargetResolution(size);
|
mVideoCaptureBuilder.setTargetResolution(size);
|
||||||
mVideoCaptureConfigBuilder.setMaxResolution(size);
|
mVideoCaptureBuilder.setMaxResolution(size);
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
|
|
||||||
mVideoCaptureConfigBuilder.setTargetRotation(getDisplaySurfaceRotation());
|
mVideoCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
if (MediaConstraints.isVideoTranscodeAvailable()) {
|
if (MediaConstraints.isVideoTranscodeAvailable()) {
|
||||||
mVideoCapture = new VideoCapture(mVideoCaptureConfigBuilder.getUseCaseConfig());
|
mVideoCapture = mVideoCaptureBuilder.build();
|
||||||
}
|
}
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
|
|
||||||
@@ -262,15 +259,15 @@ final class CameraXModule {
|
|||||||
mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
|
mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
|
||||||
|
|
||||||
mPreview = mPreviewBuilder.build();
|
mPreview = mPreviewBuilder.build();
|
||||||
mPreview.setSurfaceProvider(mCameraXView.getPreviewView().getPreviewSurfaceProvider());
|
mPreview.setSurfaceProvider(mCameraView.getPreviewView().getSurfaceProvider());
|
||||||
|
|
||||||
CameraSelector cameraSelector =
|
CameraSelector cameraSelector =
|
||||||
new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
|
new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
|
||||||
if (getCaptureMode() == CameraXView.CaptureMode.IMAGE) {
|
if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
|
||||||
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
|
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
|
||||||
mImageCapture,
|
mImageCapture,
|
||||||
mPreview);
|
mPreview);
|
||||||
} else if (getCaptureMode() == CameraXView.CaptureMode.VIDEO) {
|
} else if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
|
||||||
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
|
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
|
||||||
mVideoCapture,
|
mVideoCapture,
|
||||||
mPreview);
|
mPreview);
|
||||||
@@ -301,7 +298,7 @@ final class CameraXModule {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getCaptureMode() == CameraXView.CaptureMode.VIDEO) {
|
if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
|
||||||
throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
|
throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,17 +309,32 @@ final class CameraXModule {
|
|||||||
mImageCapture.takePicture(executor, callback);
|
mImageCapture.takePicture(executor, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
|
||||||
@RequiresApi(26)
|
@NonNull Executor executor, OnImageSavedCallback callback) {
|
||||||
public void startRecording(FileDescriptor file,
|
if (mImageCapture == null) {
|
||||||
// End Signal Custom Code Block
|
return;
|
||||||
Executor executor,
|
}
|
||||||
final VideoCapture.OnVideoSavedCallback callback) {
|
|
||||||
|
if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
|
||||||
|
throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback == null) {
|
||||||
|
throw new IllegalArgumentException("OnImageSavedCallback should not be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFileOptions.getMetadata().setReversedHorizontal(mCameraLensFacing != null
|
||||||
|
&& mCameraLensFacing == CameraSelector.LENS_FACING_FRONT);
|
||||||
|
mImageCapture.takePicture(outputFileOptions, executor, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startRecording(VideoCapture.OutputFileOptions outputFileOptions,
|
||||||
|
Executor executor, final OnVideoSavedCallback callback) {
|
||||||
if (mVideoCapture == null) {
|
if (mVideoCapture == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getCaptureMode() == CameraXView.CaptureMode.IMAGE) {
|
if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
|
||||||
throw new IllegalStateException("Can not record video under IMAGE capture mode.");
|
throw new IllegalStateException("Can not record video under IMAGE capture mode.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,15 +344,14 @@ final class CameraXModule {
|
|||||||
|
|
||||||
mVideoIsRecording.set(true);
|
mVideoIsRecording.set(true);
|
||||||
mVideoCapture.startRecording(
|
mVideoCapture.startRecording(
|
||||||
file,
|
outputFileOptions,
|
||||||
executor,
|
executor,
|
||||||
new VideoCapture.OnVideoSavedCallback() {
|
new VideoCapture.OnVideoSavedCallback() {
|
||||||
@Override
|
@Override
|
||||||
// Begin Signal Custom Code Block
|
public void onVideoSaved(
|
||||||
public void onVideoSaved(@NonNull FileDescriptor savedFile) {
|
@NonNull VideoCapture.OutputFileResults outputFileResults) {
|
||||||
// End Signal Custom Code Block
|
|
||||||
mVideoIsRecording.set(false);
|
mVideoIsRecording.set(false);
|
||||||
callback.onVideoSaved(savedFile);
|
callback.onVideoSaved(outputFileResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -349,15 +360,12 @@ final class CameraXModule {
|
|||||||
@NonNull String message,
|
@NonNull String message,
|
||||||
@Nullable Throwable cause) {
|
@Nullable Throwable cause) {
|
||||||
mVideoIsRecording.set(false);
|
mVideoIsRecording.set(false);
|
||||||
Log.e(TAG, message, cause);
|
Logger.e(TAG, message, cause);
|
||||||
callback.onError(videoCaptureError, message, cause);
|
callback.onError(videoCaptureError, message, cause);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
|
||||||
@RequiresApi(26)
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
public void stopRecording() {
|
public void stopRecording() {
|
||||||
if (mVideoCapture == null) {
|
if (mVideoCapture == null) {
|
||||||
return;
|
return;
|
||||||
@@ -388,14 +396,15 @@ final class CameraXModule {
|
|||||||
|
|
||||||
@RequiresPermission(permission.CAMERA)
|
@RequiresPermission(permission.CAMERA)
|
||||||
public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
|
public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
|
||||||
String cameraId;
|
if (mCameraProvider == null) {
|
||||||
try {
|
return false;
|
||||||
cameraId = CameraX.getCameraWithLensFacing(lensFacing);
|
}
|
||||||
} catch (Exception e) {
|
try {
|
||||||
throw new IllegalStateException("Unable to query lens facing.", e);
|
return mCameraProvider.hasCamera(
|
||||||
|
new CameraSelector.Builder().requireLensFacing(lensFacing).build());
|
||||||
|
} catch (CameraInfoUnavailableException e) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cameraId != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -454,7 +463,7 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
}, CameraXExecutors.directExecutor());
|
}, CameraXExecutors.directExecutor());
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Failed to set zoom ratio");
|
Logger.e(TAG, "Failed to set zoom ratio");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,6 +495,10 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isBoundToLifecycle() {
|
||||||
|
return mCamera != null;
|
||||||
|
}
|
||||||
|
|
||||||
int getRelativeCameraOrientation(boolean compensateForMirroring) {
|
int getRelativeCameraOrientation(boolean compensateForMirroring) {
|
||||||
int rotationDegrees = 0;
|
int rotationDegrees = 0;
|
||||||
if (mCamera != null) {
|
if (mCamera != null) {
|
||||||
@@ -520,6 +533,11 @@ final class CameraXModule {
|
|||||||
if (!toUnbind.isEmpty()) {
|
if (!toUnbind.isEmpty()) {
|
||||||
mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
|
mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove surface provider once unbound.
|
||||||
|
if (mPreview != null) {
|
||||||
|
mPreview.setSurfaceProvider(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mCamera = null;
|
mCamera = null;
|
||||||
mCurrentLifecycle = null;
|
mCurrentLifecycle = null;
|
||||||
@@ -532,7 +550,7 @@ final class CameraXModule {
|
|||||||
mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
|
mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mVideoCapture != null && MediaConstraints.isVideoTranscodeAvailable()) {
|
if (mVideoCapture != null) {
|
||||||
mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
|
mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,7 +585,7 @@ final class CameraXModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraInternal camera = mImageCapture.getBoundCamera();
|
CameraInternal camera = mImageCapture.getCamera();
|
||||||
|
|
||||||
if (camera == null) {
|
if (camera == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -614,15 +632,15 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Context getContext() {
|
public Context getContext() {
|
||||||
return mCameraXView.getContext();
|
return mCameraView.getContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return mCameraXView.getWidth();
|
return mCameraView.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return mCameraXView.getHeight();
|
return mCameraView.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDisplayRotationDegrees() {
|
public int getDisplayRotationDegrees() {
|
||||||
@@ -630,15 +648,15 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected int getDisplaySurfaceRotation() {
|
protected int getDisplaySurfaceRotation() {
|
||||||
return mCameraXView.getDisplaySurfaceRotation();
|
return mCameraView.getDisplaySurfaceRotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMeasuredWidth() {
|
private int getMeasuredWidth() {
|
||||||
return mCameraXView.getMeasuredWidth();
|
return mCameraView.getMeasuredWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMeasuredHeight() {
|
private int getMeasuredHeight() {
|
||||||
return mCameraXView.getMeasuredHeight();
|
return mCameraView.getMeasuredHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -647,11 +665,11 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public CameraXView.CaptureMode getCaptureMode() {
|
public SignalCameraView.CaptureMode getCaptureMode() {
|
||||||
return mCaptureMode;
|
return mCaptureMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCaptureMode(@NonNull CameraXView.CaptureMode captureMode) {
|
public void setCaptureMode(@NonNull SignalCameraView.CaptureMode captureMode) {
|
||||||
this.mCaptureMode = captureMode;
|
this.mCaptureMode = captureMode;
|
||||||
rebindToLifecycle();
|
rebindToLifecycle();
|
||||||
}
|
}
|
||||||
58
app/src/main/java/org/signal/glide/Log.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package org.signal.glide;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public final class Log {
|
||||||
|
|
||||||
|
private Log() {}
|
||||||
|
|
||||||
|
public static void v(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().v(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void d(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().d(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void i(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().i(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void w(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().w(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().e(tag, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable) {
|
||||||
|
SignalGlideCodecs.getLogProvider().e(tag, message, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Provider {
|
||||||
|
void v(@NonNull String tag, @NonNull String message);
|
||||||
|
void d(@NonNull String tag, @NonNull String message);
|
||||||
|
void i(@NonNull String tag, @NonNull String message);
|
||||||
|
void w(@NonNull String tag, @NonNull String message);
|
||||||
|
void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable);
|
||||||
|
|
||||||
|
Provider EMPTY = new Provider() {
|
||||||
|
@Override
|
||||||
|
public void v(@NonNull String tag, @NonNull String message) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void d(@NonNull String tag, @NonNull String message) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void i(@NonNull String tag, @NonNull String message) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void w(@NonNull String tag, @NonNull String message) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void e(@NonNull String tag, @NonNull String message, @NonNull Throwable throwable) { }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/src/main/java/org/signal/glide/SignalGlideCodecs.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package org.signal.glide;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public final class SignalGlideCodecs {
|
||||||
|
|
||||||
|
private static Log.Provider logProvider = Log.Provider.EMPTY;
|
||||||
|
|
||||||
|
private SignalGlideCodecs() {}
|
||||||
|
|
||||||
|
public static void setLogProvider(@NonNull Log.Provider provider) {
|
||||||
|
logProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull Log.Provider getLogProvider() {
|
||||||
|
return logProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
app/src/main/java/org/signal/glide/apng/APNGDrawable.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.decode.APNGDecoder;
|
||||||
|
import org.signal.glide.common.FrameAnimationDrawable;
|
||||||
|
import org.signal.glide.common.decode.FrameSeqDecoder;
|
||||||
|
import org.signal.glide.common.loader.AssetStreamLoader;
|
||||||
|
import org.signal.glide.common.loader.FileLoader;
|
||||||
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
import org.signal.glide.common.loader.ResourceStreamLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNGDrawable
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
public class APNGDrawable extends FrameAnimationDrawable<APNGDecoder> {
|
||||||
|
public APNGDrawable(Loader provider) {
|
||||||
|
super(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public APNGDrawable(APNGDecoder decoder) {
|
||||||
|
super(decoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected APNGDecoder createFrameSeqDecoder(Loader streamLoader, FrameSeqDecoder.RenderListener listener) {
|
||||||
|
return new APNGDecoder(streamLoader, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static APNGDrawable fromAsset(Context context, String assetPath) {
|
||||||
|
AssetStreamLoader assetStreamLoader = new AssetStreamLoader(context, assetPath);
|
||||||
|
return new APNGDrawable(assetStreamLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static APNGDrawable fromFile(String filePath) {
|
||||||
|
FileLoader fileLoader = new FileLoader(filePath);
|
||||||
|
return new APNGDrawable(fileLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static APNGDrawable fromResource(Context context, int resId) {
|
||||||
|
ResourceStreamLoader resourceStreamLoader = new ResourceStreamLoader(context, resId);
|
||||||
|
return new APNGDrawable(resourceStreamLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/APNG#.27acTL.27:_The_Animation_Control_Chunk
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class ACTLChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("acTL");
|
||||||
|
int num_frames;
|
||||||
|
int num_plays;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void innerParse(APNGReader apngReader) throws IOException {
|
||||||
|
num_frames = apngReader.readInt();
|
||||||
|
num_plays = apngReader.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
211
app/src/main/java/org/signal/glide/apng/decode/APNGDecoder.java
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
import org.signal.glide.apng.io.APNGWriter;
|
||||||
|
import org.signal.glide.common.decode.Frame;
|
||||||
|
import org.signal.glide.common.decode.FrameSeqDecoder;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGDecoder extends FrameSeqDecoder<APNGReader, APNGWriter> {
|
||||||
|
|
||||||
|
private static final String TAG = APNGDecoder.class.getSimpleName();
|
||||||
|
|
||||||
|
private APNGWriter apngWriter;
|
||||||
|
private int mLoopCount;
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
|
||||||
|
|
||||||
|
private class SnapShot {
|
||||||
|
byte dispose_op;
|
||||||
|
Rect dstRect = new Rect();
|
||||||
|
ByteBuffer byteBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SnapShot snapShot = new SnapShot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loader webp的reader
|
||||||
|
* @param renderListener 渲染的回调
|
||||||
|
*/
|
||||||
|
public APNGDecoder(Loader loader, FrameSeqDecoder.RenderListener renderListener) {
|
||||||
|
super(loader, renderListener);
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected APNGWriter getWriter() {
|
||||||
|
if (apngWriter == null) {
|
||||||
|
apngWriter = new APNGWriter();
|
||||||
|
}
|
||||||
|
return apngWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected APNGReader getReader(Reader reader) {
|
||||||
|
return new APNGReader(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLoopCount() {
|
||||||
|
return mLoopCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void release() {
|
||||||
|
snapShot.byteBuffer = null;
|
||||||
|
apngWriter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Rect read(APNGReader reader) throws IOException {
|
||||||
|
List<Chunk> chunks = APNGParser.parse(reader);
|
||||||
|
List<Chunk> otherChunks = new ArrayList<>();
|
||||||
|
|
||||||
|
boolean actl = false;
|
||||||
|
APNGFrame lastFrame = null;
|
||||||
|
byte[] ihdrData = new byte[0];
|
||||||
|
int canvasWidth = 0, canvasHeight = 0;
|
||||||
|
for (Chunk chunk : chunks) {
|
||||||
|
if (chunk instanceof ACTLChunk) {
|
||||||
|
mLoopCount = ((ACTLChunk) chunk).num_plays;
|
||||||
|
actl = true;
|
||||||
|
} else if (chunk instanceof FCTLChunk) {
|
||||||
|
APNGFrame frame = new APNGFrame(reader, (FCTLChunk) chunk);
|
||||||
|
frame.prefixChunks = otherChunks;
|
||||||
|
frame.ihdrData = ihdrData;
|
||||||
|
frames.add(frame);
|
||||||
|
lastFrame = frame;
|
||||||
|
} else if (chunk instanceof FDATChunk) {
|
||||||
|
if (lastFrame != null) {
|
||||||
|
lastFrame.imageChunks.add(chunk);
|
||||||
|
}
|
||||||
|
} else if (chunk instanceof IDATChunk) {
|
||||||
|
if (!actl) {
|
||||||
|
//如果为非APNG图片,则只解码PNG
|
||||||
|
Frame frame = new StillFrame(reader);
|
||||||
|
frame.frameWidth = canvasWidth;
|
||||||
|
frame.frameHeight = canvasHeight;
|
||||||
|
frames.add(frame);
|
||||||
|
mLoopCount = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (lastFrame != null) {
|
||||||
|
lastFrame.imageChunks.add(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (chunk instanceof IHDRChunk) {
|
||||||
|
canvasWidth = ((IHDRChunk) chunk).width;
|
||||||
|
canvasHeight = ((IHDRChunk) chunk).height;
|
||||||
|
ihdrData = ((IHDRChunk) chunk).data;
|
||||||
|
} else if (!(chunk instanceof IENDChunk)) {
|
||||||
|
otherChunks.add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameBuffer = ByteBuffer.allocate((canvasWidth * canvasHeight / (sampleSize * sampleSize) + 1) * 4);
|
||||||
|
snapShot.byteBuffer = ByteBuffer.allocate((canvasWidth * canvasHeight / (sampleSize * sampleSize) + 1) * 4);
|
||||||
|
return new Rect(0, 0, canvasWidth, canvasHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void renderFrame(Frame frame) {
|
||||||
|
if (frame == null || fullRect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = obtainBitmap(fullRect.width() / sampleSize, fullRect.height() / sampleSize);
|
||||||
|
Canvas canvas = cachedCanvas.get(bitmap);
|
||||||
|
if (canvas == null) {
|
||||||
|
canvas = new Canvas(bitmap);
|
||||||
|
cachedCanvas.put(bitmap, canvas);
|
||||||
|
}
|
||||||
|
if (frame instanceof APNGFrame) {
|
||||||
|
// 从缓存中恢复当前帧
|
||||||
|
frameBuffer.rewind();
|
||||||
|
bitmap.copyPixelsFromBuffer(frameBuffer);
|
||||||
|
// 开始绘制前,处理快照中的设定
|
||||||
|
if (this.frameIndex == 0) {
|
||||||
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
|
} else {
|
||||||
|
canvas.save();
|
||||||
|
canvas.clipRect(snapShot.dstRect);
|
||||||
|
switch (snapShot.dispose_op) {
|
||||||
|
// 从快照中恢复上一帧之前的显示内容
|
||||||
|
case FCTLChunk.APNG_DISPOSE_OP_PREVIOUS:
|
||||||
|
snapShot.byteBuffer.rewind();
|
||||||
|
bitmap.copyPixelsFromBuffer(snapShot.byteBuffer);
|
||||||
|
break;
|
||||||
|
// 清空上一帧所画区域
|
||||||
|
case FCTLChunk.APNG_DISPOSE_OP_BACKGROUND:
|
||||||
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
|
break;
|
||||||
|
// 什么都不做
|
||||||
|
case FCTLChunk.APNG_DISPOSE_OP_NON:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后根据dispose设定传递到快照信息中
|
||||||
|
if (((APNGFrame) frame).dispose_op == FCTLChunk.APNG_DISPOSE_OP_PREVIOUS) {
|
||||||
|
if (snapShot.dispose_op != FCTLChunk.APNG_DISPOSE_OP_PREVIOUS) {
|
||||||
|
snapShot.byteBuffer.rewind();
|
||||||
|
bitmap.copyPixelsToBuffer(snapShot.byteBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snapShot.dispose_op = ((APNGFrame) frame).dispose_op;
|
||||||
|
canvas.save();
|
||||||
|
if (((APNGFrame) frame).blend_op == FCTLChunk.APNG_BLEND_OP_SOURCE) {
|
||||||
|
canvas.clipRect(
|
||||||
|
frame.frameX / sampleSize,
|
||||||
|
frame.frameY / sampleSize,
|
||||||
|
(frame.frameX + frame.frameWidth) / sampleSize,
|
||||||
|
(frame.frameY + frame.frameHeight) / sampleSize);
|
||||||
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
snapShot.dstRect.set(frame.frameX / sampleSize,
|
||||||
|
frame.frameY / sampleSize,
|
||||||
|
(frame.frameX + frame.frameWidth) / sampleSize,
|
||||||
|
(frame.frameY + frame.frameHeight) / sampleSize);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
//开始真正绘制当前帧的内容
|
||||||
|
Bitmap inBitmap = obtainBitmap(frame.frameWidth, frame.frameHeight);
|
||||||
|
recycleBitmap(frame.draw(canvas, paint, sampleSize, inBitmap, getWriter()));
|
||||||
|
recycleBitmap(inBitmap);
|
||||||
|
frameBuffer.rewind();
|
||||||
|
bitmap.copyPixelsToBuffer(frameBuffer);
|
||||||
|
recycleBitmap(bitmap);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e(TAG, "Failed to render!", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
147
app/src/main/java/org/signal/glide/apng/decode/APNGFrame.java
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
import org.signal.glide.apng.io.APNGWriter;
|
||||||
|
import org.signal.glide.common.decode.Frame;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGFrame extends Frame<APNGReader, APNGWriter> {
|
||||||
|
public final byte blend_op;
|
||||||
|
public final byte dispose_op;
|
||||||
|
byte[] ihdrData;
|
||||||
|
List<Chunk> imageChunks = new ArrayList<>();
|
||||||
|
List<Chunk> prefixChunks = new ArrayList<>();
|
||||||
|
private static final byte[] sPNGSignatures = {(byte) 137, 80, 78, 71, 13, 10, 26, 10};
|
||||||
|
private static final byte[] sPNGEndChunk = {0, 0, 0, 0, 0x49, 0x45, 0x4E, 0x44, (byte) 0xAE, 0x42, 0x60, (byte) 0x82};
|
||||||
|
|
||||||
|
private static ThreadLocal<CRC32> sCRC32 = new ThreadLocal<>();
|
||||||
|
|
||||||
|
private CRC32 getCRC32() {
|
||||||
|
CRC32 crc32 = sCRC32.get();
|
||||||
|
if (crc32 == null) {
|
||||||
|
crc32 = new CRC32();
|
||||||
|
sCRC32.set(crc32);
|
||||||
|
}
|
||||||
|
return crc32;
|
||||||
|
}
|
||||||
|
|
||||||
|
public APNGFrame(APNGReader reader, FCTLChunk fctlChunk) {
|
||||||
|
super(reader);
|
||||||
|
blend_op = fctlChunk.blend_op;
|
||||||
|
dispose_op = fctlChunk.dispose_op;
|
||||||
|
frameDuration = fctlChunk.delay_num * 1000 / (fctlChunk.delay_den == 0 ? 100 : fctlChunk.delay_den);
|
||||||
|
frameWidth = fctlChunk.width;
|
||||||
|
frameHeight = fctlChunk.height;
|
||||||
|
frameX = fctlChunk.x_offset;
|
||||||
|
frameY = fctlChunk.y_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int encode(APNGWriter apngWriter) throws IOException {
|
||||||
|
int fileSize = 8 + 13 + 12;
|
||||||
|
|
||||||
|
//prefixChunks
|
||||||
|
for (Chunk chunk : prefixChunks) {
|
||||||
|
fileSize += chunk.length + 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
//imageChunks
|
||||||
|
for (Chunk chunk : imageChunks) {
|
||||||
|
if (chunk instanceof IDATChunk) {
|
||||||
|
fileSize += chunk.length + 12;
|
||||||
|
} else if (chunk instanceof FDATChunk) {
|
||||||
|
fileSize += chunk.length + 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileSize += sPNGEndChunk.length;
|
||||||
|
apngWriter.reset(fileSize);
|
||||||
|
apngWriter.putBytes(sPNGSignatures);
|
||||||
|
//IHDR Chunk
|
||||||
|
apngWriter.writeInt(13);
|
||||||
|
int start = apngWriter.position();
|
||||||
|
apngWriter.writeFourCC(IHDRChunk.ID);
|
||||||
|
apngWriter.writeInt(frameWidth);
|
||||||
|
apngWriter.writeInt(frameHeight);
|
||||||
|
apngWriter.putBytes(ihdrData);
|
||||||
|
CRC32 crc32 = getCRC32();
|
||||||
|
crc32.reset();
|
||||||
|
crc32.update(apngWriter.toByteArray(), start, 17);
|
||||||
|
apngWriter.writeInt((int) crc32.getValue());
|
||||||
|
|
||||||
|
//prefixChunks
|
||||||
|
for (Chunk chunk : prefixChunks) {
|
||||||
|
if (chunk instanceof IENDChunk) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reader.reset();
|
||||||
|
reader.skip(chunk.offset);
|
||||||
|
reader.read(apngWriter.toByteArray(), apngWriter.position(), chunk.length + 12);
|
||||||
|
apngWriter.skip(chunk.length + 12);
|
||||||
|
}
|
||||||
|
//imageChunks
|
||||||
|
for (Chunk chunk : imageChunks) {
|
||||||
|
if (chunk instanceof IDATChunk) {
|
||||||
|
reader.reset();
|
||||||
|
reader.skip(chunk.offset);
|
||||||
|
reader.read(apngWriter.toByteArray(), apngWriter.position(), chunk.length + 12);
|
||||||
|
apngWriter.skip(chunk.length + 12);
|
||||||
|
} else if (chunk instanceof FDATChunk) {
|
||||||
|
apngWriter.writeInt(chunk.length - 4);
|
||||||
|
start = apngWriter.position();
|
||||||
|
apngWriter.writeFourCC(IDATChunk.ID);
|
||||||
|
|
||||||
|
reader.reset();
|
||||||
|
// skip to fdat data position
|
||||||
|
reader.skip(chunk.offset + 4 + 4 + 4);
|
||||||
|
reader.read(apngWriter.toByteArray(), apngWriter.position(), chunk.length - 4);
|
||||||
|
|
||||||
|
apngWriter.skip(chunk.length - 4);
|
||||||
|
crc32.reset();
|
||||||
|
crc32.update(apngWriter.toByteArray(), start, chunk.length);
|
||||||
|
apngWriter.writeInt((int) crc32.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//endChunk
|
||||||
|
apngWriter.putBytes(sPNGEndChunk);
|
||||||
|
return fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bitmap draw(Canvas canvas, Paint paint, int sampleSize, Bitmap reusedBitmap, APNGWriter writer) {
|
||||||
|
try {
|
||||||
|
int length = encode(writer);
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = false;
|
||||||
|
options.inSampleSize = sampleSize;
|
||||||
|
options.inMutable = true;
|
||||||
|
options.inBitmap = reusedBitmap;
|
||||||
|
byte[] bytes = writer.toByteArray();
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, length, options);
|
||||||
|
assert bitmap != null;
|
||||||
|
canvas.drawBitmap(bitmap, (float) frameX / sampleSize, (float) frameY / sampleSize, paint);
|
||||||
|
return bitmap;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
143
app/src/main/java/org/signal/glide/apng/decode/APNGParser.java
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.io.StreamReader;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @link {https://www.w3.org/TR/PNG/#5PNG-file-signature}
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGParser {
|
||||||
|
static class FormatException extends IOException {
|
||||||
|
FormatException() {
|
||||||
|
super("APNG Format error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAPNG(String filePath) {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = new FileInputStream(filePath);
|
||||||
|
return isAPNG(new StreamReader(inputStream));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAPNG(Context context, String assetPath) {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = context.getAssets().open(assetPath);
|
||||||
|
return isAPNG(new StreamReader(inputStream));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAPNG(Context context, int resId) {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = context.getResources().openRawResource(resId);
|
||||||
|
return isAPNG(new StreamReader(inputStream));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAPNG(Reader in) {
|
||||||
|
APNGReader reader = (in instanceof APNGReader) ? (APNGReader) in : new APNGReader(in);
|
||||||
|
try {
|
||||||
|
if (!reader.matchFourCC("\u0089PNG") || !reader.matchFourCC("\r\n\u001a\n")) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
while (reader.available() > 0) {
|
||||||
|
Chunk chunk = parseChunk(reader);
|
||||||
|
if (chunk instanceof ACTLChunk) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Chunk> parse(APNGReader reader) throws IOException {
|
||||||
|
if (!reader.matchFourCC("\u0089PNG") || !reader.matchFourCC("\r\n\u001a\n")) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Chunk> chunks = new ArrayList<>();
|
||||||
|
while (reader.available() > 0) {
|
||||||
|
chunks.add(parseChunk(reader));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Chunk parseChunk(APNGReader reader) throws IOException {
|
||||||
|
int offset = reader.position();
|
||||||
|
int size = reader.readInt();
|
||||||
|
int fourCC = reader.readFourCC();
|
||||||
|
Chunk chunk;
|
||||||
|
if (fourCC == ACTLChunk.ID) {
|
||||||
|
chunk = new ACTLChunk();
|
||||||
|
} else if (fourCC == FCTLChunk.ID) {
|
||||||
|
chunk = new FCTLChunk();
|
||||||
|
} else if (fourCC == FDATChunk.ID) {
|
||||||
|
chunk = new FDATChunk();
|
||||||
|
} else if (fourCC == IDATChunk.ID) {
|
||||||
|
chunk = new IDATChunk();
|
||||||
|
} else if (fourCC == IENDChunk.ID) {
|
||||||
|
chunk = new IENDChunk();
|
||||||
|
} else if (fourCC == IHDRChunk.ID) {
|
||||||
|
chunk = new IHDRChunk();
|
||||||
|
} else {
|
||||||
|
chunk = new Chunk();
|
||||||
|
}
|
||||||
|
chunk.offset = offset;
|
||||||
|
chunk.fourcc = fourCC;
|
||||||
|
chunk.length = size;
|
||||||
|
chunk.parse(reader);
|
||||||
|
chunk.crc = reader.readInt();
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
app/src/main/java/org/signal/glide/apng/decode/Chunk.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: Length (长度) 4字节 指定数据块中数据域的长度,其长度不超过(231-1)字节
|
||||||
|
* Chunk Type Code (数据块类型码) 4字节 数据块类型码由ASCII字母(A-Z和a-z)组成
|
||||||
|
* Chunk Data (数据块数据) 可变长度 存储按照Chunk Type Code指定的数据
|
||||||
|
* CRC (循环冗余检测) 4字节 存储用来检测是否有错误的循环冗余码
|
||||||
|
* @Link https://www.w3.org/TR/PNG
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class Chunk {
|
||||||
|
int length;
|
||||||
|
int fourcc;
|
||||||
|
int crc;
|
||||||
|
int offset;
|
||||||
|
|
||||||
|
static int fourCCToInt(String fourCC) {
|
||||||
|
if (TextUtils.isEmpty(fourCC) || fourCC.length() != 4) {
|
||||||
|
return 0xbadeffff;
|
||||||
|
}
|
||||||
|
return (fourCC.charAt(0) & 0xff)
|
||||||
|
| (fourCC.charAt(1) & 0xff) << 8
|
||||||
|
| (fourCC.charAt(2) & 0xff) << 16
|
||||||
|
| (fourCC.charAt(3) & 0xff) << 24
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse(APNGReader reader) throws IOException {
|
||||||
|
int available = reader.available();
|
||||||
|
innerParse(reader);
|
||||||
|
int offset = available - reader.available();
|
||||||
|
if (offset > length) {
|
||||||
|
throw new IOException("Out of chunk area");
|
||||||
|
} else if (offset < length) {
|
||||||
|
reader.skip(length - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void innerParse(APNGReader reader) throws IOException {
|
||||||
|
}
|
||||||
|
}
|
||||||
121
app/src/main/java/org/signal/glide/apng/decode/FCTLChunk.java
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
* @see {link=https://developer.mozilla.org/en-US/docs/Mozilla/Tech/APNG#.27fcTL.27:_The_Frame_Control_Chunk}
|
||||||
|
*/
|
||||||
|
class FCTLChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("fcTL");
|
||||||
|
int sequence_number;
|
||||||
|
/**
|
||||||
|
* x_offset >= 0
|
||||||
|
* y_offset >= 0
|
||||||
|
* width > 0
|
||||||
|
* height > 0
|
||||||
|
* x_offset + width <= 'IHDR' width
|
||||||
|
* y_offset + height <= 'IHDR' height
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Width of the following frame.
|
||||||
|
*/
|
||||||
|
int width;
|
||||||
|
/**
|
||||||
|
* Height of the following frame.
|
||||||
|
*/
|
||||||
|
int height;
|
||||||
|
/**
|
||||||
|
* X position at which to render the following frame.
|
||||||
|
*/
|
||||||
|
int x_offset;
|
||||||
|
/**
|
||||||
|
* Y position at which to render the following frame.
|
||||||
|
*/
|
||||||
|
int y_offset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The delay_num and delay_den parameters together specify a fraction indicating the time to
|
||||||
|
* display the current frame, in seconds. If the denominator is 0, it is to be treated as if it
|
||||||
|
* were 100 (that is, delay_num then specifies 1/100ths of a second).
|
||||||
|
* If the the value of the numerator is 0 the decoder should render the next frame as quickly as
|
||||||
|
* possible, though viewers may impose a reasonable lower bound.
|
||||||
|
* <p>
|
||||||
|
* Frame timings should be independent of the time required for decoding and display of each frame,
|
||||||
|
* so that animations will run at the same rate regardless of the performance of the decoder implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frame delay fraction numerator.
|
||||||
|
*/
|
||||||
|
short delay_num;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frame delay fraction denominator.
|
||||||
|
*/
|
||||||
|
short delay_den;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of frame area disposal to be done after rendering this frame.
|
||||||
|
* dispose_op specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
|
||||||
|
* If the first 'fcTL' chunk uses a dispose_op of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND.
|
||||||
|
*/
|
||||||
|
byte dispose_op;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of frame area rendering for this frame.
|
||||||
|
*/
|
||||||
|
byte blend_op;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
|
||||||
|
*/
|
||||||
|
static final int APNG_DISPOSE_OP_NON = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
|
||||||
|
*/
|
||||||
|
static final int APNG_DISPOSE_OP_BACKGROUND = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
|
||||||
|
*/
|
||||||
|
static final int APNG_DISPOSE_OP_PREVIOUS = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* blend_op<code> specifies whether the frame is to be alpha blended into the current output buffer content,
|
||||||
|
* or whether it should completely replace its region in the output buffer.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
|
||||||
|
*/
|
||||||
|
static final int APNG_BLEND_OP_SOURCE = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The frame should be composited onto the output buffer based on its alpha,
|
||||||
|
* using a simple OVER operation as described in the Alpha Channel Processing section of the Extensions
|
||||||
|
* to the PNG Specification, Version 1.2.0. Note that the second variation of the sample code is applicable.
|
||||||
|
*/
|
||||||
|
static final int APNG_BLEND_OP_OVER = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void innerParse(APNGReader reader) throws IOException {
|
||||||
|
sequence_number = reader.readInt();
|
||||||
|
width = reader.readInt();
|
||||||
|
height = reader.readInt();
|
||||||
|
x_offset = reader.readInt();
|
||||||
|
y_offset = reader.readInt();
|
||||||
|
delay_num = reader.readShort();
|
||||||
|
delay_den = reader.readShort();
|
||||||
|
dispose_op = reader.peek();
|
||||||
|
blend_op = reader.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/APNG#.27fdAT.27:_The_Frame_Data_Chunk
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class FDATChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("fdAT");
|
||||||
|
int sequence_number;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void innerParse(APNGReader reader) throws IOException {
|
||||||
|
sequence_number = reader.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 作用描述
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class IDATChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("IDAT");
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 作用描述
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class IENDChunk extends Chunk {
|
||||||
|
static final int ID = Chunk.fourCCToInt("IEND");
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The IHDR chunk shall be the first chunk in the PNG datastream. It contains:
|
||||||
|
* <p>
|
||||||
|
* Width 4 bytes
|
||||||
|
* Height 4 bytes
|
||||||
|
* Bit depth 1 byte
|
||||||
|
* Colour type 1 byte
|
||||||
|
* Compression method 1 byte
|
||||||
|
* Filter method 1 byte
|
||||||
|
* Interlace method 1 byte
|
||||||
|
*
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class IHDRChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("IHDR");
|
||||||
|
/**
|
||||||
|
* 图像宽度,以像素为单位
|
||||||
|
*/
|
||||||
|
int width;
|
||||||
|
/**
|
||||||
|
* 图像高度,以像素为单位
|
||||||
|
*/
|
||||||
|
int height;
|
||||||
|
|
||||||
|
byte[] data = new byte[5];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void innerParse(APNGReader reader) throws IOException {
|
||||||
|
width = reader.readInt();
|
||||||
|
height = reader.readInt();
|
||||||
|
reader.read(data, 0, data.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
import org.signal.glide.apng.io.APNGWriter;
|
||||||
|
import org.signal.glide.common.decode.Frame;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class StillFrame extends Frame<APNGReader, APNGWriter> {
|
||||||
|
|
||||||
|
public StillFrame(APNGReader reader) {
|
||||||
|
super(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bitmap draw(Canvas canvas, Paint paint, int sampleSize, Bitmap reusedBitmap, APNGWriter writer) {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = false;
|
||||||
|
options.inSampleSize = sampleSize;
|
||||||
|
options.inMutable = true;
|
||||||
|
options.inBitmap = reusedBitmap;
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
try {
|
||||||
|
reader.reset();
|
||||||
|
bitmap = BitmapFactory.decodeStream(reader.toInputStream(), null, options);
|
||||||
|
assert bitmap != null;
|
||||||
|
paint.setXfermode(null);
|
||||||
|
canvas.drawBitmap(bitmap, 0, 0, paint);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
app/src/main/java/org/signal/glide/apng/io/APNGReader.java
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.io;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.FilterReader;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNGReader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGReader extends FilterReader {
|
||||||
|
private static ThreadLocal<byte[]> __intBytes = new ThreadLocal<>();
|
||||||
|
|
||||||
|
|
||||||
|
protected static byte[] ensureBytes() {
|
||||||
|
byte[] bytes = __intBytes.get();
|
||||||
|
if (bytes == null) {
|
||||||
|
bytes = new byte[4];
|
||||||
|
__intBytes.set(bytes);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public APNGReader(Reader in) {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readInt() throws IOException {
|
||||||
|
byte[] buf = ensureBytes();
|
||||||
|
read(buf, 0, 4);
|
||||||
|
return buf[3] & 0xFF |
|
||||||
|
(buf[2] & 0xFF) << 8 |
|
||||||
|
(buf[1] & 0xFF) << 16 |
|
||||||
|
(buf[0] & 0xFF) << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short readShort() throws IOException {
|
||||||
|
byte[] buf = ensureBytes();
|
||||||
|
read(buf, 0, 2);
|
||||||
|
return (short) (buf[1] & 0xFF |
|
||||||
|
(buf[0] & 0xFF) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return read FourCC and match chars
|
||||||
|
*/
|
||||||
|
public boolean matchFourCC(String chars) throws IOException {
|
||||||
|
if (TextUtils.isEmpty(chars) || chars.length() != 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int fourCC = readFourCC();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (((fourCC >> (i * 8)) & 0xff) != chars.charAt(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readFourCC() throws IOException {
|
||||||
|
byte[] buf = ensureBytes();
|
||||||
|
read(buf, 0, 4);
|
||||||
|
return buf[0] & 0xff | (buf[1] & 0xff) << 8 | (buf[2] & 0xff) << 16 | (buf[3] & 0xff) << 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/src/main/java/org/signal/glide/apng/io/APNGWriter.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.io;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.ByteBufferWriter;
|
||||||
|
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNGWriter
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGWriter extends ByteBufferWriter {
|
||||||
|
public APNGWriter() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeFourCC(int val) {
|
||||||
|
putByte((byte) (val & 0xff));
|
||||||
|
putByte((byte) ((val >> 8) & 0xff));
|
||||||
|
putByte((byte) ((val >> 16) & 0xff));
|
||||||
|
putByte((byte) ((val >> 24) & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeInt(int val) {
|
||||||
|
putByte((byte) ((val >> 24) & 0xff));
|
||||||
|
putByte((byte) ((val >> 16) & 0xff));
|
||||||
|
putByte((byte) ((val >> 8) & 0xff));
|
||||||
|
putByte((byte) (val & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(int size) {
|
||||||
|
super.reset(size);
|
||||||
|
this.byteBuffer.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.DrawFilter;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PaintFlagsDrawFilter;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.glide.common.decode.FrameSeqDecoder;
|
||||||
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: Frame animation drawable
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
public abstract class FrameAnimationDrawable<Decoder extends FrameSeqDecoder> extends Drawable implements Animatable2Compat, FrameSeqDecoder.RenderListener {
|
||||||
|
private static final String TAG = FrameAnimationDrawable.class.getSimpleName();
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
private final Decoder frameSeqDecoder;
|
||||||
|
private DrawFilter drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||||
|
private Matrix matrix = new Matrix();
|
||||||
|
private Set<AnimationCallback> animationCallbacks = new HashSet<>();
|
||||||
|
private Bitmap bitmap;
|
||||||
|
private static final int MSG_ANIMATION_START = 1;
|
||||||
|
private static final int MSG_ANIMATION_END = 2;
|
||||||
|
private Handler uiHandler = new Handler(Looper.getMainLooper()) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_ANIMATION_START:
|
||||||
|
for (AnimationCallback animationCallback : animationCallbacks) {
|
||||||
|
animationCallback.onAnimationStart(FrameAnimationDrawable.this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MSG_ANIMATION_END:
|
||||||
|
for (AnimationCallback animationCallback : animationCallbacks) {
|
||||||
|
animationCallback.onAnimationEnd(FrameAnimationDrawable.this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private Runnable invalidateRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private boolean autoPlay = true;
|
||||||
|
|
||||||
|
public FrameAnimationDrawable(Decoder frameSeqDecoder) {
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
this.frameSeqDecoder = frameSeqDecoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameAnimationDrawable(Loader provider) {
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
this.frameSeqDecoder = createFrameSeqDecoder(provider, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoPlay(boolean autoPlay) {
|
||||||
|
this.autoPlay = autoPlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Decoder createFrameSeqDecoder(Loader streamLoader, FrameSeqDecoder.RenderListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loopLimit <=0为无限播放,>0为实际播放次数
|
||||||
|
*/
|
||||||
|
public void setLoopLimit(int loopLimit) {
|
||||||
|
frameSeqDecoder.setLoopLimit(loopLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
frameSeqDecoder.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pause() {
|
||||||
|
frameSeqDecoder.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume() {
|
||||||
|
frameSeqDecoder.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPaused() {
|
||||||
|
return frameSeqDecoder.isPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (autoPlay) {
|
||||||
|
frameSeqDecoder.start();
|
||||||
|
} else {
|
||||||
|
this.frameSeqDecoder.addRenderListener(this);
|
||||||
|
if (!this.frameSeqDecoder.isRunning()) {
|
||||||
|
this.frameSeqDecoder.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (autoPlay) {
|
||||||
|
frameSeqDecoder.stop();
|
||||||
|
} else {
|
||||||
|
this.frameSeqDecoder.removeRenderListener(this);
|
||||||
|
this.frameSeqDecoder.stopIfNeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunning() {
|
||||||
|
return frameSeqDecoder.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
if (bitmap == null || bitmap.isRecycled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canvas.setDrawFilter(drawFilter);
|
||||||
|
canvas.drawBitmap(bitmap, matrix, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBounds(int left, int top, int right, int bottom) {
|
||||||
|
super.setBounds(left, top, right, bottom);
|
||||||
|
boolean sampleSizeChanged = frameSeqDecoder.setDesiredSize(getBounds().width(), getBounds().height());
|
||||||
|
matrix.setScale(
|
||||||
|
1.0f * getBounds().width() * frameSeqDecoder.getSampleSize() / frameSeqDecoder.getBounds().width(),
|
||||||
|
1.0f * getBounds().height() * frameSeqDecoder.getSampleSize() / frameSeqDecoder.getBounds().height());
|
||||||
|
|
||||||
|
if (sampleSizeChanged)
|
||||||
|
this.bitmap = Bitmap.createBitmap(
|
||||||
|
frameSeqDecoder.getBounds().width() / frameSeqDecoder.getSampleSize(),
|
||||||
|
frameSeqDecoder.getBounds().height() / frameSeqDecoder.getSampleSize(),
|
||||||
|
Bitmap.Config.ARGB_8888);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
paint.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(ColorFilter colorFilter) {
|
||||||
|
paint.setColorFilter(colorFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return PixelFormat.TRANSLUCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
Message.obtain(uiHandler, MSG_ANIMATION_START).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRender(ByteBuffer byteBuffer) {
|
||||||
|
if (!isRunning()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.bitmap == null || this.bitmap.isRecycled()) {
|
||||||
|
this.bitmap = Bitmap.createBitmap(
|
||||||
|
frameSeqDecoder.getBounds().width() / frameSeqDecoder.getSampleSize(),
|
||||||
|
frameSeqDecoder.getBounds().height() / frameSeqDecoder.getSampleSize(),
|
||||||
|
Bitmap.Config.ARGB_8888);
|
||||||
|
}
|
||||||
|
byteBuffer.rewind();
|
||||||
|
if (byteBuffer.remaining() < this.bitmap.getByteCount()) {
|
||||||
|
Log.e(TAG, "onRender:Buffer not large enough for pixels");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.bitmap.copyPixelsFromBuffer(byteBuffer);
|
||||||
|
uiHandler.post(invalidateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd() {
|
||||||
|
Message.obtain(uiHandler, MSG_ANIMATION_END).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setVisible(boolean visible, boolean restart) {
|
||||||
|
if (this.autoPlay) {
|
||||||
|
if (visible) {
|
||||||
|
if (!isRunning()) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
} else if (isRunning()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.setVisible(visible, restart);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntrinsicWidth() {
|
||||||
|
try {
|
||||||
|
return frameSeqDecoder.getBounds().width();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntrinsicHeight() {
|
||||||
|
try {
|
||||||
|
return frameSeqDecoder.getBounds().height();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerAnimationCallback(@NonNull AnimationCallback animationCallback) {
|
||||||
|
this.animationCallbacks.add(animationCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unregisterAnimationCallback(@NonNull AnimationCallback animationCallback) {
|
||||||
|
return this.animationCallbacks.remove(animationCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAnimationCallbacks() {
|
||||||
|
this.animationCallbacks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/src/main/java/org/signal/glide/common/decode/Frame.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.io.Writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: One frame in an animation
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public abstract class Frame<R extends Reader, W extends Writer> {
|
||||||
|
protected final R reader;
|
||||||
|
public int frameWidth;
|
||||||
|
public int frameHeight;
|
||||||
|
public int frameX;
|
||||||
|
public int frameY;
|
||||||
|
public int frameDuration;
|
||||||
|
|
||||||
|
public Frame(R reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Bitmap draw(Canvas canvas, Paint paint, int sampleSize, Bitmap reusedBitmap, W writer);
|
||||||
|
}
|
||||||
@@ -0,0 +1,539 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.glide.common.executor.FrameDecoderExecutor;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.io.Writer;
|
||||||
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: Abstract Frame Animation Decoder
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
public abstract class FrameSeqDecoder<R extends Reader, W extends Writer> {
|
||||||
|
private static final String TAG = FrameSeqDecoder.class.getSimpleName();
|
||||||
|
private final int taskId;
|
||||||
|
|
||||||
|
private final Loader mLoader;
|
||||||
|
private final Handler workerHandler;
|
||||||
|
protected List<Frame> frames = new ArrayList<>();
|
||||||
|
protected int frameIndex = -1;
|
||||||
|
private int playCount;
|
||||||
|
private Integer loopLimit = null;
|
||||||
|
private Set<RenderListener> renderListeners = new HashSet<>();
|
||||||
|
private AtomicBoolean paused = new AtomicBoolean(true);
|
||||||
|
private static final Rect RECT_EMPTY = new Rect();
|
||||||
|
private Runnable renderTask = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (paused.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (canStep()) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
long delay = step();
|
||||||
|
long cost = System.currentTimeMillis() - start;
|
||||||
|
workerHandler.postDelayed(this, Math.max(0, delay - cost));
|
||||||
|
for (RenderListener renderListener : renderListeners) {
|
||||||
|
renderListener.onRender(frameBuffer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
protected int sampleSize = 1;
|
||||||
|
|
||||||
|
private Set<Bitmap> cacheBitmaps = new HashSet<>();
|
||||||
|
protected Map<Bitmap, Canvas> cachedCanvas = new WeakHashMap<>();
|
||||||
|
protected ByteBuffer frameBuffer;
|
||||||
|
protected volatile Rect fullRect;
|
||||||
|
private W mWriter = getWriter();
|
||||||
|
private R mReader = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If played all the needed
|
||||||
|
*/
|
||||||
|
private boolean finished = false;
|
||||||
|
|
||||||
|
private enum State {
|
||||||
|
IDLE,
|
||||||
|
RUNNING,
|
||||||
|
INITIALIZING,
|
||||||
|
FINISHING,
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile State mState = State.IDLE;
|
||||||
|
|
||||||
|
public Loader getLoader() {
|
||||||
|
return mLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract W getWriter();
|
||||||
|
|
||||||
|
protected abstract R getReader(Reader reader);
|
||||||
|
|
||||||
|
protected Bitmap obtainBitmap(int width, int height) {
|
||||||
|
Bitmap ret = null;
|
||||||
|
Iterator<Bitmap> iterator = cacheBitmaps.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
int reuseSize = width * height * 4;
|
||||||
|
ret = iterator.next();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
if (ret != null && ret.getAllocationByteCount() >= reuseSize) {
|
||||||
|
iterator.remove();
|
||||||
|
if (ret.getWidth() != width || ret.getHeight() != height) {
|
||||||
|
ret.reconfigure(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
}
|
||||||
|
ret.eraseColor(0);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ret != null && ret.getByteCount() >= reuseSize) {
|
||||||
|
if (ret.getWidth() == width && ret.getHeight() == height) {
|
||||||
|
iterator.remove();
|
||||||
|
ret.eraseColor(0);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Bitmap.Config config = Bitmap.Config.ARGB_8888;
|
||||||
|
ret = Bitmap.createBitmap(width, height, config);
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void recycleBitmap(Bitmap bitmap) {
|
||||||
|
if (bitmap != null && !cacheBitmaps.contains(bitmap)) {
|
||||||
|
cacheBitmaps.add(bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码器的渲染回调
|
||||||
|
*/
|
||||||
|
public interface RenderListener {
|
||||||
|
/**
|
||||||
|
* 播放开始
|
||||||
|
*/
|
||||||
|
void onStart();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帧播放
|
||||||
|
*/
|
||||||
|
void onRender(ByteBuffer byteBuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放结束
|
||||||
|
*/
|
||||||
|
void onEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loader webp的reader
|
||||||
|
* @param renderListener 渲染的回调
|
||||||
|
*/
|
||||||
|
public FrameSeqDecoder(Loader loader, @Nullable RenderListener renderListener) {
|
||||||
|
this.mLoader = loader;
|
||||||
|
if (renderListener != null) {
|
||||||
|
this.renderListeners.add(renderListener);
|
||||||
|
}
|
||||||
|
this.taskId = FrameDecoderExecutor.getInstance().generateTaskId();
|
||||||
|
this.workerHandler = new Handler(FrameDecoderExecutor.getInstance().getLooper(taskId));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addRenderListener(final RenderListener renderListener) {
|
||||||
|
this.workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
renderListeners.add(renderListener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeRenderListener(final RenderListener renderListener) {
|
||||||
|
this.workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
renderListeners.remove(renderListener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopIfNeeded() {
|
||||||
|
this.workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (renderListeners.size() == 0) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect getBounds() {
|
||||||
|
if (fullRect == null) {
|
||||||
|
if (mState == State.FINISHING) {
|
||||||
|
Log.e(TAG, "In finishing,do not interrupt");
|
||||||
|
}
|
||||||
|
final Thread thread = Thread.currentThread();
|
||||||
|
workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
if (fullRect == null) {
|
||||||
|
if (mReader == null) {
|
||||||
|
mReader = getReader(mLoader.obtain());
|
||||||
|
} else {
|
||||||
|
mReader.reset();
|
||||||
|
}
|
||||||
|
initCanvasBounds(read(mReader));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fullRect = RECT_EMPTY;
|
||||||
|
} finally {
|
||||||
|
LockSupport.unpark(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
LockSupport.park(thread);
|
||||||
|
}
|
||||||
|
return fullRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCanvasBounds(Rect rect) {
|
||||||
|
fullRect = rect;
|
||||||
|
frameBuffer = ByteBuffer.allocate((rect.width() * rect.height() / (sampleSize * sampleSize) + 1) * 4);
|
||||||
|
if (mWriter == null) {
|
||||||
|
mWriter = getWriter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int getFrameCount() {
|
||||||
|
return this.frames.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Loop Count defined in file
|
||||||
|
*/
|
||||||
|
protected abstract int getLoopCount();
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (fullRect == RECT_EMPTY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mState == State.RUNNING || mState == State.INITIALIZING) {
|
||||||
|
Log.i(TAG, debugInfo() + " Already started");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mState == State.FINISHING) {
|
||||||
|
Log.e(TAG, debugInfo() + " Processing,wait for finish at " + mState);
|
||||||
|
}
|
||||||
|
mState = State.INITIALIZING;
|
||||||
|
if (Looper.myLooper() == workerHandler.getLooper()) {
|
||||||
|
innerStart();
|
||||||
|
} else {
|
||||||
|
workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
innerStart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void innerStart() {
|
||||||
|
paused.compareAndSet(true, false);
|
||||||
|
|
||||||
|
final long start = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
if (frames.size() == 0) {
|
||||||
|
try {
|
||||||
|
if (mReader == null) {
|
||||||
|
mReader = getReader(mLoader.obtain());
|
||||||
|
} else {
|
||||||
|
mReader.reset();
|
||||||
|
}
|
||||||
|
initCanvasBounds(read(mReader));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Log.i(TAG, debugInfo() + " Set state to RUNNING,cost " + (System.currentTimeMillis() - start));
|
||||||
|
mState = State.RUNNING;
|
||||||
|
}
|
||||||
|
if (getNumPlays() == 0 || !finished) {
|
||||||
|
this.frameIndex = -1;
|
||||||
|
renderTask.run();
|
||||||
|
for (RenderListener renderListener : renderListeners) {
|
||||||
|
renderListener.onStart();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, debugInfo() + " No need to started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void innerStop() {
|
||||||
|
workerHandler.removeCallbacks(renderTask);
|
||||||
|
frames.clear();
|
||||||
|
for (Bitmap bitmap : cacheBitmaps) {
|
||||||
|
if (bitmap != null && !bitmap.isRecycled()) {
|
||||||
|
bitmap.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cacheBitmaps.clear();
|
||||||
|
if (frameBuffer != null) {
|
||||||
|
frameBuffer = null;
|
||||||
|
}
|
||||||
|
cachedCanvas.clear();
|
||||||
|
try {
|
||||||
|
if (mReader != null) {
|
||||||
|
mReader.close();
|
||||||
|
mReader = null;
|
||||||
|
}
|
||||||
|
if (mWriter != null) {
|
||||||
|
mWriter.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
release();
|
||||||
|
mState = State.IDLE;
|
||||||
|
for (RenderListener renderListener : renderListeners) {
|
||||||
|
renderListener.onEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (fullRect == RECT_EMPTY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mState == State.FINISHING || mState == State.IDLE) {
|
||||||
|
Log.i(TAG, debugInfo() + "No need to stop");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mState == State.INITIALIZING) {
|
||||||
|
Log.e(TAG, debugInfo() + "Processing,wait for finish at " + mState);
|
||||||
|
}
|
||||||
|
mState = State.FINISHING;
|
||||||
|
if (Looper.myLooper() == workerHandler.getLooper()) {
|
||||||
|
innerStop();
|
||||||
|
} else {
|
||||||
|
workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
innerStop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String debugInfo() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void release();
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return mState == State.RUNNING || mState == State.INITIALIZING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPaused() {
|
||||||
|
return paused.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoopLimit(int limit) {
|
||||||
|
this.loopLimit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.playCount = 0;
|
||||||
|
this.frameIndex = -1;
|
||||||
|
this.finished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pause() {
|
||||||
|
workerHandler.removeCallbacks(renderTask);
|
||||||
|
paused.compareAndSet(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume() {
|
||||||
|
paused.compareAndSet(true, false);
|
||||||
|
workerHandler.removeCallbacks(renderTask);
|
||||||
|
workerHandler.post(renderTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getSampleSize() {
|
||||||
|
return sampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setDesiredSize(int width, int height) {
|
||||||
|
boolean sampleSizeChanged = false;
|
||||||
|
int sample = getDesiredSample(width, height);
|
||||||
|
if (sample != this.sampleSize) {
|
||||||
|
this.sampleSize = sample;
|
||||||
|
sampleSizeChanged = true;
|
||||||
|
final boolean tempRunning = isRunning();
|
||||||
|
workerHandler.removeCallbacks(renderTask);
|
||||||
|
workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
innerStop();
|
||||||
|
try {
|
||||||
|
initCanvasBounds(read(getReader(mLoader.obtain())));
|
||||||
|
if (tempRunning) {
|
||||||
|
innerStart();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sampleSizeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getDesiredSample(int desiredWidth, int desiredHeight) {
|
||||||
|
if (desiredWidth == 0 || desiredHeight == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int radio = Math.min(getBounds().width() / desiredWidth, getBounds().height() / desiredHeight);
|
||||||
|
int sample = 1;
|
||||||
|
while ((sample * 2) <= radio) {
|
||||||
|
sample *= 2;
|
||||||
|
}
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Rect read(R reader) throws IOException;
|
||||||
|
|
||||||
|
private int getNumPlays() {
|
||||||
|
return this.loopLimit != null ? this.loopLimit : this.getLoopCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canStep() {
|
||||||
|
if (!isRunning()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (frames.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getNumPlays() <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.playCount < getNumPlays() - 1) {
|
||||||
|
return true;
|
||||||
|
} else if (this.playCount == getNumPlays() - 1 && this.frameIndex < this.getFrameCount() - 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finished = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private long step() {
|
||||||
|
this.frameIndex++;
|
||||||
|
if (this.frameIndex >= this.getFrameCount()) {
|
||||||
|
this.frameIndex = 0;
|
||||||
|
this.playCount++;
|
||||||
|
}
|
||||||
|
Frame frame = getFrame(this.frameIndex);
|
||||||
|
if (frame == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
renderFrame(frame);
|
||||||
|
return frame.frameDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void renderFrame(Frame frame);
|
||||||
|
|
||||||
|
private Frame getFrame(int index) {
|
||||||
|
if (index < 0 || index >= frames.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return frames.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Indexed frame
|
||||||
|
*
|
||||||
|
* @param index <0 means reverse from last index
|
||||||
|
*/
|
||||||
|
public Bitmap getFrameBitmap(int index) throws IOException {
|
||||||
|
if (mState != State.IDLE) {
|
||||||
|
Log.e(TAG, debugInfo() + ",stop first");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mState = State.RUNNING;
|
||||||
|
paused.compareAndSet(true, false);
|
||||||
|
if (frames.size() == 0) {
|
||||||
|
if (mReader == null) {
|
||||||
|
mReader = getReader(mLoader.obtain());
|
||||||
|
} else {
|
||||||
|
mReader.reset();
|
||||||
|
}
|
||||||
|
initCanvasBounds(read(mReader));
|
||||||
|
}
|
||||||
|
if (index < 0) {
|
||||||
|
index += this.frames.size();
|
||||||
|
}
|
||||||
|
if (index < 0) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
frameIndex = -1;
|
||||||
|
while (frameIndex < index) {
|
||||||
|
if (canStep()) {
|
||||||
|
step();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameBuffer.rewind();
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(getBounds().width() / getSampleSize(), getBounds().height() / getSampleSize(), Bitmap.Config.ARGB_8888);
|
||||||
|
bitmap.copyPixelsFromBuffer(frameBuffer);
|
||||||
|
innerStop();
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.executor;
|
||||||
|
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: com.github.penfeizhou.animation.executor
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-11-21
|
||||||
|
*/
|
||||||
|
public class FrameDecoderExecutor {
|
||||||
|
private static int sPoolNumber = 4;
|
||||||
|
private ArrayList<HandlerThread> mHandlerThreadGroup = new ArrayList<>();
|
||||||
|
private AtomicInteger counter = new AtomicInteger(0);
|
||||||
|
|
||||||
|
private FrameDecoderExecutor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Inner {
|
||||||
|
static final FrameDecoderExecutor sInstance = new FrameDecoderExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPoolSize(int size) {
|
||||||
|
sPoolNumber = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrameDecoderExecutor getInstance() {
|
||||||
|
return Inner.sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Looper getLooper(int taskId) {
|
||||||
|
int idx = taskId % sPoolNumber;
|
||||||
|
if (idx >= mHandlerThreadGroup.size()) {
|
||||||
|
HandlerThread handlerThread = new HandlerThread("FrameDecoderExecutor-" + idx);
|
||||||
|
handlerThread.start();
|
||||||
|
|
||||||
|
mHandlerThreadGroup.add(handlerThread);
|
||||||
|
Looper looper = handlerThread.getLooper();
|
||||||
|
if (looper != null) {
|
||||||
|
return looper;
|
||||||
|
} else {
|
||||||
|
return Looper.getMainLooper();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mHandlerThreadGroup.get(idx) != null) {
|
||||||
|
Looper looper = mHandlerThreadGroup.get(idx).getLooper();
|
||||||
|
if (looper != null) {
|
||||||
|
return looper;
|
||||||
|
} else {
|
||||||
|
return Looper.getMainLooper();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Looper.getMainLooper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int generateTaskId() {
|
||||||
|
return counter.getAndIncrement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-14
|
||||||
|
*/
|
||||||
|
public class ByteBufferReader implements Reader {
|
||||||
|
|
||||||
|
private final ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
public ByteBufferReader(ByteBuffer byteBuffer) {
|
||||||
|
this.byteBuffer = byteBuffer;
|
||||||
|
byteBuffer.position(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long total) throws IOException {
|
||||||
|
byteBuffer.position((int) (byteBuffer.position() + total));
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte peek() throws IOException {
|
||||||
|
return byteBuffer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() throws IOException {
|
||||||
|
byteBuffer.position(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int position() {
|
||||||
|
return byteBuffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buffer, int start, int byteCount) throws IOException {
|
||||||
|
byteBuffer.get(buffer, start, byteCount);
|
||||||
|
return byteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return byteBuffer.limit() - byteBuffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream toInputStream() throws IOException {
|
||||||
|
return new ByteArrayInputStream(byteBuffer.array());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: ByteBufferWriter
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-12
|
||||||
|
*/
|
||||||
|
public class ByteBufferWriter implements Writer {
|
||||||
|
|
||||||
|
protected ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
public ByteBufferWriter() {
|
||||||
|
reset(10 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putByte(byte b) {
|
||||||
|
byteBuffer.put(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putBytes(byte[] b) {
|
||||||
|
byteBuffer.put(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int position() {
|
||||||
|
return byteBuffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void skip(int length) {
|
||||||
|
byteBuffer.position(length + position());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] toByteArray() {
|
||||||
|
return byteBuffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(int size) {
|
||||||
|
if (byteBuffer == null || size > byteBuffer.capacity()) {
|
||||||
|
byteBuffer = ByteBuffer.allocate(size);
|
||||||
|
this.byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
byteBuffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/src/main/java/org/signal/glide/common/io/FileReader.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: FileReader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-23
|
||||||
|
*/
|
||||||
|
public class FileReader extends FilterReader {
|
||||||
|
private final File mFile;
|
||||||
|
|
||||||
|
public FileReader(File file) throws IOException {
|
||||||
|
super(new StreamReader(new FileInputStream(file)));
|
||||||
|
mFile = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() throws IOException {
|
||||||
|
reader.close();
|
||||||
|
reader = new StreamReader(new FileInputStream(mFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: FilterReader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-23
|
||||||
|
*/
|
||||||
|
public class FilterReader implements Reader {
|
||||||
|
protected Reader reader;
|
||||||
|
|
||||||
|
public FilterReader(Reader in) {
|
||||||
|
this.reader = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long total) throws IOException {
|
||||||
|
return reader.skip(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte peek() throws IOException {
|
||||||
|
return reader.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() throws IOException {
|
||||||
|
reader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int position() {
|
||||||
|
return reader.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buffer, int start, int byteCount) throws IOException {
|
||||||
|
return reader.read(buffer, start, byteCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return reader.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream toInputStream() throws IOException {
|
||||||
|
reset();
|
||||||
|
return reader.toInputStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
35
app/src/main/java/org/signal/glide/common/io/Reader.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @link {https://developers.google.com/speed/webp/docs/riff_container#terminology_basics}
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-11
|
||||||
|
*/
|
||||||
|
public interface Reader {
|
||||||
|
long skip(long total) throws IOException;
|
||||||
|
|
||||||
|
byte peek() throws IOException;
|
||||||
|
|
||||||
|
void reset() throws IOException;
|
||||||
|
|
||||||
|
int position();
|
||||||
|
|
||||||
|
int read(byte[] buffer, int start, int byteCount) throws IOException;
|
||||||
|
|
||||||
|
int available() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close io
|
||||||
|
*/
|
||||||
|
void close() throws IOException;
|
||||||
|
|
||||||
|
InputStream toInputStream() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-11
|
||||||
|
*/
|
||||||
|
public class StreamReader extends FilterInputStream implements Reader {
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
public StreamReader(InputStream in) {
|
||||||
|
super(in);
|
||||||
|
try {
|
||||||
|
in.reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte peek() throws IOException {
|
||||||
|
byte ret = (byte) read();
|
||||||
|
position++;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
int ret = super.read(b, off, len);
|
||||||
|
position += Math.max(0, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
super.reset();
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
long ret = super.skip(n);
|
||||||
|
position += ret;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int position() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream toInputStream() throws IOException {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/src/main/java/org/signal/glide/common/io/Writer.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-12
|
||||||
|
*/
|
||||||
|
public interface Writer {
|
||||||
|
void reset(int size);
|
||||||
|
|
||||||
|
void putByte(byte b);
|
||||||
|
|
||||||
|
void putBytes(byte[] b);
|
||||||
|
|
||||||
|
int position();
|
||||||
|
|
||||||
|
void skip(int length);
|
||||||
|
|
||||||
|
byte[] toByteArray();
|
||||||
|
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 从Asset中读取流
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/28
|
||||||
|
*/
|
||||||
|
public class AssetStreamLoader extends StreamLoader {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final String mAssetName;
|
||||||
|
|
||||||
|
public AssetStreamLoader(Context context, String assetName) {
|
||||||
|
mContext = context.getApplicationContext();
|
||||||
|
mAssetName = assetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream getInputStream() throws IOException {
|
||||||
|
return mContext.getAssets().open(mAssetName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.ByteBufferReader;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: ByteBufferLoader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-15
|
||||||
|
*/
|
||||||
|
public abstract class ByteBufferLoader implements Loader {
|
||||||
|
public abstract ByteBuffer getByteBuffer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader obtain() throws IOException {
|
||||||
|
return new ByteBufferReader(getByteBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.FileReader;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 从文件加载流
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/28
|
||||||
|
*/
|
||||||
|
public class FileLoader implements Loader {
|
||||||
|
|
||||||
|
private final File mFile;
|
||||||
|
private Reader mReader;
|
||||||
|
|
||||||
|
public FileLoader(String path) {
|
||||||
|
mFile = new File(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Reader obtain() throws IOException {
|
||||||
|
return new FileReader(mFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/src/main/java/org/signal/glide/common/loader/Loader.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: Loader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-14
|
||||||
|
*/
|
||||||
|
public interface Loader {
|
||||||
|
Reader obtain() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 从资源加载流
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/28
|
||||||
|
*/
|
||||||
|
public class ResourceStreamLoader extends StreamLoader {
|
||||||
|
private final Context mContext;
|
||||||
|
private final int mResId;
|
||||||
|
|
||||||
|
|
||||||
|
public ResourceStreamLoader(Context context, int resId) {
|
||||||
|
mContext = context.getApplicationContext();
|
||||||
|
mResId = resId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream getInputStream() throws IOException {
|
||||||
|
return mContext.getResources().openRawResource(mResId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.io.StreamReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/28
|
||||||
|
*/
|
||||||
|
public abstract class StreamLoader implements Loader {
|
||||||
|
protected abstract InputStream getInputStream() throws IOException;
|
||||||
|
|
||||||
|
|
||||||
|
public final synchronized Reader obtain() throws IOException {
|
||||||
|
return new StreamReader(getInputStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||||
|
|
||||||
public final class AppCapabilities {
|
public final class AppCapabilities {
|
||||||
|
|
||||||
private AppCapabilities() {
|
private AppCapabilities() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final boolean UUID_CAPABLE = false;
|
private static final boolean UUID_CAPABLE = false;
|
||||||
|
private static final boolean GV2_CAPABLE = true;
|
||||||
|
private static final boolean GV1_MIGRATION = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param storageCapable Whether or not the user can use storage service. This is another way of
|
* @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.
|
* asking if the user has set a Signal PIN or not.
|
||||||
*/
|
*/
|
||||||
public static SignalServiceProfile.Capabilities getCapabilities(boolean storageCapable) {
|
public static AccountAttributes.Capabilities getCapabilities(boolean storageCapable) {
|
||||||
return new SignalServiceProfile.Capabilities(UUID_CAPABLE, FeatureFlags.groupsV2(), storageCapable);
|
return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import android.content.Context;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.insights.InsightsOptOut;
|
import org.thoughtcrime.securesms.insights.InsightsOptOut;
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@@ -34,10 +34,16 @@ public final class AppInitialization {
|
|||||||
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
||||||
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
|
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
|
||||||
|
TextSecurePreferences.setPasswordDisabled(context, true);
|
||||||
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
|
TextSecurePreferences.setReadReceiptsEnabled(context, true);
|
||||||
|
TextSecurePreferences.setTypingIndicatorsEnabled(context, true);
|
||||||
|
TextSecurePreferences.setHasSeenWelcomeScreen(context, false);
|
||||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||||
SignalStore.onFirstEverAppLaunch();
|
SignalStore.onFirstEverAppLaunch();
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
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.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
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()));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||||
}
|
}
|
||||||
@@ -47,8 +53,32 @@ public final class AppInitialization {
|
|||||||
|
|
||||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||||
SignalStore.onFirstEverAppLaunch();
|
SignalStore.onFirstEverAppLaunch();
|
||||||
|
SignalStore.onboarding().clearAll();
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
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.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary migration method that does the safest bits of {@link #onFirstEverAppLaunch(Context)}
|
||||||
|
*/
|
||||||
|
public static void onRepairFirstEverAppLaunch(@NonNull Context context) {
|
||||||
|
Log.w(TAG, "onRepairFirstEverAppLaunch()");
|
||||||
|
|
||||||
|
InsightsOptOut.userRequestedOptOut(context);
|
||||||
|
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
|
||||||
|
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
||||||
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
|
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
|
||||||
|
TextSecurePreferences.setPasswordDisabled(context, true);
|
||||||
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
|
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.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
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()));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,25 +16,26 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
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.core.util.concurrent.SignalExecutors;
|
||||||
|
import org.signal.core.util.logging.AndroidLogger;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.core.util.logging.PersistentLogger;
|
||||||
|
import org.signal.core.util.tracing.Tracer;
|
||||||
|
import org.signal.glide.SignalGlideCodecs;
|
||||||
import org.signal.ringrtc.CallManager;
|
import org.signal.ringrtc.CallManager;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
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;
|
||||||
@@ -42,15 +43,15 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
|||||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||||
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.GroupV1MigrationJob;
|
||||||
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.RefreshPreKeysJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.LogSecretProvider;
|
||||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||||
import org.thoughtcrime.securesms.logging.SignalUncaughtExceptionHandler;
|
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
@@ -66,9 +67,14 @@ 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.storage.StorageSyncHelper;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||||
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||||
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;
|
||||||
@@ -87,17 +93,13 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*
|
*
|
||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
public class ApplicationContext extends MultiDexApplication implements DefaultLifecycleObserver {
|
public class ApplicationContext extends MultiDexApplication implements AppForegroundObserver.Listener {
|
||||||
|
|
||||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||||
|
|
||||||
private ExpiringMessageManager expiringMessageManager;
|
private ExpiringMessageManager expiringMessageManager;
|
||||||
private ViewOnceMessageManager viewOnceMessageManager;
|
private ViewOnceMessageManager viewOnceMessageManager;
|
||||||
private TypingStatusRepository typingStatusRepository;
|
private PersistentLogger persistentLogger;
|
||||||
private TypingStatusSender typingStatusSender;
|
|
||||||
private PersistentLogger persistentLogger;
|
|
||||||
|
|
||||||
private volatile boolean isAppVisible;
|
|
||||||
|
|
||||||
public static ApplicationContext getInstance(Context context) {
|
public static ApplicationContext getInstance(Context context) {
|
||||||
return (ApplicationContext)context.getApplicationContext();
|
return (ApplicationContext)context.getApplicationContext();
|
||||||
@@ -105,67 +107,99 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
Tracer.getInstance().start("Application#onCreate()");
|
||||||
|
AppStartup.getInstance().onApplicationCreate();
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
super.onCreate();
|
|
||||||
initializeSecurityProvider();
|
|
||||||
initializeLogging();
|
|
||||||
Log.i(TAG, "onCreate()");
|
|
||||||
initializeCrashHandling();
|
|
||||||
initializeAppDependencies();
|
|
||||||
initializeFirstEverAppLaunch();
|
|
||||||
initializeApplicationMigrations();
|
|
||||||
initializeMessageRetrieval();
|
|
||||||
initializeExpiringMessageManager();
|
|
||||||
initializeRevealableMessageManager();
|
|
||||||
initializeTypingStatusRepository();
|
|
||||||
initializeTypingStatusSender();
|
|
||||||
initializeGcmCheck();
|
|
||||||
initializeSignedPreKeyCheck();
|
|
||||||
initializePeriodicTasks();
|
|
||||||
initializeCircumvention();
|
|
||||||
initializeRingRtc();
|
|
||||||
initializePendingMessages();
|
|
||||||
initializeBlobProvider();
|
|
||||||
initializeCleanup();
|
|
||||||
|
|
||||||
FeatureFlags.init();
|
if (FeatureFlags.internalUser()) {
|
||||||
NotificationChannels.create(this);
|
Tracer.getInstance().setMaxBufferSize(35_000);
|
||||||
RefreshPreKeysJob.scheduleIfNecessary();
|
|
||||||
StorageSyncHelper.scheduleRoutineSync();
|
|
||||||
RetrieveProfileJob.enqueueRoutineFetchIfNeccessary(this);
|
|
||||||
RegistrationUtil.maybeMarkRegistrationComplete(this);
|
|
||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 21) {
|
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().beginJobLoop();
|
super.onCreate();
|
||||||
|
|
||||||
|
AppStartup.getInstance().addBlocking("security-provider", this::initializeSecurityProvider)
|
||||||
|
.addBlocking("logging", () -> {
|
||||||
|
initializeLogging();
|
||||||
|
Log.i(TAG, "onCreate()");
|
||||||
|
})
|
||||||
|
.addBlocking("crash-handling", this::initializeCrashHandling)
|
||||||
|
.addBlocking("eat-db", () -> DatabaseFactory.getInstance(this))
|
||||||
|
.addBlocking("app-dependencies", this::initializeAppDependencies)
|
||||||
|
.addBlocking("notification-channels", () -> NotificationChannels.create(this))
|
||||||
|
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
|
||||||
|
.addBlocking("app-migrations", this::initializeApplicationMigrations)
|
||||||
|
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||||
|
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this))
|
||||||
|
.addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this))
|
||||||
|
.addBlocking("message-retriever", this::initializeMessageRetrieval)
|
||||||
|
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
|
||||||
|
.addBlocking("vector-compat", () -> {
|
||||||
|
if (Build.VERSION.SDK_INT < 21) {
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addBlocking("proxy-init", () -> {
|
||||||
|
if (SignalStore.proxy().isProxyEnabled()) {
|
||||||
|
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
|
||||||
|
Conscrypt.setUseEngineSocketByDefault(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||||
|
.addNonBlocking(this::initializeGcmCheck)
|
||||||
|
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
||||||
|
.addNonBlocking(this::initializePeriodicTasks)
|
||||||
|
.addNonBlocking(this::initializeCircumvention)
|
||||||
|
.addNonBlocking(this::initializePendingMessages)
|
||||||
|
.addNonBlocking(this::initializeCleanup)
|
||||||
|
.addNonBlocking(this::initializeGlideCodecs)
|
||||||
|
.addNonBlocking(FeatureFlags::init)
|
||||||
|
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
|
||||||
|
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||||
|
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
||||||
|
.addPostRender(this::initializeExpiringMessageManager)
|
||||||
|
.addPostRender(this::initializeBlobProvider)
|
||||||
|
.execute();
|
||||||
|
|
||||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
|
Tracer.getInstance().end("Application#onCreate()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart(@NonNull LifecycleOwner owner) {
|
public void onForeground() {
|
||||||
isAppVisible = true;
|
long startTime = System.currentTimeMillis();
|
||||||
Log.i(TAG, "App is now visible.");
|
Log.i(TAG, "App is now visible.");
|
||||||
FeatureFlags.refreshIfNecessary();
|
|
||||||
ApplicationDependencies.getRecipientCache().warmUp();
|
|
||||||
executePendingContactSync();
|
|
||||||
KeyCachingService.onAppForegrounded(this);
|
|
||||||
ApplicationDependencies.getFrameRateTracker().begin();
|
ApplicationDependencies.getFrameRateTracker().begin();
|
||||||
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
||||||
|
|
||||||
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
|
FeatureFlags.refreshIfNecessary();
|
||||||
|
ApplicationDependencies.getRecipientCache().warmUp();
|
||||||
|
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
|
||||||
|
GroupV1MigrationJob.enqueueRoutineMigrationsIfNecessary(this);
|
||||||
|
executePendingContactSync();
|
||||||
|
KeyCachingService.onAppForegrounded(this);
|
||||||
|
ApplicationDependencies.getShakeToReport().enable();
|
||||||
|
checkBuildExpiration();
|
||||||
|
});
|
||||||
|
|
||||||
|
Log.d(TAG, "onStart() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop(@NonNull LifecycleOwner owner) {
|
public void onBackground() {
|
||||||
isAppVisible = false;
|
|
||||||
Log.i(TAG, "App is no longer visible.");
|
Log.i(TAG, "App is no longer visible.");
|
||||||
KeyCachingService.onAppBackgrounded(this);
|
KeyCachingService.onAppBackgrounded(this);
|
||||||
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
||||||
ApplicationDependencies.getFrameRateTracker().end();
|
ApplicationDependencies.getFrameRateTracker().end();
|
||||||
|
ApplicationDependencies.getShakeToReport().disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpiringMessageManager getExpiringMessageManager() {
|
public ExpiringMessageManager getExpiringMessageManager() {
|
||||||
|
if (expiringMessageManager == null) {
|
||||||
|
initializeExpiringMessageManager();
|
||||||
|
}
|
||||||
return expiringMessageManager;
|
return expiringMessageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,22 +207,17 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
return viewOnceMessageManager;
|
return viewOnceMessageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypingStatusRepository getTypingStatusRepository() {
|
|
||||||
return typingStatusRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypingStatusSender getTypingStatusSender() {
|
|
||||||
return typingStatusSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAppVisible() {
|
|
||||||
return isAppVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentLogger getPersistentLogger() {
|
public PersistentLogger getPersistentLogger() {
|
||||||
return persistentLogger;
|
return persistentLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkBuildExpiration() {
|
||||||
|
if (Util.getTimeUntilBuildExpiry() <= 0 && !SignalStore.misc().isClientDeprecated()) {
|
||||||
|
Log.w(TAG, "Build expired!");
|
||||||
|
SignalStore.misc().markClientDeprecated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeSecurityProvider() {
|
private void initializeSecurityProvider() {
|
||||||
try {
|
try {
|
||||||
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
||||||
@@ -214,8 +243,8 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeLogging() {
|
private void initializeLogging() {
|
||||||
persistentLogger = new PersistentLogger(this);
|
persistentLogger = new PersistentLogger(this, LogSecretProvider.getOrCreateAttachmentSecret(this), BuildConfig.VERSION_NAME);
|
||||||
org.thoughtcrime.securesms.logging.Log.initialize(new AndroidLogger(), persistentLogger);
|
org.signal.core.util.logging.Log.initialize(new AndroidLogger(), persistentLogger);
|
||||||
|
|
||||||
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
||||||
}
|
}
|
||||||
@@ -234,18 +263,24 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAppDependencies() {
|
private void initializeAppDependencies() {
|
||||||
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this, new SignalServiceNetworkAccess(this)));
|
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeFirstEverAppLaunch() {
|
private void initializeFirstEverAppLaunch() {
|
||||||
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
|
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
|
||||||
if (!SQLCipherOpenHelper.databaseFileExists(this)) {
|
if (!SQLCipherOpenHelper.databaseFileExists(this) || VersionTracker.getDaysSinceFirstInstalled(this) < 365) {
|
||||||
Log.i(TAG, "First ever app launch!");
|
Log.i(TAG, "First ever app launch!");
|
||||||
AppInitialization.onFirstEverAppLaunch(this);
|
AppInitialization.onFirstEverAppLaunch(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
|
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
|
||||||
TextSecurePreferences.setFirstInstallVersion(this, BuildConfig.CANONICAL_VERSION_CODE);
|
TextSecurePreferences.setFirstInstallVersion(this, BuildConfig.CANONICAL_VERSION_CODE);
|
||||||
|
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 90) {
|
||||||
|
Log.i(TAG, "Detected a new install that doesn't have passphrases disabled -- assuming bad initialization.");
|
||||||
|
AppInitialization.onRepairFirstEverAppLaunch(this);
|
||||||
|
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 912) {
|
||||||
|
Log.i(TAG, "Detected a not-recent install that doesn't have passphrases disabled -- disabling now.");
|
||||||
|
TextSecurePreferences.setPasswordDisabled(this, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,19 +308,12 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
this.viewOnceMessageManager = new ViewOnceMessageManager(this);
|
this.viewOnceMessageManager = new ViewOnceMessageManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeTypingStatusRepository() {
|
|
||||||
this.typingStatusRepository = new TypingStatusRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeTypingStatusSender() {
|
|
||||||
this.typingStatusSender = new TypingStatusSender(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializePeriodicTasks() {
|
private void initializePeriodicTasks() {
|
||||||
RotateSignedPreKeyListener.schedule(this);
|
RotateSignedPreKeyListener.schedule(this);
|
||||||
DirectoryRefreshListener.schedule(this);
|
DirectoryRefreshListener.schedule(this);
|
||||||
LocalBackupListener.schedule(this);
|
LocalBackupListener.schedule(this);
|
||||||
RotateSenderCertificateListener.schedule(this);
|
RotateSenderCertificateListener.schedule(this);
|
||||||
|
MessageProcessReceiver.startOrUpdateAlarm(this);
|
||||||
|
|
||||||
if (BuildConfig.PLAY_STORE_DISABLED) {
|
if (BuildConfig.PLAY_STORE_DISABLED) {
|
||||||
UpdateApkRefreshListener.schedule(this);
|
UpdateApkRefreshListener.schedule(this);
|
||||||
@@ -328,23 +356,15 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@WorkerThread
|
||||||
private void initializeCircumvention() {
|
private void initializeCircumvention() {
|
||||||
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
|
if (new SignalServiceNetworkAccess(ApplicationContext.this).isCensored(ApplicationContext.this)) {
|
||||||
@Override
|
try {
|
||||||
protected Void doInBackground(Void... params) {
|
ProviderInstaller.installIfNeeded(ApplicationContext.this);
|
||||||
if (new SignalServiceNetworkAccess(ApplicationContext.this).isCensored(ApplicationContext.this)) {
|
} catch (Throwable t) {
|
||||||
try {
|
Log.w(TAG, t);
|
||||||
ProviderInstaller.installIfNeeded(ApplicationContext.this);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
Log.w(TAG, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executePendingContactSync() {
|
private void executePendingContactSync() {
|
||||||
@@ -359,28 +379,56 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
if (Build.VERSION.SDK_INT >= 26) {
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
FcmJobService.schedule(this);
|
FcmJobService.schedule(this);
|
||||||
} else {
|
} else {
|
||||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
|
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||||
}
|
}
|
||||||
TextSecurePreferences.setNeedsMessagePull(this, false);
|
TextSecurePreferences.setNeedsMessagePull(this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
private void initializeBlobProvider() {
|
private void initializeBlobProvider() {
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
BlobProvider.getInstance().onSessionStart(this);
|
||||||
BlobProvider.getInstance().onSessionStart(this);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
private void initializeCleanup() {
|
private void initializeCleanup() {
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
||||||
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
||||||
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
}
|
||||||
|
|
||||||
|
private void initializeGlideCodecs() {
|
||||||
|
SignalGlideCodecs.setLogProvider(new org.signal.glide.Log.Provider() {
|
||||||
|
@Override
|
||||||
|
public void v(@NonNull String tag, @NonNull String message) {
|
||||||
|
Log.v(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void d(@NonNull String tag, @NonNull String message) {
|
||||||
|
Log.d(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void i(@NonNull String tag, @NonNull String message) {
|
||||||
|
Log.i(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void w(@NonNull String tag, @NonNull String message) {
|
||||||
|
Log.w(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable) {
|
||||||
|
Log.e(tag, message, throwable);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
|
DynamicLanguageContextWrapper.updateContext(base);
|
||||||
|
super.attachBaseContext(base);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ProviderInitializationException extends RuntimeException {
|
private static class ProviderInitializationException extends RuntimeException {
|
||||||
|
|||||||
@@ -17,34 +17,44 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.help.HelpFragment;
|
import org.thoughtcrime.securesms.help.HelpFragment;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
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;
|
||||||
|
import org.thoughtcrime.securesms.preferences.BackupsPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
||||||
|
import org.thoughtcrime.securesms.preferences.DataAndStoragePreferenceFragment;
|
||||||
|
import org.thoughtcrime.securesms.preferences.EditProxyFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
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.widgets.ProfilePreference;
|
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
||||||
|
import org.thoughtcrime.securesms.preferences.widgets.UsernamePreference;
|
||||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||||
|
import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
|
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||||
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Activity for application preference display and management.
|
* The Activity for application preference display and management.
|
||||||
@@ -56,10 +66,15 @@ import org.thoughtcrime.securesms.util.ThemeUtil;
|
|||||||
public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
{
|
{
|
||||||
|
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
|
||||||
|
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
|
||||||
|
public static final String LAUNCH_TO_PROXY_FRAGMENT = "launch.to.proxy.fragment";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
||||||
|
|
||||||
private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile";
|
private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile";
|
||||||
|
private static final String PREFERENCE_CATEGORY_USERNAME = "preference_category_username";
|
||||||
private static final String PREFERENCE_CATEGORY_SMS_MMS = "preference_category_sms_mms";
|
private static final String PREFERENCE_CATEGORY_SMS_MMS = "preference_category_sms_mms";
|
||||||
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications";
|
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications";
|
||||||
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
|
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
|
||||||
@@ -69,10 +84,15 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
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_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 static final String PREFERENCE_CATEGORY_DONATE = "preference_category_donate";
|
||||||
|
|
||||||
|
private static final String WAS_CONFIGURATION_UPDATED = "was_configuration_updated";
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
|
private boolean wasConfigurationUpdated = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreCreate() {
|
protected void onPreCreate() {
|
||||||
dynamicTheme.onCreate(this);
|
dynamicTheme.onCreate(this);
|
||||||
@@ -86,11 +106,25 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
if (getIntent() != null && getIntent().getCategories() != null && getIntent().getCategories().contains("android.intent.category.NOTIFICATION_PREFERENCES")) {
|
if (getIntent() != null && getIntent().getCategories() != null && getIntent().getCategories().contains("android.intent.category.NOTIFICATION_PREFERENCES")) {
|
||||||
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
|
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
|
||||||
|
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_BACKUPS_FRAGMENT, false)) {
|
||||||
|
initFragment(android.R.id.content, new BackupsPreferenceFragment());
|
||||||
|
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_HELP_FRAGMENT, false)) {
|
||||||
|
initFragment(android.R.id.content, new HelpFragment());
|
||||||
|
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_PROXY_FRAGMENT, false)) {
|
||||||
|
initFragment(android.R.id.content, EditProxyFragment.newInstance());
|
||||||
} else if (icicle == null) {
|
} else if (icicle == null) {
|
||||||
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
||||||
|
} else {
|
||||||
|
wasConfigurationUpdated = icicle.getBoolean(WAS_CONFIGURATION_UPDATED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
outState.putBoolean(WAS_CONFIGURATION_UPDATED, wasConfigurationUpdated);
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -112,20 +146,29 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
if (fragmentManager.getBackStackEntryCount() > 0) {
|
if (fragmentManager.getBackStackEntryCount() > 0) {
|
||||||
fragmentManager.popBackStack();
|
fragmentManager.popBackStack();
|
||||||
} else {
|
} else {
|
||||||
// TODO [greyson] Navigation
|
if (wasConfigurationUpdated) {
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
setResult(MainActivity.RESULT_CONFIG_CHANGED);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
} else {
|
||||||
startActivity(intent);
|
setResult(RESULT_OK);
|
||||||
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
onSupportNavigateUp();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
if (key.equals(TextSecurePreferences.THEME_PREF)) {
|
if (key.equals(TextSecurePreferences.THEME_PREF)) {
|
||||||
|
DynamicTheme.setDefaultDayNightMode(this);
|
||||||
recreate();
|
recreate();
|
||||||
} else if (key.equals(TextSecurePreferences.LANGUAGE_PREF)) {
|
} else if (key.equals(TextSecurePreferences.LANGUAGE_PREF)) {
|
||||||
|
CachedInflater.from(this).clear();
|
||||||
|
wasConfigurationUpdated = true;
|
||||||
recreate();
|
recreate();
|
||||||
|
|
||||||
Intent intent = new Intent(this, KeyCachingService.class);
|
Intent intent = new Intent(this, KeyCachingService.class);
|
||||||
@@ -134,6 +177,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void pushFragment(@NonNull Fragment fragment) {
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
|
||||||
|
.replace(android.R.id.content, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment {
|
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -142,6 +193,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_PROFILE)
|
this.findPreference(PREFERENCE_CATEGORY_PROFILE)
|
||||||
.setOnPreferenceClickListener(new ProfileClickListener());
|
.setOnPreferenceClickListener(new ProfileClickListener());
|
||||||
|
this.findPreference(PREFERENCE_CATEGORY_USERNAME)
|
||||||
|
.setOnPreferenceClickListener(new UsernameClickListener());
|
||||||
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
|
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS));
|
||||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
||||||
@@ -159,7 +212,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
this.findPreference(PREFERENCE_CATEGORY_HELP)
|
this.findPreference(PREFERENCE_CATEGORY_HELP)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(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));
|
||||||
|
this.findPreference(PREFERENCE_CATEGORY_DONATE)
|
||||||
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DONATE));
|
||||||
|
|
||||||
tintIcons();
|
tintIcons();
|
||||||
}
|
}
|
||||||
@@ -168,12 +223,30 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
if (Build.VERSION.SDK_INT >= 21) return;
|
if (Build.VERSION.SDK_INT >= 21) return;
|
||||||
|
|
||||||
Preference preference = this.findPreference(PREFERENCE_CATEGORY_SMS_MMS);
|
Preference preference = this.findPreference(PREFERENCE_CATEGORY_SMS_MMS);
|
||||||
preference.getIcon().setColorFilter(ThemeUtil.getThemedColor(requireContext(), R.attr.icon_tint), PorterDuff.Mode.SRC_IN);
|
preference.getIcon().setColorFilter(ContextCompat.getColor(requireContext(), R.color.signal_icon_tint_primary), PorterDuff.Mode.SRC_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
|
|
||||||
|
if (FeatureFlags.usernames()) {
|
||||||
|
UsernamePreference pref = (UsernamePreference) findPreference(PREFERENCE_CATEGORY_USERNAME);
|
||||||
|
pref.setVisible(shouldDisplayUsernameReminder());
|
||||||
|
pref.setOnLongClickListener(v -> {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setMessage(R.string.ApplicationPreferencesActivity_hide_reminder)
|
||||||
|
.setPositiveButton(R.string.ApplicationPreferencesActivity_hide, (dialog, which) -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
SignalStore.misc().hideUsernameReminder();
|
||||||
|
findPreference(PREFERENCE_CATEGORY_USERNAME).setVisible(false);
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
|
.setCancelable(true)
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -188,6 +261,11 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
private void setCategorySummaries() {
|
private void setCategorySummaries() {
|
||||||
((ProfilePreference)this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
|
((ProfilePreference)this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
|
||||||
|
|
||||||
|
if (FeatureFlags.usernames()) {
|
||||||
|
this.findPreference(PREFERENCE_CATEGORY_USERNAME)
|
||||||
|
.setVisible(shouldDisplayUsernameReminder());
|
||||||
|
}
|
||||||
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
|
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
|
||||||
.setSummary(SmsMmsPreferenceFragment.getSummary(getActivity()));
|
.setSummary(SmsMmsPreferenceFragment.getSummary(getActivity()));
|
||||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
||||||
@@ -207,6 +285,10 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldDisplayUsernameReminder() {
|
||||||
|
return FeatureFlags.usernames() && !Recipient.self().getUsername().isPresent() && SignalStore.misc().shouldShowUsernameReminder();
|
||||||
|
}
|
||||||
|
|
||||||
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
|
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
|
||||||
private String category;
|
private String category;
|
||||||
|
|
||||||
@@ -235,7 +317,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
fragment = new ChatsPreferenceFragment();
|
fragment = new ChatsPreferenceFragment();
|
||||||
break;
|
break;
|
||||||
case PREFERENCE_CATEGORY_STORAGE:
|
case PREFERENCE_CATEGORY_STORAGE:
|
||||||
fragment = new StoragePreferenceFragment();
|
fragment = new DataAndStoragePreferenceFragment();
|
||||||
break;
|
break;
|
||||||
case PREFERENCE_CATEGORY_DEVICES:
|
case PREFERENCE_CATEGORY_DEVICES:
|
||||||
Intent intent = new Intent(getActivity(), DeviceActivity.class);
|
Intent intent = new Intent(getActivity(), DeviceActivity.class);
|
||||||
@@ -247,6 +329,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
case PREFERENCE_CATEGORY_HELP:
|
case PREFERENCE_CATEGORY_HELP:
|
||||||
fragment = new HelpFragment();
|
fragment = new HelpFragment();
|
||||||
break;
|
break;
|
||||||
|
case PREFERENCE_CATEGORY_DONATE:
|
||||||
|
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.donate_url));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
@@ -255,14 +340,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
|
|
||||||
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
|
((ApplicationPreferencesActivity) requireActivity()).pushFragment(fragment);
|
||||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
|
||||||
|
|
||||||
fragmentTransaction.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end);
|
|
||||||
|
|
||||||
fragmentTransaction.replace(android.R.id.content, fragment);
|
|
||||||
fragmentTransaction.addToBackStack(null);
|
|
||||||
fragmentTransaction.commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -272,7 +350,15 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
requireActivity().startActivity(EditProfileActivity.getIntentForUserProfileEdit(preference.getContext()));
|
requireActivity().startActivity(ManageProfileActivity.getIntent(requireActivity()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UsernameClickListener implements Preference.OnPreferenceClickListener {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
requireActivity().startActivity(ManageProfileActivity.getIntentForUsernameEdit(preference.getContext()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.transition.TransitionInflater;
|
import android.transition.TransitionInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -28,14 +26,15 @@ import com.bumptech.glide.request.target.CustomTarget;
|
|||||||
import com.bumptech.glide.request.target.Target;
|
import com.bumptech.glide.request.target.Target;
|
||||||
import com.bumptech.glide.request.transition.Transition;
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
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.FullscreenHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for displaying avatars full screen.
|
* Activity for displaying avatars full screen.
|
||||||
@@ -72,27 +71,21 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
|||||||
getWindow().setSharedElementReturnTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_return_transition_set));
|
getWindow().setSharedElementReturnTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_return_transition_set));
|
||||||
}
|
}
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
ImageView avatar = findViewById(R.id.avatar);
|
||||||
ImageView avatar = findViewById(R.id.avatar);
|
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
requireSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
|
|
||||||
showSystemUI();
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
Context context = getApplicationContext();
|
Context context = getApplicationContext();
|
||||||
RecipientId recipientId = RecipientId.from(getIntent().getStringExtra(RECIPIENT_ID_EXTRA));
|
RecipientId recipientId = RecipientId.from(getIntent().getStringExtra(RECIPIENT_ID_EXTRA));
|
||||||
|
|
||||||
Recipient.live(recipientId).observe(this, recipient -> {
|
Recipient.live(recipientId).observe(this, recipient -> {
|
||||||
ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient, recipient.getProfileAvatar())
|
ContactPhoto contactPhoto = recipient.isSelf() ? new ProfileContactPhoto(recipient, recipient.getProfileAvatar())
|
||||||
: recipient.getContactPhoto();
|
: 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)
|
FallbackContactPhoto fallbackPhoto = recipient.isSelf() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
|
||||||
: recipient.getFallbackContactPhoto();
|
: recipient.getFallbackContactPhoto();
|
||||||
|
|
||||||
Resources resources = this.getResources();
|
Resources resources = this.getResources();
|
||||||
|
|
||||||
@@ -132,47 +125,13 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
|||||||
toolbar.setTitle(recipient.getDisplayName(context));
|
toolbar.setTitle(recipient.getDisplayName(context));
|
||||||
});
|
});
|
||||||
|
|
||||||
avatar.setOnClickListener(v -> toggleUiVisibility());
|
FullscreenHelper fullscreenHelper = new FullscreenHelper(this);
|
||||||
|
|
||||||
showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
|
findViewById(android.R.id.content).setOnClickListener(v -> fullscreenHelper.toggleUiVisibility());
|
||||||
}
|
|
||||||
|
|
||||||
private static void showAndHideWithSystemUI(@NonNull Window window, @NonNull View... views) {
|
fullscreenHelper.configureToolbarSpacer(findViewById(R.id.toolbar_cutout_spacer));
|
||||||
window.getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> {
|
|
||||||
boolean hide = (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
|
|
||||||
|
|
||||||
for (View view : views) {
|
fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
|
||||||
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
|
@Override
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.content.res.Configuration;
|
||||||
import android.os.Build.VERSION_CODES;
|
|
||||||
import android.os.Bundle;
|
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.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
|
import org.thoughtcrime.securesms.util.ConfigurationUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageActivityHelper;
|
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all activities. The vast majority of activities shouldn't extend this directly.
|
* 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
|
* Instead, they should extend {@link PassphraseRequiredActivity} so they're protected by
|
||||||
@@ -29,20 +33,22 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
AppStartup.getInstance().onCriticalRenderEventStart();
|
||||||
logEvent("onCreate()");
|
logEvent("onCreate()");
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
AppStartup.getInstance().onCriticalRenderEventEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
initializeScreenshotSecurity();
|
initializeScreenshotSecurity();
|
||||||
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
logEvent("onStart()");
|
logEvent("onStart()");
|
||||||
|
ApplicationDependencies.getShakeToReport().registerActivity(this);
|
||||||
super.onStart();
|
super.onStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,19 +78,39 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||||||
ActivityCompat.startActivity(this, intent, bundle);
|
ActivityCompat.startActivity(this, intent, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
@Override
|
||||||
protected void setStatusBarColor(int color) {
|
protected void attachBaseContext(@NonNull Context newBase) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
super.attachBaseContext(newBase);
|
||||||
getWindow().setStatusBarColor(color);
|
|
||||||
}
|
Configuration configuration = new Configuration(newBase.getResources().getConfiguration());
|
||||||
|
int appCompatNightMode = getDelegate().getLocalNightMode() != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED ? getDelegate().getLocalNightMode()
|
||||||
|
: AppCompatDelegate.getDefaultNightMode();
|
||||||
|
|
||||||
|
configuration.uiMode = (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | mapNightModeToConfigurationUiMode(newBase, appCompatNightMode);
|
||||||
|
|
||||||
|
applyOverrideConfiguration(configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context newBase) {
|
public void applyOverrideConfiguration(@NonNull Configuration overrideConfiguration) {
|
||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(newBase, TextSecurePreferences.getLanguage(newBase)));
|
DynamicLanguageContextWrapper.prepareOverrideConfiguration(this, overrideConfiguration);
|
||||||
|
super.applyOverrideConfiguration(overrideConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logEvent(@NonNull String event) {
|
private void logEvent(@NonNull String event) {
|
||||||
Log.d(TAG, "[" + Log.tag(getClass()) + "] " + event);
|
Log.d(TAG, "[" + Log.tag(getClass()) + "] " + event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final @NonNull ActionBar requireSupportActionBar() {
|
||||||
|
return Objects.requireNonNull(getSupportActionBar());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int mapNightModeToConfigurationUiMode(@NonNull Context context, @AppCompatDelegate.NightMode int appCompatNightMode) {
|
||||||
|
if (appCompatNightMode == AppCompatDelegate.MODE_NIGHT_YES) {
|
||||||
|
return Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
} else if (appCompatNightMode == AppCompatDelegate.MODE_NIGHT_NO) {
|
||||||
|
return Configuration.UI_MODE_NIGHT_NO;
|
||||||
|
}
|
||||||
|
return ConfigurationUtil.getNightModeConfiguration(context.getApplicationContext());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.view.View;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
||||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
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.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
@@ -21,17 +27,20 @@ import java.util.Locale;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface BindableConversationItem extends Unbindable {
|
public interface BindableConversationItem extends Unbindable {
|
||||||
void bind(@NonNull MessageRecord messageRecord,
|
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
|
@NonNull ConversationMessage messageRecord,
|
||||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||||
@NonNull GlideRequests glideRequests,
|
@NonNull GlideRequests glideRequests,
|
||||||
@NonNull Locale locale,
|
@NonNull Locale locale,
|
||||||
@NonNull Set<MessageRecord> batchSelected,
|
@NonNull Set<ConversationMessage> batchSelected,
|
||||||
@NonNull Recipient recipients,
|
@NonNull Recipient recipients,
|
||||||
@Nullable String searchQuery,
|
@Nullable String searchQuery,
|
||||||
boolean pulseHighlight);
|
boolean pulseMention,
|
||||||
|
boolean hasWallpaper,
|
||||||
|
boolean isMessageRequestAccepted);
|
||||||
|
|
||||||
MessageRecord getMessageRecord();
|
ConversationMessage getConversationMessage();
|
||||||
|
|
||||||
void setEventListener(@Nullable EventListener listener);
|
void setEventListener(@Nullable EventListener listener);
|
||||||
|
|
||||||
@@ -45,8 +54,21 @@ public interface BindableConversationItem extends Unbindable {
|
|||||||
void onAddToContactsClicked(@NonNull Contact contact);
|
void onAddToContactsClicked(@NonNull Contact contact);
|
||||||
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
|
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
|
||||||
void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
|
void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
|
||||||
void onReactionClicked(long messageId, boolean isMms);
|
void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms);
|
||||||
void onGroupMemberAvatarClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId);
|
void onGroupMemberClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId);
|
||||||
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
|
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
|
||||||
|
void onRegisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
|
||||||
|
void onUnregisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
|
||||||
|
void onVoiceNotePause(@NonNull Uri uri);
|
||||||
|
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double position);
|
||||||
|
void onVoiceNoteSeekTo(@NonNull Uri uri, double position);
|
||||||
|
void onGroupMigrationLearnMoreClicked(@NonNull GroupMigrationMembershipChange membershipChange);
|
||||||
|
void onDecryptionFailedLearnMoreClicked();
|
||||||
|
void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient);
|
||||||
|
void onJoinGroupCallClicked();
|
||||||
|
void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId);
|
||||||
|
|
||||||
|
/** @return true if handled, false if you want to let the normal url handling continue */
|
||||||
|
boolean onUrlClicked(@NonNull String url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ import androidx.lifecycle.Lifecycle;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
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;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
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.loaders.BlockedContactsLoader;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.preferences.BlockedContactListItem;
|
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
|
||||||
|
|
||||||
public class BlockedContactsActivity extends PassphraseRequiredActivity {
|
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPreCreate() {
|
|
||||||
dynamicTheme.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
getSupportActionBar().setTitle(R.string.BlockedContactsActivity_blocked_contacts);
|
|
||||||
initFragment(android.R.id.content, new BlockedContactsFragment());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dynamicTheme.onResume(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSupportNavigateUp() {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BlockedContactsFragment
|
|
||||||
extends ListFragment
|
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor>, ListView.OnItemClickListener
|
|
||||||
{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
|
||||||
return inflater.inflate(R.layout.blocked_contacts_fragment, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle) {
|
|
||||||
super.onCreate(bundle);
|
|
||||||
setListAdapter(new BlockedContactAdapter(requireActivity(), GlideApp.with(this), null));
|
|
||||||
LoaderManager.getInstance(this).initLoader(0, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle bundle) {
|
|
||||||
super.onActivityCreated(bundle);
|
|
||||||
getListView().setOnItemClickListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new BlockedContactsLoader(getActivity());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
|
|
||||||
if (getListAdapter() != null) {
|
|
||||||
((CursorAdapter) getListAdapter()).changeCursor(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
|
|
||||||
if (getListAdapter() != null) {
|
|
||||||
((CursorAdapter) getListAdapter()).changeCursor(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
Recipient recipient = ((BlockedContactListItem)view).getRecipient();
|
|
||||||
BlockUnblockDialog.showUnblockFor(requireContext(), getLifecycle(), recipient, () -> {
|
|
||||||
RecipientUtil.unblock(requireContext(), recipient);
|
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BlockedContactAdapter extends CursorAdapter {
|
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
|
||||||
|
|
||||||
BlockedContactAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, @Nullable Cursor c) {
|
|
||||||
super(context, c);
|
|
||||||
this.glideRequests = glideRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
|
||||||
return LayoutInflater.from(context)
|
|
||||||
.inflate(R.layout.blocked_contact_list_item, parent, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
|
||||||
RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RecipientDatabase.ID)));
|
|
||||||
LiveRecipient recipient = Recipient.live(recipientId);
|
|
||||||
|
|
||||||
((BlockedContactListItem) view).set(glideRequests, recipient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,26 +3,25 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
|
||||||
|
|
||||||
public class ClearProfileAvatarActivity extends Activity {
|
public final class ClearAvatarPromptActivity extends Activity {
|
||||||
|
|
||||||
private static final String ARG_TITLE = "arg_title";
|
private static final String ARG_TITLE = "arg_title";
|
||||||
|
|
||||||
public static Intent createForUserProfilePhoto() {
|
public static Intent createForUserProfilePhoto() {
|
||||||
return new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
|
Intent intent = new Intent(ApplicationDependencies.getApplication(), ClearAvatarPromptActivity.class);
|
||||||
|
intent.putExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_profile_photo);
|
||||||
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Intent createForGroupProfilePhoto() {
|
public static Intent createForGroupProfilePhoto() {
|
||||||
Intent intent = new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
|
Intent intent = new Intent(ApplicationDependencies.getApplication(), ClearAvatarPromptActivity.class);
|
||||||
intent.putExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_group_photo);
|
intent.putExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_group_photo);
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
@@ -31,10 +30,10 @@ public class ClearProfileAvatarActivity extends Activity {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
int titleId = getIntent().getIntExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_profile_photo);
|
int message = getIntent().getIntExtra(ARG_TITLE, 0);
|
||||||
|
|
||||||
new AlertDialog.Builder(new ContextThemeWrapper(this, DynamicTheme.isDarkTheme(this) ? R.style.TextSecure_DarkTheme : R.style.TextSecure_LightTheme))
|
new AlertDialog.Builder(new ContextThemeWrapper(this, DynamicTheme.isDarkTheme(this) ? R.style.TextSecure_DarkTheme : R.style.TextSecure_LightTheme))
|
||||||
.setMessage(titleId)
|
.setMessage(message)
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
|
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
|
||||||
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
|
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
@@ -13,9 +13,8 @@ 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.MessageDatabase;
|
||||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||||
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;
|
||||||
@@ -113,8 +112,8 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processOutgoingMessageRecord(MessageRecord messageRecord) {
|
private void processOutgoingMessageRecord(MessageRecord messageRecord) {
|
||||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
||||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
|
MessageDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
|
||||||
|
|
||||||
if (messageRecord.isMms()) {
|
if (messageRecord.isMms()) {
|
||||||
mmsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
mmsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
||||||
@@ -137,8 +136,8 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
|
|
||||||
private void processIncomingMessageRecord(MessageRecord messageRecord) {
|
private void processIncomingMessageRecord(MessageRecord messageRecord) {
|
||||||
try {
|
try {
|
||||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getContext());
|
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getContext());
|
||||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
||||||
|
|
||||||
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
||||||
mismatch.getRecipientId(getContext()),
|
mismatch.getRecipientId(getContext()),
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import android.os.Bundle;
|
|||||||
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
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.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
@@ -99,7 +99,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
|
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||||
contactsFragment.setOnContactSelectedListener(this);
|
|
||||||
contactsFragment.setOnRefreshListener(this);
|
contactsFragment.setOnRefreshListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +112,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactSelected(Optional<RecipientId> recipientId, String number) {}
|
public boolean onBeforeContactSelected(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) {}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.Manifest;
|
|||||||
import android.animation.LayoutTransition;
|
import android.animation.LayoutTransition;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -29,7 +30,6 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.CycleInterpolator;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.HorizontalScrollView;
|
import android.widget.HorizontalScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -55,7 +55,9 @@ import com.annimon.stream.Stream;
|
|||||||
import com.google.android.material.chip.ChipGroup;
|
import com.google.android.material.chip.ChipGroup;
|
||||||
import com.pnikosis.materialishprogress.ProgressWheel;
|
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.WarningTextView;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactChip;
|
import org.thoughtcrime.securesms.contacts.ContactChip;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||||
@@ -63,17 +65,18 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
|||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
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.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
||||||
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
@@ -83,7 +86,6 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,35 +106,42 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
public static final int NO_LIMIT = Integer.MAX_VALUE;
|
public static final int NO_LIMIT = Integer.MAX_VALUE;
|
||||||
|
|
||||||
public static final String DISPLAY_MODE = "display_mode";
|
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 REFRESHABLE = "refreshable";
|
||||||
public static final String RECENTS = "recents";
|
public static final String RECENTS = "recents";
|
||||||
public static final String TOTAL_CAPACITY = "total_capacity";
|
public static final String SELECTION_LIMITS = "selection_limits";
|
||||||
public static final String CURRENT_SELECTION = "current_selection";
|
public static final String CURRENT_SELECTION = "current_selection";
|
||||||
|
public static final String HIDE_COUNT = "hide_count";
|
||||||
|
public static final String CAN_SELECT_SELF = "can_select_self";
|
||||||
|
public static final String DISPLAY_CHIPS = "display_chips";
|
||||||
|
|
||||||
|
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 WarningTextView groupLimit;
|
||||||
|
private OnSelectionLimitReachedListener onSelectionLimitReachedListener;
|
||||||
|
|
||||||
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 headerAdapter;
|
||||||
@Nullable private FixedViewsAdapter footerAdapter;
|
@Nullable private FixedViewsAdapter footerAdapter;
|
||||||
@Nullable private ListCallback listCallback;
|
@Nullable private ListCallback listCallback;
|
||||||
@Nullable private ScrollCallback scrollCallback;
|
@Nullable private ScrollCallback scrollCallback;
|
||||||
private GlideRequests glideRequests;
|
private GlideRequests glideRequests;
|
||||||
private int selectionLimit;
|
private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS;
|
||||||
private Set<RecipientId> currentSelection;
|
private Set<RecipientId> currentSelection;
|
||||||
|
private boolean isMulti;
|
||||||
|
private boolean hideCount;
|
||||||
|
private boolean canSelectSelf;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
@@ -145,6 +154,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
if (context instanceof ScrollCallback) {
|
if (context instanceof ScrollCallback) {
|
||||||
scrollCallback = (ScrollCallback) context;
|
scrollCallback = (ScrollCallback) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context instanceof OnContactSelectedListener) {
|
||||||
|
onContactSelectedListener = (OnContactSelectedListener) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof OnSelectionLimitReachedListener) {
|
||||||
|
onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -207,9 +224,19 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
swipeRefresh.setEnabled(requireActivity().getIntent().getBooleanExtra(REFRESHABLE, true));
|
Intent intent = requireActivity().getIntent();
|
||||||
|
|
||||||
|
swipeRefresh.setEnabled(intent.getBooleanExtra(REFRESHABLE, true));
|
||||||
|
|
||||||
|
hideCount = intent.getBooleanExtra(HIDE_COUNT, false);
|
||||||
|
selectionLimit = intent.getParcelableExtra(SELECTION_LIMITS);
|
||||||
|
isMulti = selectionLimit != null;
|
||||||
|
canSelectSelf = intent.getBooleanExtra(CAN_SELECT_SELF, !isMulti);
|
||||||
|
|
||||||
|
if (!isMulti) {
|
||||||
|
selectionLimit = SelectionLimits.NO_LIMITS;
|
||||||
|
}
|
||||||
|
|
||||||
selectionLimit = requireActivity().getIntent().getIntExtra(TOTAL_CAPACITY, NO_LIMIT);
|
|
||||||
currentSelection = getCurrentSelection();
|
currentSelection = getCurrentSelection();
|
||||||
|
|
||||||
updateGroupLimit(getChipCount());
|
updateGroupLimit(getChipCount());
|
||||||
@@ -218,12 +245,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateGroupLimit(int chipCount) {
|
private void updateGroupLimit(int chipCount) {
|
||||||
if (selectionLimit != NO_LIMIT) {
|
int members = currentSelection.size() + chipCount;
|
||||||
groupLimit.setText(String.format(Locale.getDefault(), "%d/%d", currentSelection.size() + chipCount, selectionLimit));
|
groupLimit.setText(getResources().getQuantityString(R.plurals.ContactSelectionListFragment_d_members, members, members));
|
||||||
groupLimit.setVisibility(View.VISIBLE);
|
groupLimit.setVisibility(isMulti && !hideCount ? View.VISIBLE : View.GONE);
|
||||||
} else {
|
groupLimit.setWarning(selectionWarningLimitExceeded());
|
||||||
groupLimit.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -255,7 +280,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMulti() {
|
public boolean isMulti() {
|
||||||
return requireActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
|
return isMulti;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeCursor() {
|
private void initializeCursor() {
|
||||||
@@ -265,7 +290,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
glideRequests,
|
glideRequests,
|
||||||
null,
|
null,
|
||||||
new ListClickListener(),
|
new ListClickListener(),
|
||||||
isMulti(),
|
isMulti,
|
||||||
currentSelection);
|
currentSelection);
|
||||||
|
|
||||||
RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader();
|
RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader();
|
||||||
@@ -285,7 +310,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
recyclerView.setAdapter(concatenateAdapter);
|
recyclerView.setAdapter(concatenateAdapter);
|
||||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
|
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true, 0));
|
||||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
@@ -438,8 +463,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
swipeRefresh.setVisibility(View.VISIBLE);
|
swipeRefresh.setVisibility(View.VISIBLE);
|
||||||
reset();
|
reset();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
|
Context context = getContext();
|
||||||
initializeNoContactsPermission();
|
if (context != null) {
|
||||||
|
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
|
||||||
|
initializeNoContactsPermission();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
@@ -451,15 +479,18 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
||||||
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
||||||
|
|
||||||
if (isMulti() && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
if (!canSelectSelf && 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();
|
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMulti() || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) {
|
if (!isMulti || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) {
|
||||||
if (selectionLimitReached()) {
|
if (selectionHardLimitReached()) {
|
||||||
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_the_group_is_full, Toast.LENGTH_SHORT).show();
|
if (onSelectionLimitReachedListener != null) {
|
||||||
groupLimit.animate().scaleX(1.3f).scaleY(1.3f).setInterpolator(new CycleInterpolator(0.5f)).start();
|
onSelectionLimitReachedListener.onHardLimitReached(selectionLimit.getHardLimit());
|
||||||
|
} else {
|
||||||
|
GroupLimitDialog.showHardLimitMessage(requireContext());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,45 +504,61 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
if (uuid.isPresent()) {
|
if (uuid.isPresent()) {
|
||||||
Recipient recipient = Recipient.externalUsername(requireContext(), uuid.get(), contact.getNumber());
|
Recipient recipient = Recipient.externalUsername(requireContext(), uuid.get(), contact.getNumber());
|
||||||
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), contact.getNumber());
|
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), contact.getNumber());
|
||||||
markContactSelected(selected);
|
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
|
||||||
|
|
||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
onContactSelectedListener.onContactSelected(Optional.of(recipient.getId()), null);
|
if (onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null)) {
|
||||||
|
markContactSelected(selected);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markContactSelected(selected);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.ContactSelectionListFragment_username_not_found)
|
.setTitle(R.string.ContactSelectionListFragment_username_not_found)
|
||||||
.setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, contact.getNumber()))
|
.setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, contact.getNumber()))
|
||||||
.setPositiveButton(R.string.ContactSelectionListFragment_okay, (dialog, which) -> dialog.dismiss())
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
markContactSelected(selectedContact);
|
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
|
||||||
|
|
||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
onContactSelectedListener.onContactSelected(contact.getRecipientId(), contact.getNumber());
|
if (onContactSelectedListener.onBeforeContactSelected(contact.getRecipientId(), contact.getNumber())) {
|
||||||
|
markContactSelected(selectedContact);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markContactSelected(selectedContact);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
markContactUnselected(selectedContact);
|
markContactUnselected(selectedContact);
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
|
||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
|
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean selectionLimitReached() {
|
private boolean selectionHardLimitReached() {
|
||||||
return getChipCount() >= selectionLimit;
|
return getChipCount() + currentSelection.size() >= selectionLimit.getHardLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean selectionWarningLimitReachedExactly() {
|
||||||
|
return getChipCount() + currentSelection.size() == selectionLimit.getRecommendedLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean selectionWarningLimitExceeded() {
|
||||||
|
return getChipCount() + currentSelection.size() > selectionLimit.getRecommendedLimit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markContactSelected(@NonNull SelectedContact selectedContact) {
|
private void markContactSelected(@NonNull SelectedContact selectedContact) {
|
||||||
cursorRecyclerViewAdapter.addSelectedContact(selectedContact);
|
cursorRecyclerViewAdapter.addSelectedContact(selectedContact);
|
||||||
if (isMulti()) {
|
if (isMulti) {
|
||||||
addChipForSelectedContact(selectedContact);
|
addChipForSelectedContact(selectedContact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,6 +629,13 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
private void addChip(@NonNull ContactChip chip) {
|
private void addChip(@NonNull ContactChip chip) {
|
||||||
chipGroup.addView(chip);
|
chipGroup.addView(chip);
|
||||||
updateGroupLimit(getChipCount());
|
updateGroupLimit(getChipCount());
|
||||||
|
if (selectionWarningLimitReachedExactly()) {
|
||||||
|
if (onSelectionLimitReachedListener != null) {
|
||||||
|
onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit());
|
||||||
|
} else {
|
||||||
|
GroupLimitDialog.showRecommendedLimitMessage(requireContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getChipCount() {
|
private int getChipCount() {
|
||||||
@@ -602,6 +656,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setChipGroupVisibility(int visibility) {
|
private void setChipGroupVisibility(int visibility) {
|
||||||
|
if (!requireActivity().getIntent().getBooleanExtra(DISPLAY_CHIPS, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
|
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
|
||||||
|
|
||||||
ConstraintSet constraintSet = new ConstraintSet();
|
ConstraintSet constraintSet = new ConstraintSet();
|
||||||
@@ -610,24 +668,26 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
constraintSet.applyTo(constraintLayout);
|
constraintSet.applyTo(constraintLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
|
||||||
this.onContactSelectedListener = onContactSelectedListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
|
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
|
||||||
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void smoothScrollChipsToEnd() {
|
private void smoothScrollChipsToEnd() {
|
||||||
int x = chipGroupScrollContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? chipGroup.getWidth() : 0;
|
int x = ViewUtil.isLtr(chipGroupScrollContainer) ? chipGroup.getWidth() : 0;
|
||||||
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnContactSelectedListener {
|
public interface OnContactSelectedListener {
|
||||||
void onContactSelected(Optional<RecipientId> recipientId, String number);
|
/** @return True if the contact is allowed to be selected, otherwise false. */
|
||||||
|
boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number);
|
||||||
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface OnSelectionLimitReachedListener {
|
||||||
|
void onSuggestedLimitReached(int limit);
|
||||||
|
void onHardLimitReached(int limit);
|
||||||
|
}
|
||||||
|
|
||||||
public interface ListCallback {
|
public interface ListCallback {
|
||||||
void onInvite();
|
void onInvite();
|
||||||
void onNewGroup(boolean forceV1);
|
void onNewGroup(boolean forceV1);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.content.ServiceConnection;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -150,7 +151,7 @@ public class DatabaseMigrationActivity extends PassphraseRequiredActivity {
|
|||||||
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
||||||
} else {
|
} else {
|
||||||
// TODO [greyson] Navigation
|
// TODO [greyson] Navigation
|
||||||
startActivity(new Intent(this, MainActivity.class));
|
startActivity(MainActivity.clearTop(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +159,11 @@ public class DatabaseMigrationActivity extends PassphraseRequiredActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ImportStateHandler extends Handler {
|
private class ImportStateHandler extends Handler {
|
||||||
|
|
||||||
|
public ImportStateHandler() {
|
||||||
|
super(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
switch (message.what) {
|
switch (message.what) {
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ import android.widget.Toast;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
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.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.qr.ScanListener;
|
import org.thoughtcrime.securesms.qr.ScanListener;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.res.Configuration;
|
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 android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewAnimationUtils;
|
import android.view.ViewAnimationUtils;
|
||||||
@@ -15,6 +13,8 @@ import android.view.animation.DecelerateInterpolator;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView;
|
import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||||
import org.thoughtcrime.securesms.qr.ScanListener;
|
import org.thoughtcrime.securesms.qr.ScanListener;
|
||||||
import org.thoughtcrime.securesms.qr.ScanningThread;
|
import org.thoughtcrime.securesms.qr.ScanningThread;
|
||||||
@@ -32,9 +32,9 @@ public class DeviceAddFragment extends LoggingFragment {
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
||||||
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
|
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
|
||||||
this.overlay = ViewUtil.findById(this.container, R.id.overlay);
|
this.overlay = this.container.findViewById(R.id.overlay);
|
||||||
this.scannerView = ViewUtil.findById(this.container, R.id.scanner);
|
this.scannerView = this.container.findViewById(R.id.scanner);
|
||||||
this.devicesImage = ViewUtil.findById(this.container, R.id.devices);
|
this.devicesImage = this.container.findViewById(R.id.devices);
|
||||||
|
|
||||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
|
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
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.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
public class DeviceLinkFragment extends Fragment implements View.OnClickListener {
|
public class DeviceLinkFragment extends Fragment implements View.OnClickListener {
|
||||||
|
|
||||||
private LinearLayout container;
|
private LinearLayout container;
|
||||||
|
|||||||
@@ -6,15 +6,6 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.ListFragment;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
||||||
import org.thoughtcrime.securesms.devicelist.Device;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -24,12 +15,20 @@ import android.widget.Button;
|
|||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.ListFragment;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
import com.melnykov.fab.FloatingActionButton;
|
import com.melnykov.fab.FloatingActionButton;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.devicelist.Device;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -68,7 +67,7 @@ public class DeviceListFragment extends ListFragment
|
|||||||
|
|
||||||
this.empty = view.findViewById(R.id.empty);
|
this.empty = view.findViewById(R.id.empty);
|
||||||
this.progressContainer = view.findViewById(R.id.progress_container);
|
this.progressContainer = view.findViewById(R.id.progress_container);
|
||||||
this.addDeviceButton = ViewUtil.findById(view, R.id.add_device);
|
this.addDeviceButton = view.findViewById(R.id.add_device);
|
||||||
this.addDeviceButton.setOnClickListener(this);
|
this.addDeviceButton.setOnClickListener(this);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public final class GroupMembersDialog {
|
|||||||
public void display() {
|
public void display() {
|
||||||
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
|
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
|
||||||
.setTitle(R.string.ConversationActivity_group_members)
|
.setTitle(R.string.ConversationActivity_group_members)
|
||||||
.setIconAttribute(R.attr.group_members_dialog_icon)
|
.setIcon(R.drawable.ic_group_24)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setView(R.layout.dialog_group_members)
|
.setView(R.layout.dialog_group_members)
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChange
|
|||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||||
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.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
@@ -34,13 +35,13 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
|||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
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 PassphraseRequiredActivity implements ContactSelectionListFragment.OnContactSelectedListener {
|
public class InviteActivity extends PassphraseRequiredActivity implements ContactSelectionListFragment.OnContactSelectedListener {
|
||||||
@@ -63,7 +64,8 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS);
|
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS);
|
||||||
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
|
getIntent().putExtra(ContactSelectionListFragment.SELECTION_LIMITS, SelectionLimits.NO_LIMITS);
|
||||||
|
getIntent().putExtra(ContactSelectionListFragment.HIDE_COUNT, true);
|
||||||
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
||||||
|
|
||||||
setContentView(R.layout.invite_activity);
|
setContentView(R.layout.invite_activity);
|
||||||
@@ -92,26 +94,32 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
|
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
|
||||||
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
|
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
|
||||||
|
|
||||||
View shareButton = ViewUtil.findById(this, R.id.share_button);
|
View shareButton = findViewById(R.id.share_button);
|
||||||
View smsButton = ViewUtil.findById(this, R.id.sms_button);
|
Button smsButton = findViewById(R.id.sms_button);
|
||||||
Button smsCancelButton = ViewUtil.findById(this, R.id.cancel_sms_button);
|
Button smsCancelButton = findViewById(R.id.cancel_sms_button);
|
||||||
ContactFilterToolbar contactFilter = ViewUtil.findById(this, R.id.contact_filter);
|
ContactFilterToolbar contactFilter = findViewById(R.id.contact_filter);
|
||||||
|
|
||||||
inviteText = ViewUtil.findById(this, R.id.invite_text);
|
inviteText = findViewById(R.id.invite_text);
|
||||||
smsSendFrame = ViewUtil.findById(this, R.id.sms_send_frame);
|
smsSendFrame = findViewById(R.id.sms_send_frame);
|
||||||
smsSendButton = ViewUtil.findById(this, R.id.send_sms_button);
|
smsSendButton = findViewById(R.id.send_sms_button);
|
||||||
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||||
|
|
||||||
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
|
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
|
||||||
updateSmsButtonText();
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||||
|
|
||||||
contactsFragment.setOnContactSelectedListener(this);
|
|
||||||
shareButton.setOnClickListener(new ShareClickListener());
|
|
||||||
smsButton.setOnClickListener(new SmsClickListener());
|
|
||||||
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
|
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
|
||||||
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
||||||
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
||||||
contactFilter.setNavigationIcon(R.drawable.ic_search_conversation_24);
|
contactFilter.setNavigationIcon(R.drawable.ic_search_conversation_24);
|
||||||
|
|
||||||
|
if (Util.isDefaultSmsProvider(this)) {
|
||||||
|
shareButton.setOnClickListener(new ShareClickListener());
|
||||||
|
smsButton.setOnClickListener(new SmsClickListener());
|
||||||
|
} else {
|
||||||
|
shareButton.setVisibility(View.GONE);
|
||||||
|
smsButton.setOnClickListener(new ShareClickListener());
|
||||||
|
smsButton.setText(R.string.InviteActivity_share);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Animation loadAnimation(@AnimRes int animResId) {
|
private Animation loadAnimation(@AnimRes int animResId) {
|
||||||
@@ -121,13 +129,14 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
|
public boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||||
updateSmsButtonText();
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size() + 1);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
||||||
updateSmsButtonText();
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendSmsInvites() {
|
private void sendSmsInvites() {
|
||||||
@@ -137,12 +146,11 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
.toArray(new SelectedContact[0]));
|
.toArray(new SelectedContact[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSmsButtonText() {
|
private void updateSmsButtonText(int count) {
|
||||||
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,
|
||||||
selectedContacts.size(),
|
count,
|
||||||
selectedContacts.size()));
|
count));
|
||||||
smsSendButton.setEnabled(!selectedContacts.isEmpty());
|
smsSendButton.setEnabled(count > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onBackPressed() {
|
@Override public void onBackPressed() {
|
||||||
@@ -156,17 +164,17 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
private void cancelSmsSelection() {
|
private void cancelSmsSelection() {
|
||||||
setPrimaryColorsToolbarNormal();
|
setPrimaryColorsToolbarNormal();
|
||||||
contactsFragment.reset();
|
contactsFragment.reset();
|
||||||
updateSmsButtonText();
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||||
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE);
|
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPrimaryColorsToolbarNormal() {
|
private void setPrimaryColorsToolbarNormal() {
|
||||||
primaryToolbar.setBackgroundColor(0);
|
primaryToolbar.setBackgroundColor(0);
|
||||||
primaryToolbar.getNavigationIcon().setColorFilter(null);
|
primaryToolbar.getNavigationIcon().setColorFilter(null);
|
||||||
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.title_text_color_primary));
|
primaryToolbar.setTitleTextColor(ContextCompat.getColor(this, R.color.signal_text_primary));
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
getWindow().setStatusBarColor(ThemeUtil.getThemedColor(this, android.R.attr.statusBarColor));
|
WindowUtil.setStatusBarColor(getWindow(), ThemeUtil.getThemedColor(this, android.R.attr.statusBarColor));
|
||||||
getWindow().setNavigationBarColor(ThemeUtil.getThemedColor(this, android.R.attr.navigationBarColor));
|
getWindow().setNavigationBarColor(ThemeUtil.getThemedColor(this, android.R.attr.navigationBarColor));
|
||||||
WindowUtil.setLightStatusBarFromTheme(this);
|
WindowUtil.setLightStatusBarFromTheme(this);
|
||||||
}
|
}
|
||||||
@@ -176,11 +184,11 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
|
|
||||||
private void setPrimaryColorsToolbarForSms() {
|
private void setPrimaryColorsToolbarForSms() {
|
||||||
primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.core_ultramarine));
|
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(ContextCompat.getColor(this, R.color.signal_text_toolbar_subtitle), PorterDuff.Mode.SRC_IN);
|
||||||
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
|
primaryToolbar.setTitleTextColor(ContextCompat.getColor(this, R.color.signal_text_toolbar_title));
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.core_ultramarine));
|
WindowUtil.setStatusBarColor(getWindow(), ContextCompat.getColor(this, R.color.core_ultramarine));
|
||||||
WindowUtil.clearLightStatusBar(getWindow());
|
WindowUtil.clearLightStatusBar(getWindow());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
app/src/main/java/org/thoughtcrime/securesms/KbsEnclave.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in our {@link BuildConfig} to tie together the various attributes of a KBS instance. This
|
||||||
|
* is sitting in the root directory so it can be accessed by the build config.
|
||||||
|
*/
|
||||||
|
public final class KbsEnclave {
|
||||||
|
|
||||||
|
private final String enclaveName;
|
||||||
|
private final String serviceId;
|
||||||
|
private final String mrEnclave;
|
||||||
|
|
||||||
|
public KbsEnclave(@NonNull String enclaveName, @NonNull String serviceId, @NonNull String mrEnclave) {
|
||||||
|
this.enclaveName = enclaveName;
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.mrEnclave = mrEnclave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getMrEnclave() {
|
||||||
|
return mrEnclave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getEnclaveName() {
|
||||||
|
return enclaveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
KbsEnclave enclave = (KbsEnclave) o;
|
||||||
|
return enclaveName.equals(enclave.enclaveName) &&
|
||||||
|
serviceId.equals(enclave.serviceId) &&
|
||||||
|
mrEnclave.equals(enclave.mrEnclave);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(enclaveName, serviceId, mrEnclave);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simply logs out lifecycle events.
|
* Simply logs out lifecycle events.
|
||||||
|
|||||||
@@ -1,23 +1,63 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
|
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||||
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
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 PassphraseRequiredActivity {
|
public class MainActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
|
public static final int RESULT_CONFIG_CHANGED = Activity.RESULT_FIRST_USER + 901;
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
public static @NonNull Intent clearTop(@NonNull Context context) {
|
||||||
|
Intent intent = new Intent(context, MainActivity.class);
|
||||||
|
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
|
AppStartup.getInstance().onCriticalRenderEventStart();
|
||||||
super.onCreate(savedInstanceState, ready);
|
super.onCreate(savedInstanceState, ready);
|
||||||
setContentView(R.layout.main_activity);
|
setContentView(R.layout.main_activity);
|
||||||
|
|
||||||
navigator.onCreate(savedInstanceState);
|
navigator.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
handleGroupLinkInIntent(getIntent());
|
||||||
|
handleProxyInIntent(getIntent());
|
||||||
|
|
||||||
|
CachedInflater.from(this).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getIntent() {
|
||||||
|
return super.getIntent().setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
handleGroupLinkInIntent(intent);
|
||||||
|
handleProxyInIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -39,7 +79,29 @@ public class MainActivity extends PassphraseRequiredActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (requestCode == MainNavigator.REQUEST_CONFIG_CHANGES && resultCode == RESULT_CONFIG_CHANGED) {
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public @NonNull MainNavigator getNavigator() {
|
public @NonNull MainNavigator getNavigator() {
|
||||||
return navigator;
|
return navigator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleGroupLinkInIntent(Intent intent) {
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
CommunicationActions.handlePotentialGroupLinkUrl(this, data.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleProxyInIntent(Intent intent) {
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
CommunicationActions.handlePotentialProxyLinkUrl(this, data.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||||
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.groups.ui.creategroup.CreateGroupActivity;
|
||||||
@@ -18,6 +18,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
|||||||
|
|
||||||
public class MainNavigator {
|
public class MainNavigator {
|
||||||
|
|
||||||
|
public static final int REQUEST_CONFIG_CHANGES = 901;
|
||||||
|
|
||||||
private final MainActivity activity;
|
private final MainActivity activity;
|
||||||
|
|
||||||
public MainNavigator(@NonNull MainActivity activity) {
|
public MainNavigator(@NonNull MainActivity activity) {
|
||||||
@@ -57,7 +59,10 @@ public class MainNavigator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, int startingPosition) {
|
public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, int startingPosition) {
|
||||||
Intent intent = ConversationActivity.buildIntent(activity, recipientId, threadId, distributionType, startingPosition);
|
Intent intent = ConversationIntents.createBuilder(activity, recipientId, threadId)
|
||||||
|
.withDistributionType(distributionType)
|
||||||
|
.withStartingPosition(startingPosition)
|
||||||
|
.build();
|
||||||
|
|
||||||
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);
|
||||||
@@ -65,10 +70,9 @@ public class MainNavigator {
|
|||||||
|
|
||||||
public void goToAppSettings() {
|
public void goToAppSettings() {
|
||||||
Intent intent = new Intent(activity, ApplicationPreferencesActivity.class);
|
Intent intent = new Intent(activity, ApplicationPreferencesActivity.class);
|
||||||
activity.startActivity(intent);
|
activity.startActivityForResult(intent, REQUEST_CONFIG_CHANGES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void goToArchiveList() {
|
public void goToArchiveList() {
|
||||||
getFragmentManager().beginTransaction()
|
getFragmentManager().beginTransaction()
|
||||||
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
|
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.ContentObserver;
|
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.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -31,14 +33,14 @@ import android.view.MenuInflater;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.core.app.ShareCompat;
|
||||||
import androidx.core.util.Pair;
|
import androidx.core.util.Pair;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
@@ -51,26 +53,29 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.animation.DepthPageTransformer;
|
import org.thoughtcrime.securesms.animation.DepthPageTransformer;
|
||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||||
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
|
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||||
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
|
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment;
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
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.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.FullscreenHelper;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||||
|
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -119,6 +124,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
private boolean cameFromAllMedia;
|
private boolean cameFromAllMedia;
|
||||||
private boolean showThread;
|
private boolean showThread;
|
||||||
private MediaDatabase.Sorting sorting;
|
private MediaDatabase.Sorting sorting;
|
||||||
|
private FullscreenHelper fullscreenHelper;
|
||||||
|
|
||||||
private @Nullable Cursor cursor = null;
|
private @Nullable Cursor cursor = null;
|
||||||
|
|
||||||
@@ -133,10 +139,16 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, attachment.getSize());
|
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, attachment.getSize());
|
||||||
intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, attachment.getCaption());
|
intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, attachment.getCaption());
|
||||||
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, leftIsRecent);
|
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, leftIsRecent);
|
||||||
intent.setDataAndType(attachment.getDataUri(), mediaRecord.getContentType());
|
intent.setDataAndType(attachment.getUri(), mediaRecord.getContentType());
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(@NonNull Context newBase) {
|
||||||
|
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||||
|
super.attachBaseContext(newBase);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle, boolean ready) {
|
protected void onCreate(Bundle bundle, boolean ready) {
|
||||||
@@ -147,10 +159,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
|
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
|
||||||
|
|
||||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
fullscreenHelper = new FullscreenHelper(this);
|
||||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
|
|
||||||
showSystemUI();
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
@@ -196,7 +205,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
if (threadRecipient != null) {
|
if (threadRecipient != null) {
|
||||||
if (mediaItem.outgoing || threadRecipient.isGroup()) {
|
if (mediaItem.outgoing || threadRecipient.isGroup()) {
|
||||||
if (threadRecipient.isLocalNumber()) {
|
if (threadRecipient.isSelf()) {
|
||||||
from = getString(R.string.note_to_self);
|
from = getString(R.string.note_to_self);
|
||||||
} else {
|
} else {
|
||||||
to = threadRecipient.getDisplayName(this);
|
to = threadRecipient.getDisplayName(this);
|
||||||
@@ -261,6 +270,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
albumRail = findViewById(R.id.media_preview_album_rail);
|
albumRail = findViewById(R.id.media_preview_album_rail);
|
||||||
albumRailAdapter = new MediaRailAdapter(GlideApp.with(this), this, false);
|
albumRailAdapter = new MediaRailAdapter(GlideApp.with(this), this, false);
|
||||||
|
|
||||||
|
albumRail.setItemAnimator(null); // Or can crash when set to INVISIBLE while animating by FullscreenHelper https://issuetracker.google.com/issues/148720682
|
||||||
albumRail.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
|
albumRail.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
|
||||||
albumRail.setAdapter(albumRailAdapter);
|
albumRail.setAdapter(albumRailAdapter);
|
||||||
|
|
||||||
@@ -273,9 +283,9 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
anchorMarginsToBottomInsets(detailsContainer);
|
anchorMarginsToBottomInsets(detailsContainer);
|
||||||
|
|
||||||
anchorMarginsToTopInsets(toolbarLayout);
|
fullscreenHelper.configureToolbarSpacer(findViewById(R.id.toolbar_cutout_spacer));
|
||||||
|
|
||||||
showAndHideWithSystemUI(getWindow(), detailsContainer, toolbarLayout);
|
fullscreenHelper.showAndHideWithSystemUI(getWindow(), detailsContainer, toolbarLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
@@ -379,6 +389,27 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void share() {
|
||||||
|
MediaItem mediaItem = getCurrentMediaItem();
|
||||||
|
|
||||||
|
if (mediaItem != null) {
|
||||||
|
Uri publicUri = PartAuthority.getAttachmentPublicUri(mediaItem.uri);
|
||||||
|
String mimeType = Intent.normalizeMimeType(mediaItem.type);
|
||||||
|
Intent shareIntent = ShareCompat.IntentBuilder.from(this)
|
||||||
|
.setStream(publicUri)
|
||||||
|
.setType(mimeType)
|
||||||
|
.createChooserIntent()
|
||||||
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(shareIntent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.w(TAG, "No activity existed to share the media.", e);
|
||||||
|
Toast.makeText(this, R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("CodeBlock2Expr")
|
@SuppressWarnings("CodeBlock2Expr")
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
private void saveToDisk() {
|
private void saveToDisk() {
|
||||||
@@ -386,21 +417,30 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
if (mediaItem != null) {
|
if (mediaItem != null) {
|
||||||
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
|
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
|
||||||
|
if (StorageUtil.canWriteToMediaStore()) {
|
||||||
|
performSavetoDisk(mediaItem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Permissions.with(this)
|
Permissions.with(this)
|
||||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
|
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
.ifNecessary()
|
.ifNecessary()
|
||||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||||
.onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
|
.onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
|
||||||
.onAllGranted(() -> {
|
.onAllGranted(() -> {
|
||||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
|
performSavetoDisk(mediaItem);
|
||||||
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
|
|
||||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
|
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void performSavetoDisk(@NonNull MediaItem mediaItem) {
|
||||||
|
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
|
||||||
|
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
|
||||||
|
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private void deleteMedia() {
|
private void deleteMedia() {
|
||||||
MediaItem mediaItem = getCurrentMediaItem();
|
MediaItem mediaItem = getCurrentMediaItem();
|
||||||
@@ -409,7 +449,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setIconAttribute(R.attr.dialog_alert_icon);
|
builder.setIcon(R.drawable.ic_warning);
|
||||||
builder.setTitle(R.string.MediaPreviewActivity_media_delete_confirmation_title);
|
builder.setTitle(R.string.MediaPreviewActivity_media_delete_confirmation_title);
|
||||||
builder.setMessage(R.string.MediaPreviewActivity_media_delete_confirmation_message);
|
builder.setMessage(R.string.MediaPreviewActivity_media_delete_confirmation_message);
|
||||||
builder.setCancelable(true);
|
builder.setCancelable(true);
|
||||||
@@ -431,36 +471,45 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
super.onPrepareOptionsMenu(menu);
|
|
||||||
|
|
||||||
menu.clear();
|
menu.clear();
|
||||||
MenuInflater inflater = this.getMenuInflater();
|
MenuInflater inflater = this.getMenuInflater();
|
||||||
inflater.inflate(R.menu.media_preview, menu);
|
inflater.inflate(R.menu.media_preview, menu);
|
||||||
|
|
||||||
|
super.onCreateOptionsMenu(menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
if (!isMediaInDb()) {
|
if (!isMediaInDb()) {
|
||||||
menu.findItem(R.id.media_preview__overview).setVisible(false);
|
menu.findItem(R.id.media_preview__overview).setVisible(false);
|
||||||
menu.findItem(R.id.delete).setVisible(false);
|
menu.findItem(R.id.delete).setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restricted to API26 because of MemoryFileUtil not supporting lower API levels well
|
||||||
|
menu.findItem(R.id.media_preview__share).setVisible(Build.VERSION.SDK_INT >= 26);
|
||||||
|
|
||||||
if (cameFromAllMedia) {
|
if (cameFromAllMedia) {
|
||||||
menu.findItem(R.id.media_preview__overview).setVisible(false);
|
menu.findItem(R.id.media_preview__overview).setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onPrepareOptionsMenu(menu);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
super.onOptionsItemSelected(item);
|
super.onOptionsItemSelected(item);
|
||||||
|
|
||||||
switch (item.getItemId()) {
|
int itemId = item.getItemId();
|
||||||
case R.id.media_preview__overview: showOverview(); return true;
|
|
||||||
case R.id.media_preview__forward: forward(); return true;
|
if (itemId == R.id.media_preview__overview) { showOverview(); return true; }
|
||||||
case R.id.save: saveToDisk(); return true;
|
if (itemId == R.id.media_preview__forward) { forward(); return true; }
|
||||||
case R.id.delete: deleteMedia(); return true;
|
if (itemId == R.id.media_preview__share) { share(); return true; }
|
||||||
case android.R.id.home: finish(); return true;
|
if (itemId == R.id.save) { saveToDisk(); return true; }
|
||||||
}
|
if (itemId == R.id.delete) { deleteMedia(); return true; }
|
||||||
|
if (itemId == android.R.id.home) { finish(); return true; }
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -541,7 +590,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean singleTapOnMedia() {
|
public boolean singleTapOnMedia() {
|
||||||
toggleUiVisibility();
|
fullscreenHelper.toggleUiVisibility();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,32 +600,6 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewPagerListener extends ExtendedOnPageChangedListener {
|
private class ViewPagerListener extends ExtendedOnPageChangedListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -692,33 +715,6 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void anchorMarginsToTopInsets(@NonNull View viewToAnchor) {
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(viewToAnchor, (view, insets) -> {
|
|
||||||
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
|
||||||
|
|
||||||
layoutParams.setMargins(insets.getSystemWindowInsetLeft(),
|
|
||||||
insets.getSystemWindowInsetTop(),
|
|
||||||
insets.getSystemWindowInsetRight(),
|
|
||||||
layoutParams.bottomMargin);
|
|
||||||
|
|
||||||
view.setLayoutParams(layoutParams);
|
|
||||||
|
|
||||||
return insets;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 static class CursorPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
|
private static class CursorPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
|
||||||
|
|
||||||
@SuppressLint("UseSparseArrays")
|
@SuppressLint("UseSparseArrays")
|
||||||
@@ -796,7 +792,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
return new MediaItem(Recipient.live(recipientId).get(),
|
return new MediaItem(Recipient.live(recipientId).get(),
|
||||||
Recipient.live(threadRecipientId).get(),
|
Recipient.live(threadRecipientId).get(),
|
||||||
attachment,
|
attachment,
|
||||||
Objects.requireNonNull(attachment.getDataUri()),
|
Objects.requireNonNull(attachment.getUri()),
|
||||||
mediaRecord.getContentType(),
|
mediaRecord.getContentType(),
|
||||||
mediaRecord.getDate(),
|
mediaRecord.getDate(),
|
||||||
mediaRecord.isOutgoing());
|
mediaRecord.isOutgoing());
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
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.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|||||||
@@ -23,16 +23,15 @@ import android.view.MenuItem;
|
|||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
|
||||||
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
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.FeatureFlags;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
@@ -60,21 +59,22 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
|
public boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||||
if (recipientId.isPresent()) {
|
if (recipientId.isPresent()) {
|
||||||
launch(Recipient.resolved(recipientId.get()));
|
launch(Recipient.resolved(recipientId.get()));
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.");
|
Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.");
|
||||||
if (FeatureFlags.cds() && NetworkConstraint.isMet(this)) {
|
|
||||||
Log.i(TAG, "[onContactSelected] CDS enabled. Doing contact refresh.");
|
if (TextSecurePreferences.isPushRegistered(this) && NetworkConstraint.isMet(this)) {
|
||||||
|
Log.i(TAG, "[onContactSelected] Doing contact refresh.");
|
||||||
|
|
||||||
AlertDialog progress = SimpleProgressDialog.show(this);
|
AlertDialog progress = SimpleProgressDialog.show(this);
|
||||||
|
|
||||||
SimpleTask.run(getLifecycle(), () -> {
|
SimpleTask.run(getLifecycle(), () -> {
|
||||||
Recipient resolved = Recipient.external(this, number);
|
Recipient resolved = Recipient.external(this, number);
|
||||||
|
|
||||||
if (!resolved.isRegistered()) {
|
if (!resolved.isRegistered() || !resolved.hasUuid()) {
|
||||||
Log.i(TAG, "[onContactSelected] Not registered. Doing a directory refresh.");
|
Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.");
|
||||||
try {
|
try {
|
||||||
DirectoryHelper.refreshDirectoryFor(this, resolved, false);
|
DirectoryHelper.refreshDirectoryFor(this, resolved, false);
|
||||||
resolved = Recipient.resolved(resolved.getId());
|
resolved = Recipient.resolved(resolved.getId());
|
||||||
@@ -92,18 +92,18 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
launch(Recipient.external(this, number));
|
launch(Recipient.external(this, number));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launch(Recipient recipient) {
|
private void launch(Recipient recipient) {
|
||||||
Intent intent = new Intent(this, ConversationActivity.class);
|
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient.getId());
|
||||||
intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId());
|
Intent intent = ConversationIntents.createBuilder(this, recipient.getId(), existingThread)
|
||||||
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA));
|
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
|
||||||
intent.setDataAndType(getIntent().getData(), getIntent().getType());
|
.withDataUri(getIntent().getData())
|
||||||
|
.withDataType(getIntent().getType())
|
||||||
|
.build();
|
||||||
|
|
||||||
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);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@@ -136,11 +136,11 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
menu.clear();
|
menu.clear();
|
||||||
getMenuInflater().inflate(R.menu.new_conversation_activity, menu);
|
getMenuInflater().inflate(R.menu.new_conversation_activity, menu);
|
||||||
|
|
||||||
super.onPrepareOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ import android.content.Context;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
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;
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import android.os.Bundle;
|
|||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
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.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,12 +64,6 @@ public class PassphraseCreateActivity extends PassphraseActivity {
|
|||||||
IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this);
|
IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this);
|
||||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
||||||
|
|
||||||
TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCanonicalVersionCode());
|
|
||||||
TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true);
|
|
||||||
TextSecurePreferences.setReadReceiptsEnabled(PassphraseCreateActivity.this, true);
|
|
||||||
TextSecurePreferences.setTypingIndicatorsEnabled(PassphraseCreateActivity.this, true);
|
|
||||||
TextSecurePreferences.setHasSeenWelcomeScreen(PassphraseCreateActivity.this, false);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,16 +24,12 @@ import android.content.Intent;
|
|||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
|
||||||
import androidx.core.os.CancellationSignal;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.RelativeSizeSpan;
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.text.style.TypefaceSpan;
|
import android.text.style.TypefaceSpan;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -50,14 +46,21 @@ import android.widget.ImageButton;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
|
import androidx.core.os.CancellationSignal;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
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.logsubmit.SubmitDebugLogActivity;
|
||||||
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
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.SupportEmailUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,20 +135,25 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
MenuInflater inflater = this.getMenuInflater();
|
MenuInflater inflater = this.getMenuInflater();
|
||||||
menu.clear();
|
menu.clear();
|
||||||
|
|
||||||
inflater.inflate(R.menu.log_submit, menu);
|
inflater.inflate(R.menu.passphrase_prompt, menu);
|
||||||
super.onPrepareOptionsMenu(menu);
|
|
||||||
|
super.onCreateOptionsMenu(menu);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
super.onOptionsItemSelected(item);
|
super.onOptionsItemSelected(item);
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.menu_submit_debug_logs) {
|
||||||
case R.id.menu_submit_debug_logs: handleLogSubmit(); return true;
|
handleLogSubmit();
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_contact_support) {
|
||||||
|
sendEmailToSupport();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -292,6 +300,17 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendEmailToSupport() {
|
||||||
|
String body = SupportEmailUtil.generateSupportEmailBody(this,
|
||||||
|
R.string.PassphrasePromptActivity_signal_android_lock_screen,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
CommunicationActions.openEmail(this,
|
||||||
|
SupportEmailUtil.getSupportEmailAddress(this),
|
||||||
|
getString(R.string.PassphrasePromptActivity_signal_android_lock_screen),
|
||||||
|
body);
|
||||||
|
}
|
||||||
|
|
||||||
private class PassphraseActionListener implements TextView.OnEditorActionListener {
|
private class PassphraseActionListener implements TextView.OnEditorActionListener {
|
||||||
@Override
|
@Override
|
||||||
public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent keyEvent) {
|
public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent keyEvent) {
|
||||||
|
|||||||