Compare commits
1009 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9514d0b94 | ||
|
|
a9dab90a1e | ||
|
|
39709c8d64 | ||
|
|
c2a6963a6d | ||
|
|
bfdebbfa5d | ||
|
|
167a691018 | ||
|
|
8004565c84 | ||
|
|
a101dc4fd1 | ||
|
|
57f730d8ee | ||
|
|
3543cc80ba | ||
|
|
71613d9db1 | ||
|
|
4a0e6a3eb2 | ||
|
|
f1a87518e1 | ||
|
|
61f880fd78 | ||
|
|
09904e7a16 | ||
|
|
94658e9090 | ||
|
|
a47448b6c6 | ||
|
|
7e4b9b685a | ||
|
|
64922a8e51 | ||
|
|
f65f4704c9 | ||
|
|
b04ca202f6 | ||
|
|
83086a5a2b | ||
|
|
51a521594f | ||
|
|
0a7a7cf5a9 | ||
|
|
6bd689504c | ||
|
|
efec40ff57 | ||
|
|
69716dde4a | ||
|
|
e90fa05d60 | ||
|
|
580c000bda | ||
|
|
2f3d04d3e8 | ||
|
|
bf37d412e9 | ||
|
|
fd115ebb72 | ||
|
|
b9657208fe | ||
|
|
5d6d78a51e | ||
|
|
916006e664 | ||
|
|
55c69cd50a | ||
|
|
14565b0864 | ||
|
|
a157c1ae1d | ||
|
|
a4d458f969 | ||
|
|
3f53abedab | ||
|
|
68a2d5ed20 | ||
|
|
35e9e31a7b | ||
|
|
444d947743 | ||
|
|
c427dbad08 | ||
|
|
2cefe813e4 | ||
|
|
123ffe42c3 | ||
|
|
da20e66ecd | ||
|
|
901440017a | ||
|
|
0be76a37fe | ||
|
|
36dadc8777 | ||
|
|
182749c101 | ||
|
|
d9228bd911 | ||
|
|
a361fcc8f3 | ||
|
|
ff4f0b9f42 | ||
|
|
060dffc9cc | ||
|
|
172cc302fc | ||
|
|
416e62112f | ||
|
|
e584a90f81 | ||
|
|
9876ffb5e4 | ||
|
|
53e10f2cad | ||
|
|
cb79f75ac1 | ||
|
|
5ec9c1cd90 | ||
|
|
1f28a30ace | ||
|
|
7715917436 | ||
|
|
f79b445fdf | ||
|
|
14484deabe | ||
|
|
3ac395d33e | ||
|
|
f83b520ca9 | ||
|
|
0123f9aa87 | ||
|
|
06b64fe619 | ||
|
|
1bb87834d8 | ||
|
|
ae4167ddae | ||
|
|
383beafdef | ||
|
|
062e88b24f | ||
|
|
8299d49042 | ||
|
|
4677883838 | ||
|
|
7f0a0bef5a | ||
|
|
acc825971b | ||
|
|
62040d06b4 | ||
|
|
0921ebe5f1 | ||
|
|
3d0e15e2b8 | ||
|
|
5372f79c40 | ||
|
|
92e8f9de0e | ||
|
|
c3cf846a10 | ||
|
|
5826b0c068 | ||
|
|
2d7c043398 | ||
|
|
e20d6b63cf | ||
|
|
b85c5eb54a | ||
|
|
a1c8573fad | ||
|
|
90a27d2227 | ||
|
|
c54c6018b2 | ||
|
|
7419570f94 | ||
|
|
8860f792c4 | ||
|
|
e47db0d532 | ||
|
|
ab5d3badc2 | ||
|
|
fce362960f | ||
|
|
5bf23dcfb3 | ||
|
|
65c7dc6ca2 | ||
|
|
e30a8b6954 | ||
|
|
838e318200 | ||
|
|
62ee411901 | ||
|
|
ceefd2d92f | ||
|
|
e3870f5656 | ||
|
|
6e7022ab70 | ||
|
|
031d1551e7 | ||
|
|
6755b25361 | ||
|
|
11d0a73675 | ||
|
|
44119b6437 | ||
|
|
d4a3b442f4 | ||
|
|
aba5774446 | ||
|
|
911dd9efb1 | ||
|
|
f2a490b07e | ||
|
|
5675f080f2 | ||
|
|
f0dbe230b5 | ||
|
|
8b81800052 | ||
|
|
f598c14298 | ||
|
|
58b070e6e3 | ||
|
|
71c92a1c90 | ||
|
|
b86acb9773 | ||
|
|
1b8758b657 | ||
|
|
ed4bab1b8b | ||
|
|
a71fe0fd75 | ||
|
|
3d2a634aac | ||
|
|
01047f0546 | ||
|
|
9dac5691f0 | ||
|
|
3c489ad247 | ||
|
|
7797351341 | ||
|
|
f7212b9916 | ||
|
|
93bb49dc16 | ||
|
|
e504c490c8 | ||
|
|
42e865813c | ||
|
|
fc14d1d464 | ||
|
|
2a1e5e4471 | ||
|
|
da2ee33dff | ||
|
|
f19033a7a2 | ||
|
|
6502ef64ce | ||
|
|
b3ebf778fd | ||
|
|
1dca3698d2 | ||
|
|
2bfe1198d1 | ||
|
|
4f704670b1 | ||
|
|
a1aafd7453 | ||
|
|
4932623937 | ||
|
|
b93568d9c6 | ||
|
|
b3041ab6e0 | ||
|
|
3a151b30ac | ||
|
|
97b3d36433 | ||
|
|
81e3252128 | ||
|
|
426c83c6cc | ||
|
|
b427754a81 | ||
|
|
08f023fb12 | ||
|
|
5f1454aeb8 | ||
|
|
0d254e0724 | ||
|
|
e882e6e111 | ||
|
|
4b0811f9aa | ||
|
|
817f1ee938 | ||
|
|
2d93d74b9f | ||
|
|
93f37ad70f | ||
|
|
3c6bed90db | ||
|
|
fa26fb6b8b | ||
|
|
263ddb0d1e | ||
|
|
8be659c1c8 | ||
|
|
e5c9dddb5a | ||
|
|
6da72aad6d | ||
|
|
5dd5a024c9 | ||
|
|
c0eac5564c | ||
|
|
0d0ee753df | ||
|
|
908f952893 | ||
|
|
1c80e65c5a | ||
|
|
20b13a929b | ||
|
|
4637e1b5d8 | ||
|
|
4b6cb79c75 | ||
|
|
feaf2a33a9 | ||
|
|
4c893a11fc | ||
|
|
f4dd80c929 | ||
|
|
4af078007e | ||
|
|
be297120a1 | ||
|
|
a9741cadbf | ||
|
|
79200c82da | ||
|
|
d9c9ae8dae | ||
|
|
8ee96b40d0 | ||
|
|
67f0f45b67 | ||
|
|
881ab90982 | ||
|
|
6d7e09fec1 | ||
|
|
c274ed6a96 | ||
|
|
53ffca964d | ||
|
|
3da3367291 | ||
|
|
412ee220ce | ||
|
|
a3e3667dc2 | ||
|
|
d5f63da9e4 | ||
|
|
f8d2044356 | ||
|
|
4d2dc61f5d | ||
|
|
5492685df2 | ||
|
|
ad8c6bc579 | ||
|
|
fb08f8ae17 | ||
|
|
7833d7c99a | ||
|
|
335ff61011 | ||
|
|
2029ea378f | ||
|
|
cd7bc63cec | ||
|
|
958331a8ea | ||
|
|
2ba206b9db | ||
|
|
9b90e371f9 | ||
|
|
ff1c298817 | ||
|
|
dfe804dfa0 | ||
|
|
978c6f9349 | ||
|
|
c5c176a818 | ||
|
|
9f2d57493d | ||
|
|
0972d8f1e1 | ||
|
|
cf361334c4 | ||
|
|
c72dd86fed | ||
|
|
b6c653ff77 | ||
|
|
5e3bbb0e64 | ||
|
|
64124f6f4b | ||
|
|
6f6a6826d9 | ||
|
|
57c0b8fd0f | ||
|
|
c54f016213 | ||
|
|
bece58d939 | ||
|
|
36443c59f9 | ||
|
|
02f0301f25 | ||
|
|
334cf669ed | ||
|
|
8442143818 | ||
|
|
b25b8b90e4 | ||
|
|
06aec0b7d7 | ||
|
|
835d7f5ccb | ||
|
|
ffd0b16753 | ||
|
|
b351fb43e6 | ||
|
|
7da47c9586 | ||
|
|
e4755b298f | ||
|
|
4a65487842 | ||
|
|
1466875293 | ||
|
|
fd1e552ad1 | ||
|
|
be3e89ac20 | ||
|
|
b8f1b98c74 | ||
|
|
4bdd07db16 | ||
|
|
511b095647 | ||
|
|
23f4d30e57 | ||
|
|
45c587c5e4 | ||
|
|
115e74d844 | ||
|
|
1475a77260 | ||
|
|
d0e2fbf8e7 | ||
|
|
0f2f0450e3 | ||
|
|
e57f24c062 | ||
|
|
203d7de6a2 | ||
|
|
6e5f2f50fb | ||
|
|
875895524e | ||
|
|
3a21a2a49e | ||
|
|
262b4e7d62 | ||
|
|
c202f97088 | ||
|
|
f96eac96f9 | ||
|
|
c59006e06e | ||
|
|
84e27e7bff | ||
|
|
a3a4b10f83 | ||
|
|
a644c81736 | ||
|
|
27b9fbe490 | ||
|
|
2131c56513 | ||
|
|
95dba15db8 | ||
|
|
c23215604d | ||
|
|
8c9f274d5a | ||
|
|
504a70f3ee | ||
|
|
ad6f51901e | ||
|
|
52ef4c6235 | ||
|
|
9ba4005433 | ||
|
|
3cea3766ab | ||
|
|
ede24e0e73 | ||
|
|
df79bbc5aa | ||
|
|
3c522c677b | ||
|
|
08e86b8c82 | ||
|
|
66c3b1388a | ||
|
|
8992f59c3b | ||
|
|
1d6d27d46c | ||
|
|
625d36fb27 | ||
|
|
665ce14bb6 | ||
|
|
6e4f002b6d | ||
|
|
39a7dbda94 | ||
|
|
7ae8af4153 | ||
|
|
fb817e0c3b | ||
|
|
1eae360470 | ||
|
|
0314db0b58 | ||
|
|
4598387187 | ||
|
|
445c93a756 | ||
|
|
6c168ec575 | ||
|
|
1322f5bc08 | ||
|
|
1c40f2d167 | ||
|
|
18133e2a10 | ||
|
|
e5b0941d30 | ||
|
|
811bef8c35 | ||
|
|
057107ea7a | ||
|
|
273e5f9168 | ||
|
|
35930fb23a | ||
|
|
c794b5c2e7 | ||
|
|
e74d502ae6 | ||
|
|
e5ce6e3e2e | ||
|
|
65020dde1a | ||
|
|
98f432d23c | ||
|
|
2651b789dd | ||
|
|
dbabac34b0 | ||
|
|
6866b7a277 | ||
|
|
03c19f54c2 | ||
|
|
ba510ca77d | ||
|
|
bb7409fd91 | ||
|
|
23e5da4d95 | ||
|
|
fb1b46b67e | ||
|
|
7a21e6b5f8 | ||
|
|
6342a45b4e | ||
|
|
bcc5d485ab | ||
|
|
36fe150678 | ||
|
|
54f92ae466 | ||
|
|
b9b2924939 | ||
|
|
513e5b45c5 | ||
|
|
1fad5e2c1e | ||
|
|
5a28cf616d | ||
|
|
c08199659b | ||
|
|
ca508514a7 | ||
|
|
da2038dd46 | ||
|
|
f02e2d23d0 | ||
|
|
ef1c25c3d3 | ||
|
|
152cc27394 | ||
|
|
c582aca465 | ||
|
|
80e85fb49a | ||
|
|
d660e22e61 | ||
|
|
51856c4f06 | ||
|
|
fd37da42f9 | ||
|
|
11df2bc51f | ||
|
|
6770d21cf7 | ||
|
|
f490d1f6d2 | ||
|
|
f890ae8ddc | ||
|
|
5d5d61d8ed | ||
|
|
75589f1b2d | ||
|
|
6225c676e2 | ||
|
|
9b18668f49 | ||
|
|
2f80e7f1ff | ||
|
|
790413680d | ||
|
|
47e9a4ec29 | ||
|
|
defd5e8047 | ||
|
|
8c6a88374b | ||
|
|
7343613bea | ||
|
|
155dda1fa4 | ||
|
|
3c74306c8d | ||
|
|
13ecd9eee6 | ||
|
|
c48f3b4582 | ||
|
|
30c007194d | ||
|
|
ef5b68eb35 | ||
|
|
c47dcd5720 | ||
|
|
ed3c5ab479 | ||
|
|
a697b6c3d4 | ||
|
|
3965df78c9 | ||
|
|
64ebf20c1b | ||
|
|
797bed6701 | ||
|
|
e84e021127 | ||
|
|
0b9515b58b | ||
|
|
81ec9e96c7 | ||
|
|
ee09793ef2 | ||
|
|
61a130e645 | ||
|
|
19d342749a | ||
|
|
94adcf04f5 | ||
|
|
53e1da0f43 | ||
|
|
b41989de03 | ||
|
|
6c7848b750 | ||
|
|
07bd9ad840 | ||
|
|
14236d3062 | ||
|
|
6c4df30252 | ||
|
|
45218470af | ||
|
|
417ee1e047 | ||
|
|
08a3bc457e | ||
|
|
0cc2cba883 | ||
|
|
24d461c8b2 | ||
|
|
4d472fccd2 | ||
|
|
45d010bdb6 | ||
|
|
70db617229 | ||
|
|
d8256013a3 | ||
|
|
6d2c22addc | ||
|
|
9640f3f215 | ||
|
|
80c911e118 | ||
|
|
f2d5ea0391 | ||
|
|
a94d77d81e | ||
|
|
2d2de1a652 | ||
|
|
01f8823fb2 | ||
|
|
260575d139 | ||
|
|
1fb3290038 | ||
|
|
37596320e8 | ||
|
|
7a959c2c3e | ||
|
|
877c03e6a1 | ||
|
|
d3431d227b | ||
|
|
fbf307bf01 | ||
|
|
d672857e82 | ||
|
|
dd934e0095 | ||
|
|
8c9df8d3be | ||
|
|
b3aec58e69 | ||
|
|
b4111cffef | ||
|
|
ecc8d1738e | ||
|
|
d1982cbc0a | ||
|
|
03b65ce6dc | ||
|
|
56ea11cdff | ||
|
|
7a02404f7b | ||
|
|
b9a960a7c8 | ||
|
|
02c87a4d7b | ||
|
|
0dd9cd82f8 | ||
|
|
ec486d66f7 | ||
|
|
69cd7eb449 | ||
|
|
1427de7c65 | ||
|
|
8ad66e1e5e | ||
|
|
a2e31e97db | ||
|
|
1f3e131690 | ||
|
|
276b757e2d | ||
|
|
093df70602 | ||
|
|
fe9ab66f31 | ||
|
|
138f9476ac | ||
|
|
cb9ab61b6b | ||
|
|
bcbd365326 | ||
|
|
afdf4e365f | ||
|
|
553b7522aa | ||
|
|
13f38dd594 | ||
|
|
31e1c6f7aa | ||
|
|
02d060ca0a | ||
|
|
5e2a3ac644 | ||
|
|
2fc461b85f | ||
|
|
29a0b86411 | ||
|
|
efc3e7b25d | ||
|
|
6c2adfeec2 | ||
|
|
3124d6d43e | ||
|
|
e5a6b7d47d | ||
|
|
add65cf592 | ||
|
|
129effd0ec | ||
|
|
2aad00df85 | ||
|
|
85e0e74bc6 | ||
|
|
7fa200401c | ||
|
|
1a452efbb9 | ||
|
|
eb4bdf1db2 | ||
|
|
d58f68cb44 | ||
|
|
2f30d29351 | ||
|
|
dc6dc192dc | ||
|
|
751afadebd | ||
|
|
ac71c02dfa | ||
|
|
4567da193e | ||
|
|
bd2a1d5574 | ||
|
|
ab44d608d2 | ||
|
|
cdc7f1565e | ||
|
|
1493581a4d | ||
|
|
4461d6cf7f | ||
|
|
38e64b1f75 | ||
|
|
eb1daf4a20 | ||
|
|
e0c38f7c72 | ||
|
|
5638ff4a3a | ||
|
|
f4ae39dd44 | ||
|
|
d235125138 | ||
|
|
78d7759197 | ||
|
|
eddaad3b05 | ||
|
|
f2c80c800c | ||
|
|
a0e787e424 | ||
|
|
c9d1fb8533 | ||
|
|
006eebb09e | ||
|
|
4aec824bfd | ||
|
|
56f7564ce4 | ||
|
|
f89daefd43 | ||
|
|
8572a2d262 | ||
|
|
d6e41be4b4 | ||
|
|
5e715ffcce | ||
|
|
5100341e60 | ||
|
|
5ca4db6ea5 | ||
|
|
e02e07ae7a | ||
|
|
f3caedc045 | ||
|
|
59c49254e7 | ||
|
|
ad81b310e3 | ||
|
|
bf124b87fa | ||
|
|
a4868602b5 | ||
|
|
763aeabddd | ||
|
|
71fc5af320 | ||
|
|
d28f0e3544 | ||
|
|
b4d0dde129 | ||
|
|
281630e751 | ||
|
|
32d79ead15 | ||
|
|
5a91c7e84a | ||
|
|
e2e1200c89 | ||
|
|
ed1be76606 | ||
|
|
a64de91781 | ||
|
|
e802c8b8cc | ||
|
|
86f2cf0ac4 | ||
|
|
a844a6b6c1 | ||
|
|
c31146e902 | ||
|
|
fcc5db2fe6 | ||
|
|
9fdd3ae1be | ||
|
|
c99509a967 | ||
|
|
1169331462 | ||
|
|
0978822939 | ||
|
|
8bf8ecf7fa | ||
|
|
f55db6a5d7 | ||
|
|
e82d6cf91d | ||
|
|
65a1d165ac | ||
|
|
e3b27bd39c | ||
|
|
07dde01c3b | ||
|
|
11a2e8686c | ||
|
|
daeeb17142 | ||
|
|
35ab2f6704 | ||
|
|
9c428f6db7 | ||
|
|
a76b067b96 | ||
|
|
a843619c5b | ||
|
|
5bbc4aea95 | ||
|
|
4676043826 | ||
|
|
64a841487f | ||
|
|
cfd69f2da8 | ||
|
|
e97a14f617 | ||
|
|
b7118b6bd8 | ||
|
|
742da4ccb8 | ||
|
|
97296ca7d7 | ||
|
|
1486a9ae1b | ||
|
|
a847e385cb | ||
|
|
134284723b | ||
|
|
a6eb44ba95 | ||
|
|
1457738905 | ||
|
|
3dd0a60555 | ||
|
|
1eef18dcd3 | ||
|
|
c86ee33371 | ||
|
|
60690208de | ||
|
|
69ebee3eeb | ||
|
|
4bdb367c19 | ||
|
|
c817a3097d | ||
|
|
b3aa25ad59 | ||
|
|
cdddfd37d2 | ||
|
|
2547db2a8e | ||
|
|
9363f0ebb4 | ||
|
|
8d6d8019fe | ||
|
|
e27089157d | ||
|
|
99fc75eeda | ||
|
|
ec63dd704a | ||
|
|
d46a9f6d1d | ||
|
|
4b7d87c6bc | ||
|
|
9c5a0ba7eb | ||
|
|
e461625da4 | ||
|
|
c393cd655d | ||
|
|
ed8edb5aee | ||
|
|
5df1fa3c65 | ||
|
|
e796968d19 | ||
|
|
c8f17e2ab0 | ||
|
|
69870eb229 | ||
|
|
670aed2074 | ||
|
|
0020c7c6dc | ||
|
|
20b98122c1 | ||
|
|
35c102aa98 | ||
|
|
fb316a22c6 | ||
|
|
5342af60cb | ||
|
|
3d01bd7c57 | ||
|
|
1b3ac83876 | ||
|
|
4ba3104bf3 | ||
|
|
eda2b87a57 | ||
|
|
ac0216d916 | ||
|
|
d0986383ad | ||
|
|
ab7f507b03 | ||
|
|
e096ba27ce | ||
|
|
25ce2a649a | ||
|
|
0e200b1fb6 | ||
|
|
552b19cbb0 | ||
|
|
3f0377a840 | ||
|
|
d9147874dd | ||
|
|
06ed124057 | ||
|
|
68aa97a676 | ||
|
|
20bb07e829 | ||
|
|
53b6cc21b1 | ||
|
|
96a80f0ed2 | ||
|
|
9ebb150b68 | ||
|
|
4ae7312c7f | ||
|
|
113393de8f | ||
|
|
5daa027c10 | ||
|
|
7394b4ac27 | ||
|
|
1259da01a5 | ||
|
|
86a8cd29e5 | ||
|
|
bfc84d50dd | ||
|
|
af060f52e1 | ||
|
|
f87fc1d639 | ||
|
|
6ddfbcb945 | ||
|
|
d4f11867a8 | ||
|
|
759f30244a | ||
|
|
fcc49ae7b6 | ||
|
|
1aa8e9753d | ||
|
|
f400504898 | ||
|
|
41e6097ac5 | ||
|
|
8e4b08b493 | ||
|
|
df948179d8 | ||
|
|
7b3aa43217 | ||
|
|
e42fe5349b | ||
|
|
1f578ebd2c | ||
|
|
b80875773f | ||
|
|
3caebb8613 | ||
|
|
24ac705fe8 | ||
|
|
57acdd4b21 | ||
|
|
fddba2906a | ||
|
|
c42023855b | ||
|
|
dd38dd9cae | ||
|
|
5f0341cd53 | ||
|
|
e3d3129e6d | ||
|
|
ed8b6c6bc9 | ||
|
|
2218fc0d41 | ||
|
|
b8cbcfe986 | ||
|
|
dbc5f5bfcc | ||
|
|
33cb02b9e4 | ||
|
|
8783d150e8 | ||
|
|
449ea9375e | ||
|
|
d5a73a3380 | ||
|
|
173dd180d9 | ||
|
|
f332cbf1bc | ||
|
|
c4d317b33e | ||
|
|
9c59d6a69b | ||
|
|
61bdd4e027 | ||
|
|
ab2efe78b1 | ||
|
|
c3af3e4740 | ||
|
|
6418eac658 | ||
|
|
d74e9f7410 | ||
|
|
569c83d90e | ||
|
|
1dc3cf7824 | ||
|
|
d8dead82b6 | ||
|
|
b053fbc4a7 | ||
|
|
2144dc3b67 | ||
|
|
243b4b9414 | ||
|
|
e068fde8f2 | ||
|
|
3162f04937 | ||
|
|
58a32c11ec | ||
|
|
f06817f00d | ||
|
|
da4be5c1cf | ||
|
|
a59f5d953a | ||
|
|
815a988587 | ||
|
|
b65d9ffaed | ||
|
|
0fc73c3a6f | ||
|
|
57fdc1b223 | ||
|
|
22d5fc6cba | ||
|
|
c36f9646f9 | ||
|
|
45e11f6291 | ||
|
|
2893c3dc0e | ||
|
|
116022b01d | ||
|
|
09cba8774d | ||
|
|
41129f7c50 | ||
|
|
44d014c445 | ||
|
|
51e086b20e | ||
|
|
d71a5c99f4 | ||
|
|
fb0243a029 | ||
|
|
713441d9cb | ||
|
|
451f0fd12b | ||
|
|
5a84fa5a80 | ||
|
|
751ba8d1c2 | ||
|
|
974ed439a4 | ||
|
|
0172c1e385 | ||
|
|
faa19acf81 | ||
|
|
1f9afb6c6e | ||
|
|
5d96bc2d3a | ||
|
|
9366596f5f | ||
|
|
cb6e3ade15 | ||
|
|
45178b3eb3 | ||
|
|
31e3e37c9b | ||
|
|
e1489bb407 | ||
|
|
8b50d8645a | ||
|
|
796fdb1cf6 | ||
|
|
5203d40804 | ||
|
|
21252aad0f | ||
|
|
0c535904fc | ||
|
|
490944a02a | ||
|
|
ace85df9b7 | ||
|
|
9e56441d4a | ||
|
|
d83c3d35eb | ||
|
|
8f26d63d6f | ||
|
|
75c520097a | ||
|
|
38375982dd | ||
|
|
d24a71bbd2 | ||
|
|
7964c9fca7 | ||
|
|
ec07e4b233 | ||
|
|
b4266b8575 | ||
|
|
07201203b2 | ||
|
|
e7c5eb93dd | ||
|
|
e70229c672 | ||
|
|
86c5b28562 | ||
|
|
a9149c5dc0 | ||
|
|
a64430c65f | ||
|
|
75aab4c031 | ||
|
|
6f8be3260c | ||
|
|
e74460bd91 | ||
|
|
c25250cb05 | ||
|
|
42c3cc5296 | ||
|
|
e4b3f90457 | ||
|
|
992b04f8c5 | ||
|
|
d1e0f3646a | ||
|
|
b4ba565923 | ||
|
|
006e7dc736 | ||
|
|
5ed6407ea3 | ||
|
|
faf6b5a4e4 | ||
|
|
f92891895e | ||
|
|
d8cc3c86b4 | ||
|
|
e7f233db5b | ||
|
|
fd9c420dc8 | ||
|
|
dc9fceb8cf | ||
|
|
dc9b8169c0 | ||
|
|
38caf1e2b7 | ||
|
|
4b3e7c8858 | ||
|
|
2be3068675 | ||
|
|
682e47c7b3 | ||
|
|
8c90c3ad81 | ||
|
|
d5afcc4aec | ||
|
|
14ed4201c0 | ||
|
|
1b9efeb049 | ||
|
|
4b862cf4c7 | ||
|
|
4e7683961e | ||
|
|
ce9d44d010 | ||
|
|
32d052259f | ||
|
|
fb98874948 | ||
|
|
55a62ead05 | ||
|
|
f81f50646e | ||
|
|
f707c1d02c | ||
|
|
417c04bf1c | ||
|
|
8f7f836598 | ||
|
|
32aea8d154 | ||
|
|
3f6c8cb622 | ||
|
|
8f6ff215aa | ||
|
|
4f01bacb49 | ||
|
|
e6f4b0976f | ||
|
|
e0d9c3f149 | ||
|
|
9d3cebf430 | ||
|
|
5e106bf510 | ||
|
|
fc617fb7a9 | ||
|
|
b88ecb6370 | ||
|
|
9b21001953 | ||
|
|
cb0e10c7ab | ||
|
|
69884935f3 | ||
|
|
b809008291 | ||
|
|
79e77f871e | ||
|
|
32ac6e3429 | ||
|
|
da56c2790f | ||
|
|
687192f071 | ||
|
|
2e82ee0aaf | ||
|
|
4dacf4e342 | ||
|
|
b91f04316a | ||
|
|
3c4252a933 | ||
|
|
be4b687e48 | ||
|
|
8950100bd7 | ||
|
|
d651716d99 | ||
|
|
270606699b | ||
|
|
cc7617a302 | ||
|
|
c04b5f2085 | ||
|
|
28f3ded4bd | ||
|
|
d56607a686 | ||
|
|
f8dde57133 | ||
|
|
f2ea13a142 | ||
|
|
69a1fa0d3c | ||
|
|
5cb54b9ad7 | ||
|
|
ee610cadd3 | ||
|
|
acf131308b | ||
|
|
c591ec3185 | ||
|
|
264a245d27 | ||
|
|
f6c25d2a8b | ||
|
|
d069d9331c | ||
|
|
ba14031945 | ||
|
|
0b639e0169 | ||
|
|
afee8631e1 | ||
|
|
214cb25d1b | ||
|
|
a1457d22d6 | ||
|
|
82107e2938 | ||
|
|
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 |
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.kt]
|
||||||
|
indent_size = 2
|
||||||
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.
|
||||||
15
.github/workflows/android.yml
vendored
@@ -14,18 +14,25 @@ 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;21.0.6113669" --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
|
||||||
|
|
||||||
|
- name: Remove Android S
|
||||||
|
run: $ANDROID_HOME/tools/bin/sdkmanager --uninstall "platforms;android-S"
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew qa
|
run: ./gradlew qa
|
||||||
|
|
||||||
|
- name: Archive reports for failed build
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: reports
|
||||||
|
path: '*/build/reports'
|
||||||
|
|||||||
193
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="RIGHT_MARGIN" value="240" />
|
||||||
|
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||||
|
<option name="SOFT_MARGINS" value="160" />
|
||||||
|
<JavaCodeStyleSettings>
|
||||||
|
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
||||||
|
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ANNOTATION_PARAMETERS" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_TEXT_BLOCKS" value="true" />
|
||||||
|
</JavaCodeStyleSettings>
|
||||||
|
<JetCodeStyleSettings>
|
||||||
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</JetCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="BRACE_STYLE" value="5" />
|
||||||
|
<option name="CLASS_BRACE_STYLE" value="5" />
|
||||||
|
<option name="METHOD_BRACE_STYLE" value="5" />
|
||||||
|
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_THROWS_LIST" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||||
|
<option name="ALIGN_GROUP_FIELD_DECLARATIONS" value="true" />
|
||||||
|
<option name="ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS" value="true" />
|
||||||
|
<option name="ALIGN_CONSECUTIVE_ASSIGNMENTS" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||||
|
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||||
|
<option name="WRAP_FIRST_METHOD_IN_CALL_CHAIN" value="true" />
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="METHOD_ANNOTATION_WRAP" value="0" />
|
||||||
|
<option name="CLASS_ANNOTATION_WRAP" value="0" />
|
||||||
|
<option name="FIELD_ANNOTATION_WRAP" value="0" />
|
||||||
|
<option name="ENUM_CONSTANTS_WRAP" value="5" />
|
||||||
|
<option name="WRAP_ON_TYPING" value="0" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<groups>
|
||||||
|
<group>
|
||||||
|
<type>GETTERS_AND_SETTERS</type>
|
||||||
|
<order>KEEP</order>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<type>OVERRIDDEN_METHODS</type>
|
||||||
|
<order>KEEP</order>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<type>DEPENDENT_METHODS</type>
|
||||||
|
<order>BREADTH_FIRST</order>
|
||||||
|
</group>
|
||||||
|
</groups>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
216
app/build.gradle
@@ -3,11 +3,16 @@ import org.signal.signing.ApkSignerUtil
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'com.google.protobuf'
|
apply plugin: 'com.google.protobuf'
|
||||||
apply plugin: 'androidx.navigation.safeargs'
|
apply plugin: 'androidx.navigation.safeargs'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
|
apply plugin: 'org.jlleitschuh.gradle.ktlint'
|
||||||
apply from: 'translations.gradle'
|
apply from: 'translations.gradle'
|
||||||
apply from: 'witness-verifications.gradle'
|
apply from: 'witness-verifications.gradle'
|
||||||
|
apply plugin: 'org.jetbrains.kotlin.android'
|
||||||
|
apply plugin: 'app.cash.exhaustive'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
@@ -16,24 +21,12 @@ repositories {
|
|||||||
includeGroupByRegex "com\\.github\\.chrisbanes.*"
|
includeGroupByRegex "com\\.github\\.chrisbanes.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
url "https://raw.github.com/signalapp/maven/master/shortcutbadger/releases/"
|
|
||||||
content {
|
|
||||||
includeGroupByRegex "me\\.leolin.*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maven {
|
maven {
|
||||||
url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/"
|
url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/"
|
||||||
content {
|
content {
|
||||||
includeGroupByRegex "com\\.github\\.dmytrodanylyk\\.circular-progress-button\\.*"
|
includeGroupByRegex "com\\.github\\.dmytrodanylyk\\.circular-progress-button\\.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
url "https://raw.github.com/signalapp/maven/master/sqlcipher/release/"
|
|
||||||
content {
|
|
||||||
includeGroupByRegex "org\\.signal.*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maven { // textdrawable
|
maven { // textdrawable
|
||||||
url 'https://dl.bintray.com/amulyakhare/maven'
|
url 'https://dl.bintray.com/amulyakhare/maven'
|
||||||
content {
|
content {
|
||||||
@@ -44,6 +37,9 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
maven {
|
||||||
|
url "https://dl.cloudsmith.io/qxAgwaeEE1vN8aLU/mobilecoin/mobilecoin/maven/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protobuf {
|
protobuf {
|
||||||
@@ -61,8 +57,8 @@ protobuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 769
|
def canonicalVersionCode = 880
|
||||||
def canonicalVersionName = "5.2.0"
|
def canonicalVersionName = "5.17.2"
|
||||||
|
|
||||||
def postFixSize = 100
|
def postFixSize = 100
|
||||||
def abiPostFix = ['universal' : 0,
|
def abiPostFix = ['universal' : 0,
|
||||||
@@ -80,6 +76,11 @@ android {
|
|||||||
flavorDimensions 'distribution', 'environment'
|
flavorDimensions 'distribution', 'environment'
|
||||||
useLibrary 'org.apache.http.legacy'
|
useLibrary 'org.apache.http.legacy'
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
freeCompilerArgs = ["-Xallow-result-return-type"]
|
||||||
|
}
|
||||||
|
|
||||||
dexOptions {
|
dexOptions {
|
||||||
javaMaxHeapSize "4g"
|
javaMaxHeapSize "4g"
|
||||||
}
|
}
|
||||||
@@ -108,6 +109,7 @@ android {
|
|||||||
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\""
|
||||||
@@ -116,6 +118,8 @@ android {
|
|||||||
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", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
|
||||||
|
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\"}"
|
||||||
|
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.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\""
|
||||||
@@ -128,6 +132,14 @@ android {
|
|||||||
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('", "') + '"}'
|
||||||
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
||||||
|
buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\""
|
||||||
|
buildConfigField "int[]", "MOBILE_COIN_REGIONS", "new int[]{44}"
|
||||||
|
buildConfigField "String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\""
|
||||||
|
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\""
|
||||||
|
|
||||||
|
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"unset\""
|
||||||
|
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"unset\""
|
||||||
|
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"unset\""
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||||
@@ -148,6 +160,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
sourceCompatibility JAVA_VERSION
|
sourceCompatibility JAVA_VERSION
|
||||||
targetCompatibility JAVA_VERSION
|
targetCompatibility JAVA_VERSION
|
||||||
}
|
}
|
||||||
@@ -160,10 +173,8 @@ android {
|
|||||||
exclude 'META-INF/LICENSE'
|
exclude 'META-INF/LICENSE'
|
||||||
exclude 'META-INF/NOTICE'
|
exclude 'META-INF/NOTICE'
|
||||||
exclude 'META-INF/proguard/androidx-annotations.pro'
|
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||||
}
|
exclude '/org/spongycastle/x509/CertPathReviewerMessages.properties'
|
||||||
|
exclude '/org/spongycastle/x509/CertPathReviewerMessages_de.properties'
|
||||||
aaptOptions {
|
|
||||||
ignoreAssetsPattern '!contours.tfl:!LMprec_600.emd:!blazeface.tfl'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -194,22 +205,34 @@ android {
|
|||||||
'proguard/proguard.cfg'
|
'proguard/proguard.cfg'
|
||||||
testProguardFiles 'proguard/proguard-automation.pro',
|
testProguardFiles 'proguard/proguard-automation.pro',
|
||||||
'proguard/proguard.cfg'
|
'proguard/proguard.cfg'
|
||||||
|
|
||||||
|
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Debug\""
|
||||||
}
|
}
|
||||||
flipper {
|
flipper {
|
||||||
initWith debug
|
initWith debug
|
||||||
isDefault false
|
isDefault false
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
matchingFallbacks = ['debug']
|
matchingFallbacks = ['debug']
|
||||||
|
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Flipper\""
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles = buildTypes.debug.proguardFiles
|
proguardFiles = buildTypes.debug.proguardFiles
|
||||||
|
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Release\""
|
||||||
}
|
}
|
||||||
perf {
|
perf {
|
||||||
initWith debug
|
initWith debug
|
||||||
isDefault false
|
isDefault false
|
||||||
debuggable false
|
debuggable false
|
||||||
matchingFallbacks = ['debug']
|
matchingFallbacks = ['debug']
|
||||||
|
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Perf\""
|
||||||
|
}
|
||||||
|
mock {
|
||||||
|
initWith debug
|
||||||
|
isDefault false
|
||||||
|
minifyEnabled false
|
||||||
|
matchingFallbacks = ['debug']
|
||||||
|
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Mock\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +243,7 @@ android {
|
|||||||
ext.websiteUpdateUrl = "null"
|
ext.websiteUpdateUrl = "null"
|
||||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"play\""
|
||||||
}
|
}
|
||||||
|
|
||||||
website {
|
website {
|
||||||
@@ -227,6 +251,7 @@ android {
|
|||||||
ext.websiteUpdateUrl = "https://updates.signal.org/android"
|
ext.websiteUpdateUrl = "https://updates.signal.org/android"
|
||||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
|
||||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
|
||||||
|
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"website\""
|
||||||
}
|
}
|
||||||
|
|
||||||
internal {
|
internal {
|
||||||
@@ -234,12 +259,35 @@ android {
|
|||||||
ext.websiteUpdateUrl = "null"
|
ext.websiteUpdateUrl = "null"
|
||||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"internal\""
|
||||||
|
}
|
||||||
|
|
||||||
|
nightly {
|
||||||
|
dimension 'distribution'
|
||||||
|
versionNameSuffix "-nightly-untagged-${getDateSuffix()}"
|
||||||
|
ext.websiteUpdateUrl = "null"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"internal\""
|
||||||
|
}
|
||||||
|
|
||||||
|
study {
|
||||||
|
dimension 'distribution'
|
||||||
|
|
||||||
|
applicationIdSuffix ".study"
|
||||||
|
ext.websiteUpdateUrl = "null"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"study\""
|
||||||
}
|
}
|
||||||
|
|
||||||
prod {
|
prod {
|
||||||
dimension 'environment'
|
dimension 'environment'
|
||||||
|
|
||||||
isDefault true
|
isDefault true
|
||||||
|
|
||||||
|
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\""
|
||||||
|
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Prod\""
|
||||||
}
|
}
|
||||||
|
|
||||||
staging {
|
staging {
|
||||||
@@ -255,23 +303,53 @@ android {
|
|||||||
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", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
||||||
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\", " +
|
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\", " +
|
||||||
"\"038c40bbbacdc873caa81ac793bb75afde6dfe436a99ab1f15e3f0cbb7434ced\", " +
|
"\"51a56084c0b21c6b8f62b1bc792ec9bedac4c7c3964bb08ddcab868158c09982\", " +
|
||||||
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")"
|
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")"
|
||||||
buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]"
|
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=\""
|
||||||
|
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\""
|
||||||
|
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\""
|
||||||
|
|
||||||
|
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
variant.outputs.each { output ->
|
variant.outputs.each { output ->
|
||||||
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
if (output.baseName.contains('nightly')) {
|
||||||
def abiName = output.getFilter("ABI") ?: 'universal'
|
output.versionCodeOverride = canonicalVersionCode * postFixSize + 5
|
||||||
def postFix = abiPostFix.get(abiName, 0)
|
def tag = getCurrentGitTag()
|
||||||
|
if (tag != null && tag.length() > 0) {
|
||||||
|
output.versionNameOverride = tag
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
||||||
|
def abiName = output.getFilter("ABI") ?: 'universal'
|
||||||
|
def postFix = abiPostFix.get(abiName, 0)
|
||||||
|
|
||||||
if (postFix >= postFixSize) throw new AssertionError("postFix is too large")
|
if (postFix >= postFixSize) throw new AssertionError("postFix is too large")
|
||||||
|
|
||||||
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
|
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android.variantFilter { variant ->
|
||||||
|
def distribution = variant.getFlavors().get(0).name
|
||||||
|
def environment = variant.getFlavors().get(1).name
|
||||||
|
def buildType = variant.buildType.name
|
||||||
|
|
||||||
|
if (distribution == 'study' && buildType != 'perf' && buildType != 'mock') {
|
||||||
|
variant.setIgnore(true)
|
||||||
|
} else if (distribution != 'study' && buildType == 'mock') {
|
||||||
|
variant.setIgnore(true)
|
||||||
|
} else if (distribution == 'internal' && buildType != 'flipper' && buildType != 'perf' && buildType != 'release') {
|
||||||
|
variant.setIgnore(true)
|
||||||
|
} else if (distribution == 'nightly' && environment != 'prod') {
|
||||||
|
variant.setIgnore(true)
|
||||||
|
} else if (distribution == 'nightly' && buildType != 'flipper' && buildType != 'perf' && buildType != 'release') {
|
||||||
|
variant.setIgnore(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,13 +367,17 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'androidx.core:core-ktx:1.5.0'
|
||||||
|
implementation 'androidx.fragment:fragment-ktx:1.2.5'
|
||||||
lintChecks project(':lintchecks')
|
lintChecks project(':lintchecks')
|
||||||
|
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
implementation ('androidx.appcompat:appcompat:1.2.0') {
|
implementation ('androidx.appcompat:appcompat:1.2.0') {
|
||||||
force = true
|
force = true
|
||||||
}
|
}
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation 'com.google.android.material:material:1.3.0'
|
||||||
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'
|
||||||
@@ -315,10 +397,10 @@ dependencies {
|
|||||||
implementation "androidx.camera:camera-view:1.0.0-alpha18"
|
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 'com.google.firebase:firebase-ml-vision:24.0.3'
|
implementation "androidx.biometric:biometric:1.1.0"
|
||||||
implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
|
implementation "androidx.sharetarget:sharetarget:1.1.0"
|
||||||
|
|
||||||
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
|
implementation ('com.google.firebase:firebase-messaging:22.0.0') {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
@@ -338,22 +420,28 @@ dependencies {
|
|||||||
implementation project(':paging')
|
implementation project(':paging')
|
||||||
implementation project(':core-util')
|
implementation project(':core-util')
|
||||||
implementation project(':video')
|
implementation project(':video')
|
||||||
|
implementation project(':device-transfer')
|
||||||
|
|
||||||
implementation 'org.signal:zkgroup-android:0.7.0'
|
implementation 'org.signal:zkgroup-android:0.7.0'
|
||||||
implementation 'org.whispersystems:signal-client-android:0.1.5'
|
implementation 'org.whispersystems:signal-client-android:0.8.3'
|
||||||
implementation 'com.google.protobuf:protobuf-javalite:3.10.0'
|
implementation 'com.google.protobuf:protobuf-javalite:3.10.0'
|
||||||
|
|
||||||
|
implementation('com.mobilecoin:android-sdk:1.1.0') {
|
||||||
|
exclude group: 'com.google.protobuf'
|
||||||
|
}
|
||||||
|
|
||||||
implementation 'org.signal:argon2:13.1@aar'
|
implementation 'org.signal:argon2:13.1@aar'
|
||||||
|
|
||||||
implementation 'org.signal:ringrtc-android:2.8.9'
|
implementation 'org.signal:ringrtc-android:2.10.6'
|
||||||
|
|
||||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
implementation "me.leolin:ShortcutBadger:1.1.22"
|
||||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||||
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||||
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
||||||
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
kapt 'androidx.annotation:annotation:1.1.0'
|
||||||
implementation 'com.makeramen:roundedimageview:2.1.0'
|
implementation 'com.makeramen:roundedimageview:2.1.0'
|
||||||
implementation 'com.pnikosis:materialish-progress:1.5'
|
implementation 'com.pnikosis:materialish-progress:1.5'
|
||||||
implementation 'org.greenrobot:eventbus:3.0.0'
|
implementation 'org.greenrobot:eventbus:3.0.0'
|
||||||
@@ -382,19 +470,22 @@ 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'
|
||||||
implementation 'org.signal:android-database-sqlcipher:3.5.9-S3'
|
|
||||||
|
implementation "net.zetetic:android-database-sqlcipher:4.4.3"
|
||||||
|
implementation "androidx.sqlite:sqlite:2.1.0"
|
||||||
|
|
||||||
implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
|
implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
|
||||||
exclude group: 'com.fasterxml.jackson.core'
|
exclude group: 'com.fasterxml.jackson.core'
|
||||||
exclude group: 'org.freemarker'
|
exclude group: 'org.freemarker'
|
||||||
}
|
}
|
||||||
implementation 'dnsjava:dnsjava:2.1.9'
|
implementation 'dnsjava:dnsjava:2.1.9'
|
||||||
|
|
||||||
flipperImplementation 'com.facebook.flipper:flipper:0.32.2'
|
flipperImplementation 'com.facebook.flipper:flipper:0.91.0'
|
||||||
flipperImplementation 'com.facebook.soloader:soloader:0.8.2'
|
flipperImplementation 'com.facebook.soloader:soloader:0.10.1'
|
||||||
|
|
||||||
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'
|
||||||
@@ -411,8 +502,16 @@ dependencies {
|
|||||||
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
||||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||||
|
|
||||||
|
testImplementation(testFixtures(project(":libsignal-service")))
|
||||||
|
|
||||||
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'
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0"
|
||||||
|
|
||||||
|
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
|
||||||
|
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyVerification {
|
dependencyVerification {
|
||||||
@@ -481,6 +580,10 @@ task signProductionWebsiteRelease {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def getLastCommitTimestamp() {
|
def getLastCommitTimestamp() {
|
||||||
|
if (!(new File('.git').exists())) {
|
||||||
|
return System.currentTimeMillis().toString()
|
||||||
|
}
|
||||||
|
|
||||||
new ByteArrayOutputStream().withStream { os ->
|
new ByteArrayOutputStream().withStream { os ->
|
||||||
def result = exec {
|
def result = exec {
|
||||||
executable = 'git'
|
executable = 'git'
|
||||||
@@ -492,6 +595,39 @@ def getLastCommitTimestamp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getGitHash() {
|
||||||
|
if (!(new File('.git').exists())) {
|
||||||
|
return "abcd1234"
|
||||||
|
}
|
||||||
|
|
||||||
|
def stdout = new ByteArrayOutputStream()
|
||||||
|
exec {
|
||||||
|
commandLine 'git', 'rev-parse', '--short', 'HEAD'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
return stdout.toString().trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
def getCurrentGitTag() {
|
||||||
|
if (!(new File('.git').exists())) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
def stdout = new ByteArrayOutputStream()
|
||||||
|
exec {
|
||||||
|
commandLine 'git', 'tag', '--points-at', 'HEAD'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
def output = stdout.toString().trim()
|
||||||
|
|
||||||
|
if (output != null && output.size() > 0) {
|
||||||
|
return output.split('\n')[0];
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tasks.withType(Test) {
|
tasks.withType(Test) {
|
||||||
testLogging {
|
testLogging {
|
||||||
events "failed"
|
events "failed"
|
||||||
@@ -512,3 +648,9 @@ def loadKeystoreProperties(filename) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getDateSuffix() {
|
||||||
|
def date = new Date()
|
||||||
|
def formattedDate = date.format('yyyy-MM-dd-HH:mm')
|
||||||
|
return formattedDate
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
<issue id="LogNotAppSignal" severity="error" />
|
<issue id="LogNotAppSignal" severity="error" />
|
||||||
<issue id="LogTagInlined" severity="error" />
|
<issue id="LogTagInlined" severity="error" />
|
||||||
|
|
||||||
|
<issue id="AlertDialogBuilderUsage" severity="warning" />
|
||||||
|
|
||||||
<issue id="RestrictedApi" severity="error">
|
<issue id="RestrictedApi" severity="error">
|
||||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
|
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
|
||||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
|
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import net.sqlcipher.database.SQLiteStatement;
|
|||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.Hex;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -237,7 +238,12 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||||||
case Cursor.FIELD_TYPE_FLOAT:
|
case Cursor.FIELD_TYPE_FLOAT:
|
||||||
return cursor.getDouble(column);
|
return cursor.getDouble(column);
|
||||||
case Cursor.FIELD_TYPE_BLOB:
|
case Cursor.FIELD_TYPE_BLOB:
|
||||||
return cursor.getBlob(column);
|
byte[] blob = cursor.getBlob(column);
|
||||||
|
String bytes = blob != null ? "(blob) " + Hex.toStringCondensed(Arrays.copyOf(blob, Math.min(blob.length, 32))) : null;
|
||||||
|
if (bytes != null && bytes.length() == 32 && blob.length > 32) {
|
||||||
|
bytes += "...";
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
case Cursor.FIELD_TYPE_STRING:
|
case Cursor.FIELD_TYPE_STRING:
|
||||||
default:
|
default:
|
||||||
return cursor.getString(column);
|
return cursor.getString(column);
|
||||||
|
|||||||
@@ -64,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" />
|
||||||
@@ -154,17 +153,28 @@
|
|||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="tsdevice"/>
|
<data android:scheme="tsdevice"/>
|
||||||
</intent-filter>
|
</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="sgnl"
|
||||||
|
android:host="linkdevice"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<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>
|
||||||
@@ -188,7 +198,7 @@
|
|||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.service.chooser.chooser_target_service"
|
android:name="android.service.chooser.chooser_target_service"
|
||||||
android:value=".service.DirectShareService" />
|
android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
@@ -252,6 +262,16 @@
|
|||||||
<data android:scheme="https"
|
<data android:scheme="https"
|
||||||
android:host="signal.group"/>
|
android:host="signal.group"/>
|
||||||
</intent-filter>
|
</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>
|
||||||
|
|
||||||
<activity android:name=".conversation.ConversationActivity"
|
<activity android:name=".conversation.ConversationActivity"
|
||||||
@@ -288,13 +308,10 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
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=".recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity"
|
||||||
android:windowSoftInputMode="stateAlwaysHidden"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustResize"/>
|
||||||
<activity android:name=".recipients.ui.managerecipient.ManageRecipientActivity"
|
|
||||||
android:windowSoftInputMode="stateAlwaysHidden"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".DatabaseMigrationActivity"
|
<activity android:name=".DatabaseMigrationActivity"
|
||||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||||
@@ -302,7 +319,7 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".migrations.ApplicationMigrationActivity"
|
<activity android:name=".migrations.ApplicationMigrationActivity"
|
||||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -346,14 +363,43 @@
|
|||||||
<activity android:name=".VerifyIdentityActivity"
|
<activity android:name=".VerifyIdentityActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".ApplicationPreferencesActivity"
|
<activity android:name=".components.settings.app.AppSettingsActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
|
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=".components.settings.conversation.ConversationSettingsActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/Signal.DayNight.ConversationSettings"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden">
|
||||||
|
</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=".devicetransfer.olddevice.OldDeviceTransferActivity"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<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"
|
||||||
@@ -453,13 +499,21 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
|
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
|
||||||
android:theme="@style/TextSecure.DarkTheme"
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".profiles.edit.EditProfileActivity"
|
<activity android:name=".profiles.edit.EditProfileActivity"
|
||||||
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=".payments.preferences.PaymentsActivity"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<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"
|
||||||
@@ -535,7 +589,24 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||||
android:launchMode="singleTask" />
|
android:launchMode="singleTask" />
|
||||||
|
|
||||||
<service android:enabled="true" android:name=".service.WebRtcCallService"/>
|
<activity android:name=".ratelimit.RecaptchaProofActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" />
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<activity android:name=".reactions.edit.EditReactionsActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<service android:enabled="true" android:name=".service.webrtc.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"/>
|
||||||
@@ -580,13 +651,6 @@
|
|||||||
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
|
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service android:name=".service.DirectShareService"
|
|
||||||
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.service.chooser.ChooserTargetService" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service android:name=".service.GenericForegroundService"/>
|
<service android:name=".service.GenericForegroundService"/>
|
||||||
|
|
||||||
<service android:name=".gcm.FcmFetchService" />
|
<service android:name=".gcm.FcmFetchService" />
|
||||||
@@ -646,26 +710,16 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".notifications.AndroidAutoHeardReceiver"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_HEARD"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.AndroidAutoReplyReceiver"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_REPLY"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".service.ExpirationListener" />
|
<receiver android:name=".service.ExpirationListener" />
|
||||||
|
|
||||||
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
||||||
|
|
||||||
|
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
|
||||||
|
|
||||||
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
|
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
|
||||||
|
|
||||||
|
<receiver android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver" />
|
||||||
|
|
||||||
<provider android:name=".providers.PartProvider"
|
<provider android:name=".providers.PartProvider"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
@@ -694,10 +748,6 @@
|
|||||||
android:authorities="${applicationId}.database.conversation"
|
android:authorities="${applicationId}.database.conversation"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$ConversationList"
|
|
||||||
android:authorities="${applicationId}.database.conversationlist"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Attachment"
|
<provider android:name=".database.DatabaseContentProviders$Attachment"
|
||||||
android:authorities="${applicationId}.database.attachment"
|
android:authorities="${applicationId}.database.attachment"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
@@ -735,6 +785,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" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 421 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 664 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 608 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 552 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 631 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 653 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 652 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 531 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 685 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 603 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 93 KiB |
1
app/src/main/assets/emoji/emoji_data.json
Normal file
@@ -65,6 +65,8 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@@ -82,7 +84,7 @@ import java.util.concurrent.Executor;
|
|||||||
@RequiresApi(21)
|
@RequiresApi(21)
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
public final class SignalCameraView extends FrameLayout {
|
public final class SignalCameraView extends FrameLayout {
|
||||||
static final String TAG = SignalCameraView.class.getSimpleName();
|
static final String TAG = Log.tag(SignalCameraView.class);
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -512,7 +512,12 @@ final class SignalCameraXModule {
|
|||||||
return rotationDegrees;
|
return rotationDegrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeExperimentalUsageError")
|
||||||
public void invalidateView() {
|
public void invalidateView() {
|
||||||
|
if (mPreview != null) {
|
||||||
|
mPreview.setTargetRotation(getDisplaySurfaceRotation()); // Fixes issue #10940 (rotation not updated on phones using "Legacy API")
|
||||||
|
}
|
||||||
|
|
||||||
updateViewInfo();
|
updateViewInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void e(@NonNull String tag, @NonNull String message) {
|
public static void e(@NonNull String tag, @NonNull String message) {
|
||||||
e(tag, message, null);
|
SignalGlideCodecs.getLogProvider().e(tag, message, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable) {
|
public static void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
|
||||||
import org.signal.glide.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.glide.apng.io.APNGReader;
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
import org.signal.glide.apng.io.APNGWriter;
|
import org.signal.glide.apng.io.APNGWriter;
|
||||||
import org.signal.glide.common.decode.Frame;
|
import org.signal.glide.common.decode.Frame;
|
||||||
@@ -32,7 +32,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class APNGDecoder extends FrameSeqDecoder<APNGReader, APNGWriter> {
|
public class APNGDecoder extends FrameSeqDecoder<APNGReader, APNGWriter> {
|
||||||
|
|
||||||
private static final String TAG = APNGDecoder.class.getSimpleName();
|
private static final String TAG = Log.tag(APNGDecoder.class);
|
||||||
|
|
||||||
private APNGWriter apngWriter;
|
private APNGWriter apngWriter;
|
||||||
private int mLoopCount;
|
private int mLoopCount;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import android.os.Message;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
|
||||||
|
|
||||||
import org.signal.glide.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.glide.common.decode.FrameSeqDecoder;
|
import org.signal.glide.common.decode.FrameSeqDecoder;
|
||||||
import org.signal.glide.common.loader.Loader;
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ import java.util.Set;
|
|||||||
* @CreateDate: 2019/3/27
|
* @CreateDate: 2019/3/27
|
||||||
*/
|
*/
|
||||||
public abstract class FrameAnimationDrawable<Decoder extends FrameSeqDecoder> extends Drawable implements Animatable2Compat, FrameSeqDecoder.RenderListener {
|
public abstract class FrameAnimationDrawable<Decoder extends FrameSeqDecoder> extends Drawable implements Animatable2Compat, FrameSeqDecoder.RenderListener {
|
||||||
private static final String TAG = FrameAnimationDrawable.class.getSimpleName();
|
private static final String TAG = Log.tag(FrameAnimationDrawable.class);
|
||||||
private final Paint paint = new Paint();
|
private final Paint paint = new Paint();
|
||||||
private final Decoder frameSeqDecoder;
|
private final Decoder frameSeqDecoder;
|
||||||
private DrawFilter drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
private DrawFilter drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import android.os.Looper;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import org.signal.glide.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.glide.common.executor.FrameDecoderExecutor;
|
import org.signal.glide.common.executor.FrameDecoderExecutor;
|
||||||
import org.signal.glide.common.io.Reader;
|
import org.signal.glide.common.io.Reader;
|
||||||
import org.signal.glide.common.io.Writer;
|
import org.signal.glide.common.io.Writer;
|
||||||
@@ -39,7 +39,7 @@ import java.util.concurrent.locks.LockSupport;
|
|||||||
* @CreateDate: 2019/3/27
|
* @CreateDate: 2019/3/27
|
||||||
*/
|
*/
|
||||||
public abstract class FrameSeqDecoder<R extends Reader, W extends Writer> {
|
public abstract class FrameSeqDecoder<R extends Reader, W extends Writer> {
|
||||||
private static final String TAG = FrameSeqDecoder.class.getSimpleName();
|
private static final String TAG = Log.tag(FrameSeqDecoder.class);
|
||||||
private final int taskId;
|
private final int taskId;
|
||||||
|
|
||||||
private final Loader mLoader;
|
private final Loader mLoader;
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ public final class AppCapabilities {
|
|||||||
* asking if the user has set a Signal PIN or not.
|
* asking if the user has set a Signal PIN or not.
|
||||||
*/
|
*/
|
||||||
public static AccountAttributes.Capabilities getCapabilities(boolean storageCapable) {
|
public static AccountAttributes.Capabilities getCapabilities(boolean storageCapable) {
|
||||||
return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION);
|
return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION, FeatureFlags.senderKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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.EmojiSearchIndexDownloadJob;
|
||||||
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.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
@@ -43,6 +44,7 @@ public final class AppInitialization {
|
|||||||
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()));
|
||||||
}
|
}
|
||||||
@@ -53,10 +55,13 @@ public final class AppInitialization {
|
|||||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||||
SignalStore.onFirstEverAppLaunch();
|
SignalStore.onFirstEverAppLaunch();
|
||||||
SignalStore.onboarding().clearAll();
|
SignalStore.onboarding().clearAll();
|
||||||
|
TextSecurePreferences.onPostBackupRestore(context);
|
||||||
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()));
|
||||||
|
EmojiSearchIndexDownloadJob.scheduleImmediately();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,6 +81,7 @@ public final class AppInitialization {
|
|||||||
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()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,23 +17,20 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.SensorManager;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
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 net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
|
||||||
import org.conscrypt.Conscrypt;
|
import org.conscrypt.Conscrypt;
|
||||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||||
import org.signal.core.util.ShakeDetector;
|
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.AndroidLogger;
|
import org.signal.core.util.logging.AndroidLogger;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
@@ -42,12 +39,15 @@ import org.signal.core.util.tracing.Tracer;
|
|||||||
import org.signal.glide.SignalGlideCodecs;
|
import org.signal.glide.SignalGlideCodecs;
|
||||||
import org.signal.ringrtc.CallManager;
|
import org.signal.ringrtc.CallManager;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
||||||
|
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
|
||||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||||
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
@@ -57,23 +57,24 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||||
import org.thoughtcrime.securesms.logging.LogSecretProvider;
|
import org.thoughtcrime.securesms.logging.LogSecretProvider;
|
||||||
|
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||||
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;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
|
import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
|
||||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
|
|
||||||
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
||||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||||
import org.thoughtcrime.securesms.shakereport.ShakeToReport;
|
|
||||||
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.AppStartup;
|
||||||
|
import org.thoughtcrime.securesms.util.ByteUnit;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||||
@@ -86,10 +87,11 @@ import org.webrtc.voiceengine.WebRtcAudioUtils;
|
|||||||
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
||||||
|
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will be called once when the TextSecure process is created.
|
* Will be called once when the TextSecure process is created.
|
||||||
*
|
*
|
||||||
@@ -98,15 +100,11 @@ 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 = Log.tag(ApplicationContext.class);
|
||||||
|
|
||||||
private ExpiringMessageManager expiringMessageManager;
|
private PersistentLogger persistentLogger;
|
||||||
private ViewOnceMessageManager viewOnceMessageManager;
|
|
||||||
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();
|
||||||
@@ -127,17 +125,22 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
AppStartup.getInstance().addBlocking("security-provider", this::initializeSecurityProvider)
|
AppStartup.getInstance().addBlocking("security-provider", this::initializeSecurityProvider)
|
||||||
.addBlocking("logging", () -> {
|
.addBlocking("logging", () -> {
|
||||||
initializeLogging();
|
initializeLogging();
|
||||||
Log.i(TAG, "onCreate()");
|
Log.i(TAG, "onCreate()");
|
||||||
})
|
})
|
||||||
.addBlocking("crash-handling", this::initializeCrashHandling)
|
.addBlocking("crash-handling", this::initializeCrashHandling)
|
||||||
.addBlocking("eat-db", () -> DatabaseFactory.getInstance(this))
|
.addBlocking("sqlcipher-init", () -> SqlCipherLibraryLoader.load(this))
|
||||||
|
.addBlocking("rx-init", () -> {
|
||||||
|
RxJavaPlugins.setInitIoSchedulerHandler(schedulerSupplier -> Schedulers.from(SignalExecutors.BOUNDED_IO, true, false));
|
||||||
|
RxJavaPlugins.setInitComputationSchedulerHandler(schedulerSupplier -> Schedulers.from(SignalExecutors.BOUNDED, true, false));
|
||||||
|
})
|
||||||
.addBlocking("app-dependencies", this::initializeAppDependencies)
|
.addBlocking("app-dependencies", this::initializeAppDependencies)
|
||||||
|
.addBlocking("notification-channels", () -> NotificationChannels.create(this))
|
||||||
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
|
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
|
||||||
.addBlocking("app-migrations", this::initializeApplicationMigrations)
|
.addBlocking("app-migrations", this::initializeApplicationMigrations)
|
||||||
.addBlocking("ring-rtc", this::initializeRingRtc)
|
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||||
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this))
|
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this))
|
||||||
.addBlocking("lifecycle-observer", () -> ProcessLifecycleOwner.get().getLifecycle().addObserver(this))
|
.addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this))
|
||||||
.addBlocking("message-retriever", this::initializeMessageRetrieval)
|
.addBlocking("message-retriever", this::initializeMessageRetrieval)
|
||||||
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
|
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
|
||||||
.addBlocking("vector-compat", () -> {
|
.addBlocking("vector-compat", () -> {
|
||||||
@@ -145,7 +148,16 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.addBlocking("proxy-init", () -> {
|
||||||
|
if (SignalStore.proxy().isProxyEnabled()) {
|
||||||
|
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
|
||||||
|
Conscrypt.setUseEngineSocketByDefault(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addBlocking("blob-provider", this::initializeBlobProvider)
|
||||||
|
.addBlocking("feature-flags", FeatureFlags::init)
|
||||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||||
|
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
||||||
.addNonBlocking(this::initializeGcmCheck)
|
.addNonBlocking(this::initializeGcmCheck)
|
||||||
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
||||||
.addNonBlocking(this::initializePeriodicTasks)
|
.addNonBlocking(this::initializePeriodicTasks)
|
||||||
@@ -153,25 +165,25 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
.addNonBlocking(this::initializePendingMessages)
|
.addNonBlocking(this::initializePendingMessages)
|
||||||
.addNonBlocking(this::initializeCleanup)
|
.addNonBlocking(this::initializeCleanup)
|
||||||
.addNonBlocking(this::initializeGlideCodecs)
|
.addNonBlocking(this::initializeGlideCodecs)
|
||||||
.addNonBlocking(FeatureFlags::init)
|
|
||||||
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
|
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
|
||||||
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||||
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
||||||
|
.addNonBlocking(EmojiSource::refresh)
|
||||||
|
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
||||||
.addPostRender(this::initializeExpiringMessageManager)
|
.addPostRender(this::initializeExpiringMessageManager)
|
||||||
.addPostRender(this::initializeBlobProvider)
|
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
||||||
.addPostRender(() -> NotificationChannels.create(this))
|
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
||||||
|
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
|
||||||
|
.addPostRender(() -> DatabaseFactory.getMessageLogDatabase(this).trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
|
||||||
|
|
||||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
Tracer.getInstance().end("Application#onCreate()");
|
Tracer.getInstance().end("Application#onCreate()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart(@NonNull LifecycleOwner owner) {
|
public void onForeground() {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
isAppVisible = true;
|
|
||||||
Log.i(TAG, "App is now visible.");
|
Log.i(TAG, "App is now visible.");
|
||||||
|
|
||||||
ApplicationDependencies.getFrameRateTracker().begin();
|
ApplicationDependencies.getFrameRateTracker().begin();
|
||||||
@@ -192,8 +204,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
||||||
@@ -201,21 +212,6 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
ApplicationDependencies.getShakeToReport().disable();
|
ApplicationDependencies.getShakeToReport().disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpiringMessageManager getExpiringMessageManager() {
|
|
||||||
if (expiringMessageManager == null) {
|
|
||||||
initializeExpiringMessageManager();
|
|
||||||
}
|
|
||||||
return expiringMessageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ViewOnceMessageManager getViewOnceMessageManager() {
|
|
||||||
return viewOnceMessageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAppVisible() {
|
|
||||||
return isAppVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentLogger getPersistentLogger() {
|
public PersistentLogger getPersistentLogger() {
|
||||||
return persistentLogger;
|
return persistentLogger;
|
||||||
}
|
}
|
||||||
@@ -252,8 +248,8 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeLogging() {
|
private void initializeLogging() {
|
||||||
persistentLogger = new PersistentLogger(this, LogSecretProvider.getOrCreateAttachmentSecret(this), BuildConfig.VERSION_NAME);
|
persistentLogger = new PersistentLogger(this, LogSecretProvider.getOrCreateAttachmentSecret(this), BuildConfig.VERSION_NAME, FeatureFlags.internalUser() ? 15 : 7, ByteUnit.KILOBYTES.toBytes(300));
|
||||||
org.signal.core.util.logging.Log.initialize(new AndroidLogger(), persistentLogger);
|
org.signal.core.util.logging.Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), persistentLogger);
|
||||||
|
|
||||||
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
||||||
}
|
}
|
||||||
@@ -272,7 +268,7 @@ 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() {
|
||||||
@@ -287,6 +283,9 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 90) {
|
} 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.");
|
Log.i(TAG, "Detected a new install that doesn't have passphrases disabled -- assuming bad initialization.");
|
||||||
AppInitialization.onRepairFirstEverAppLaunch(this);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,11 +306,15 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeExpiringMessageManager() {
|
private void initializeExpiringMessageManager() {
|
||||||
this.expiringMessageManager = new ExpiringMessageManager(this);
|
ApplicationDependencies.getExpiringMessageManager().checkSchedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeRevealableMessageManager() {
|
private void initializeRevealableMessageManager() {
|
||||||
this.viewOnceMessageManager = new ViewOnceMessageManager(this);
|
ApplicationDependencies.getViewOnceMessageManager().scheduleIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializePendingRetryReceiptManager() {
|
||||||
|
ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializePeriodicTasks() {
|
private void initializePeriodicTasks() {
|
||||||
@@ -319,6 +322,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
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);
|
||||||
@@ -327,31 +331,11 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
private void initializeRingRtc() {
|
private void initializeRingRtc() {
|
||||||
try {
|
try {
|
||||||
Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{
|
if (RtcDeviceLists.hardwareAECBlocked()) {
|
||||||
add("Pixel");
|
|
||||||
add("Pixel XL");
|
|
||||||
add("Moto G5");
|
|
||||||
add("Moto G (5S) Plus");
|
|
||||||
add("Moto G4");
|
|
||||||
add("TA-1053");
|
|
||||||
add("Mi A1");
|
|
||||||
add("Mi A2");
|
|
||||||
add("E5823"); // Sony z5 compact
|
|
||||||
add("Redmi Note 5");
|
|
||||||
add("FP2"); // Fairphone FP2
|
|
||||||
add("MI 5");
|
|
||||||
}};
|
|
||||||
|
|
||||||
Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {{
|
|
||||||
add("Pixel");
|
|
||||||
add("Pixel XL");
|
|
||||||
}};
|
|
||||||
|
|
||||||
if (HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) {
|
|
||||||
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
|
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) {
|
if (!RtcDeviceLists.openSLESAllowed()) {
|
||||||
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +368,7 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -392,7 +376,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private void initializeBlobProvider() {
|
private void initializeBlobProvider() {
|
||||||
BlobProvider.getInstance().onSessionStart(this);
|
BlobProvider.getInstance().initialize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
|||||||
@@ -1,362 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
* Copyright (C) 2013-2017 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.help.HelpFragment;
|
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|
||||||
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.BackupsPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.DataAndStoragePreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
|
||||||
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.recipients.Recipient;
|
|
||||||
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.DynamicTheme;
|
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Activity for application preference display and management.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|
||||||
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";
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
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_NOTIFICATIONS = "preference_category_notifications";
|
|
||||||
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
|
|
||||||
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
|
|
||||||
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
|
|
||||||
private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
|
|
||||||
private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
|
|
||||||
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
|
|
||||||
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
|
||||||
private static final String PREFERENCE_CATEGORY_DONATE = "preference_category_donate";
|
|
||||||
|
|
||||||
private static final String WAS_CONFIGURATION_UPDATED = "was_configuration_updated";
|
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private boolean wasConfigurationUpdated = false;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreCreate() {
|
|
||||||
dynamicTheme.onCreate(this);
|
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle icicle, boolean ready) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
if (getIntent() != null && getIntent().getCategories() != null && getIntent().getCategories().contains("android.intent.category.NOTIFICATION_PREFERENCES")) {
|
|
||||||
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 (icicle == null) {
|
|
||||||
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
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dynamicTheme.onResume(this);
|
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
|
||||||
{
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentById(android.R.id.content);
|
|
||||||
fragment.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSupportNavigateUp() {
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
if (fragmentManager.getBackStackEntryCount() > 0) {
|
|
||||||
fragmentManager.popBackStack();
|
|
||||||
} else {
|
|
||||||
if (wasConfigurationUpdated) {
|
|
||||||
setResult(MainActivity.RESULT_CONFIG_CHANGED);
|
|
||||||
} else {
|
|
||||||
setResult(RESULT_OK);
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
onSupportNavigateUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
||||||
if (key.equals(TextSecurePreferences.THEME_PREF)) {
|
|
||||||
DynamicTheme.setDefaultDayNightMode(this);
|
|
||||||
recreate();
|
|
||||||
} else if (key.equals(TextSecurePreferences.LANGUAGE_PREF)) {
|
|
||||||
CachedInflater.from(this).clear();
|
|
||||||
wasConfigurationUpdated = true;
|
|
||||||
recreate();
|
|
||||||
|
|
||||||
Intent intent = new Intent(this, KeyCachingService.class);
|
|
||||||
intent.setAction(KeyCachingService.LOCALE_CHANGE_EVENT);
|
|
||||||
startService(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle icicle) {
|
|
||||||
super.onCreate(icicle);
|
|
||||||
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_PROFILE)
|
|
||||||
.setOnPreferenceClickListener(new ProfileClickListener());
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_USERNAME)
|
|
||||||
.setOnPreferenceClickListener(new UsernameClickListener());
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APP_PROTECTION));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_CHATS)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_STORAGE)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_STORAGE));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_DEVICES)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_HELP)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_HELP));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_DONATE)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DONATE));
|
|
||||||
|
|
||||||
tintIcons();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tintIcons() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) return;
|
|
||||||
|
|
||||||
Preference preference = this.findPreference(PREFERENCE_CATEGORY_SMS_MMS);
|
|
||||||
preference.getIcon().setColorFilter(ContextCompat.getColor(requireContext(), R.color.signal_icon_tint_primary), PorterDuff.Mode.SRC_IN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
|
||||||
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
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.text_secure_normal__menu_settings);
|
|
||||||
setCategorySummaries();
|
|
||||||
setCategoryVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCategorySummaries() {
|
|
||||||
((ProfilePreference)this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
|
|
||||||
|
|
||||||
if (FeatureFlags.usernames()) {
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_USERNAME)
|
|
||||||
.setVisible(shouldDisplayUsernameReminder());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
|
|
||||||
.setSummary(SmsMmsPreferenceFragment.getSummary(getActivity()));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
|
||||||
.setSummary(NotificationsPreferenceFragment.getSummary(getActivity()));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION)
|
|
||||||
.setSummary(AppProtectionPreferenceFragment.getSummary(getActivity()));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
|
|
||||||
.setSummary(AppearancePreferenceFragment.getSummary(getActivity()));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_CHATS)
|
|
||||||
.setSummary(ChatsPreferenceFragment.getSummary(getActivity()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCategoryVisibility() {
|
|
||||||
Preference devicePreference = this.findPreference(PREFERENCE_CATEGORY_DEVICES);
|
|
||||||
if (devicePreference != null && !TextSecurePreferences.isPushRegistered(getActivity())) {
|
|
||||||
getPreferenceScreen().removePreference(devicePreference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean shouldDisplayUsernameReminder() {
|
|
||||||
return FeatureFlags.usernames() && !Recipient.self().getUsername().isPresent() && SignalStore.misc().shouldShowUsernameReminder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
|
|
||||||
private String category;
|
|
||||||
|
|
||||||
CategoryClickListener(String category) {
|
|
||||||
this.category = category;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
Fragment fragment = null;
|
|
||||||
|
|
||||||
switch (category) {
|
|
||||||
case PREFERENCE_CATEGORY_SMS_MMS:
|
|
||||||
fragment = new SmsMmsPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_NOTIFICATIONS:
|
|
||||||
fragment = new NotificationsPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_APP_PROTECTION:
|
|
||||||
fragment = new AppProtectionPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_APPEARANCE:
|
|
||||||
fragment = new AppearancePreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_CHATS:
|
|
||||||
fragment = new ChatsPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_STORAGE:
|
|
||||||
fragment = new DataAndStoragePreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_DEVICES:
|
|
||||||
Intent intent = new Intent(getActivity(), DeviceActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_ADVANCED:
|
|
||||||
fragment = new AdvancedPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_HELP:
|
|
||||||
fragment = new HelpFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_DONATE:
|
|
||||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.donate_url));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragment != null) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
|
|
||||||
((ApplicationPreferencesActivity) requireActivity()).pushFragment(fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
requireActivity().startActivity(EditProfileActivity.getIntentForUserProfileEdit(preference.getContext()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UsernameClickListener implements Preference.OnPreferenceClickListener {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
requireActivity().startActivity(EditProfileActivity.getIntentForUsernameEdit(preference.getContext()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -11,8 +11,12 @@ import androidx.lifecycle.Observer;
|
|||||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
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.conversation.ConversationMessage;
|
||||||
|
import org.thoughtcrime.securesms.conversation.colors.Colorizable;
|
||||||
|
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
||||||
|
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord;
|
||||||
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.giph.mp4.GiphyMp4Playable;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
@@ -20,13 +24,14 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
|||||||
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.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
|
import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface BindableConversationItem extends Unbindable {
|
public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, Colorizable {
|
||||||
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
@NonNull ConversationMessage messageRecord,
|
@NonNull ConversationMessage messageRecord,
|
||||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
@@ -36,12 +41,21 @@ public interface BindableConversationItem extends Unbindable {
|
|||||||
@NonNull Set<ConversationMessage> batchSelected,
|
@NonNull Set<ConversationMessage> batchSelected,
|
||||||
@NonNull Recipient recipients,
|
@NonNull Recipient recipients,
|
||||||
@Nullable String searchQuery,
|
@Nullable String searchQuery,
|
||||||
boolean pulseMention);
|
boolean pulseMention,
|
||||||
|
boolean hasWallpaper,
|
||||||
|
boolean isMessageRequestAccepted,
|
||||||
|
@NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory,
|
||||||
|
boolean canPlayInline,
|
||||||
|
@NonNull Colorizer colorizer);
|
||||||
|
|
||||||
ConversationMessage getConversationMessage();
|
ConversationMessage getConversationMessage();
|
||||||
|
|
||||||
void setEventListener(@Nullable EventListener listener);
|
void setEventListener(@Nullable EventListener listener);
|
||||||
|
|
||||||
|
default void updateTimestamps() {
|
||||||
|
// Intentionally Blank.
|
||||||
|
}
|
||||||
|
|
||||||
interface EventListener {
|
interface EventListener {
|
||||||
void onQuoteClicked(MmsMessageRecord messageRecord);
|
void onQuoteClicked(MmsMessageRecord messageRecord);
|
||||||
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
||||||
@@ -55,16 +69,24 @@ public interface BindableConversationItem extends Unbindable {
|
|||||||
void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms);
|
void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms);
|
||||||
void onGroupMemberClicked(@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 onMessageWithRecaptchaNeededClicked(@NonNull MessageRecord messageRecord);
|
||||||
|
void onIncomingIdentityMismatchClicked(@NonNull RecipientId recipientId);
|
||||||
void onRegisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
|
void onRegisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
|
||||||
void onUnregisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
|
void onUnregisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
|
||||||
void onVoiceNotePause(@NonNull Uri uri);
|
void onVoiceNotePause(@NonNull Uri uri);
|
||||||
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double position);
|
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double position);
|
||||||
void onVoiceNoteSeekTo(@NonNull Uri uri, double position);
|
void onVoiceNoteSeekTo(@NonNull Uri uri, double position);
|
||||||
|
void onVoiceNotePlaybackSpeedChanged(@NonNull Uri uri, float speed);
|
||||||
void onGroupMigrationLearnMoreClicked(@NonNull GroupMigrationMembershipChange membershipChange);
|
void onGroupMigrationLearnMoreClicked(@NonNull GroupMigrationMembershipChange membershipChange);
|
||||||
void onDecryptionFailedLearnMoreClicked();
|
void onChatSessionRefreshLearnMoreClicked();
|
||||||
|
void onBadDecryptLearnMoreClicked(@NonNull RecipientId author);
|
||||||
void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient);
|
void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient);
|
||||||
void onJoinGroupCallClicked();
|
void onJoinGroupCallClicked();
|
||||||
void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId);
|
void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId);
|
||||||
|
void onEnableCallNotificationsClicked();
|
||||||
|
void onPlayInlineContent(ConversationMessage conversationMessage);
|
||||||
|
void onInMemoryMessageClicked(@NonNull InMemoryMessageRecord messageRecord);
|
||||||
|
void onViewGroupDescriptionChange(@Nullable GroupId groupId, @NonNull String description, boolean isMessageRequestAccepted);
|
||||||
|
|
||||||
/** @return true if handled, false if you want to let the normal url handling continue */
|
/** @return true if handled, false if you want to let the normal url handling continue */
|
||||||
boolean onUrlClicked(@NonNull String url);
|
boolean onUrlClicked(@NonNull String url);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import androidx.annotation.WorkerThread;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.lifecycle.Lifecycle;
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
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.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
@@ -30,15 +32,15 @@ public final class BlockUnblockDialog {
|
|||||||
AlertDialog.Builder::show);
|
AlertDialog.Builder::show);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showBlockAndDeleteFor(@NonNull Context context,
|
public static void showBlockAndReportSpamFor(@NonNull Context context,
|
||||||
@NonNull Lifecycle lifecycle,
|
@NonNull Lifecycle lifecycle,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
@NonNull Runnable onBlock,
|
@NonNull Runnable onBlock,
|
||||||
@NonNull Runnable onBlockAndDelete)
|
@NonNull Runnable onBlockAndReportSpam)
|
||||||
{
|
{
|
||||||
SimpleTask.run(lifecycle,
|
SimpleTask.run(lifecycle,
|
||||||
() -> buildBlockFor(context, recipient, onBlock, onBlockAndDelete),
|
() -> buildBlockFor(context, recipient, onBlock, onBlockAndReportSpam),
|
||||||
AlertDialog.Builder::show);
|
AlertDialog.Builder::show);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showUnblockFor(@NonNull Context context,
|
public static void showUnblockFor(@NonNull Context context,
|
||||||
@@ -55,11 +57,11 @@ public final class BlockUnblockDialog {
|
|||||||
private static AlertDialog.Builder buildBlockFor(@NonNull Context context,
|
private static AlertDialog.Builder buildBlockFor(@NonNull Context context,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
@NonNull Runnable onBlock,
|
@NonNull Runnable onBlock,
|
||||||
@Nullable Runnable onBlockAndDelete)
|
@Nullable Runnable onBlockAndReportSpam)
|
||||||
{
|
{
|
||||||
recipient = recipient.resolve();
|
recipient = recipient.resolve();
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
|
||||||
Resources resources = context.getResources();
|
Resources resources = context.getResources();
|
||||||
|
|
||||||
if (recipient.isGroup()) {
|
if (recipient.isGroup()) {
|
||||||
@@ -78,10 +80,10 @@ public final class BlockUnblockDialog {
|
|||||||
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context)));
|
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context)));
|
||||||
builder.setMessage(R.string.BlockUnblockDialog_blocked_people_wont_be_able_to_call_you_or_send_you_messages);
|
builder.setMessage(R.string.BlockUnblockDialog_blocked_people_wont_be_able_to_call_you_or_send_you_messages);
|
||||||
|
|
||||||
if (onBlockAndDelete != null) {
|
if (onBlockAndReportSpam != null) {
|
||||||
builder.setNeutralButton(android.R.string.cancel, null);
|
builder.setNeutralButton(android.R.string.cancel, null);
|
||||||
builder.setPositiveButton(R.string.BlockUnblockDialog_block_and_delete, (d, w) -> onBlockAndDelete.run());
|
builder.setNegativeButton(R.string.BlockUnblockDialog_report_spam_and_block, (d, w) -> onBlockAndReportSpam.run());
|
||||||
builder.setNegativeButton(R.string.BlockUnblockDialog_block, (d, w) -> onBlock.run());
|
builder.setPositiveButton(R.string.BlockUnblockDialog_block, (d, w) -> onBlock.run());
|
||||||
} else {
|
} else {
|
||||||
builder.setPositiveButton(R.string.BlockUnblockDialog_block, ((dialog, which) -> onBlock.run()));
|
builder.setPositiveButton(R.string.BlockUnblockDialog_block, ((dialog, which) -> onBlock.run()));
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
@@ -98,7 +100,7 @@ public final class BlockUnblockDialog {
|
|||||||
{
|
{
|
||||||
recipient = recipient.resolve();
|
recipient = recipient.resolve();
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
|
||||||
Resources resources = context.getResources();
|
Resources resources = context.getResources();
|
||||||
|
|
||||||
if (recipient.isGroup()) {
|
if (recipient.isGroup()) {
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
||||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
|
||||||
import org.thoughtcrime.securesms.util.VerifySpan;
|
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
|
||||||
|
|
||||||
public class ConfirmIdentityDialog extends AlertDialog {
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final String TAG = ConfirmIdentityDialog.class.getSimpleName();
|
|
||||||
|
|
||||||
private OnClickListener callback;
|
|
||||||
|
|
||||||
public ConfirmIdentityDialog(Context context,
|
|
||||||
MessageRecord messageRecord,
|
|
||||||
IdentityKeyMismatch mismatch)
|
|
||||||
{
|
|
||||||
super(context);
|
|
||||||
|
|
||||||
Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context));
|
|
||||||
String name = recipient.getDisplayName(context);
|
|
||||||
String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name);
|
|
||||||
SpannableString spannableString = new SpannableString(introduction + " " +
|
|
||||||
context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact));
|
|
||||||
|
|
||||||
spannableString.setSpan(new VerifySpan(context, mismatch),
|
|
||||||
introduction.length()+1, spannableString.length(),
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
|
|
||||||
setTitle(name);
|
|
||||||
setMessage(spannableString);
|
|
||||||
|
|
||||||
setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(messageRecord, mismatch, recipient.getId()));
|
|
||||||
setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), new CancelListener());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void show() {
|
|
||||||
super.show();
|
|
||||||
((TextView)this.findViewById(android.R.id.message))
|
|
||||||
.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCallback(OnClickListener callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AcceptListener implements OnClickListener {
|
|
||||||
|
|
||||||
private final MessageRecord messageRecord;
|
|
||||||
private final IdentityKeyMismatch mismatch;
|
|
||||||
private final RecipientId recipientId;
|
|
||||||
|
|
||||||
private AcceptListener(MessageRecord messageRecord, IdentityKeyMismatch mismatch, RecipientId recipientId) {
|
|
||||||
this.messageRecord = messageRecord;
|
|
||||||
this.mismatch = mismatch;
|
|
||||||
this.recipientId = recipientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
new AsyncTask<Void, Void, Void>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
synchronized (SESSION_LOCK) {
|
|
||||||
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1);
|
|
||||||
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext());
|
|
||||||
|
|
||||||
identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
processMessageRecord(messageRecord);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processMessageRecord(MessageRecord messageRecord) {
|
|
||||||
if (messageRecord.isOutgoing()) processOutgoingMessageRecord(messageRecord);
|
|
||||||
else processIncomingMessageRecord(messageRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processOutgoingMessageRecord(MessageRecord messageRecord) {
|
|
||||||
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
|
||||||
MessageDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
|
|
||||||
|
|
||||||
if (messageRecord.isMms()) {
|
|
||||||
mmsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
|
||||||
mismatch.getRecipientId(getContext()),
|
|
||||||
mismatch.getIdentityKey());
|
|
||||||
|
|
||||||
if (messageRecord.getRecipient().isPushGroup()) {
|
|
||||||
MessageSender.resendGroupMessage(getContext(), messageRecord, Recipient.resolved(mismatch.getRecipientId(getContext())).getId());
|
|
||||||
} else {
|
|
||||||
MessageSender.resend(getContext(), messageRecord);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
|
||||||
mismatch.getRecipientId(getContext()),
|
|
||||||
mismatch.getIdentityKey());
|
|
||||||
|
|
||||||
MessageSender.resend(getContext(), messageRecord);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processIncomingMessageRecord(MessageRecord messageRecord) {
|
|
||||||
try {
|
|
||||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getContext());
|
|
||||||
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
|
||||||
|
|
||||||
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
|
||||||
mismatch.getRecipientId(getContext()),
|
|
||||||
mismatch.getIdentityKey());
|
|
||||||
|
|
||||||
boolean legacy = !messageRecord.isContentBundleKeyExchange();
|
|
||||||
|
|
||||||
SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE,
|
|
||||||
Optional.of(RecipientUtil.toSignalServiceAddress(getContext(), messageRecord.getIndividualRecipient())),
|
|
||||||
messageRecord.getRecipientDeviceId(),
|
|
||||||
messageRecord.getDateSent(),
|
|
||||||
legacy ? Base64.decode(messageRecord.getBody()) : null,
|
|
||||||
!legacy ? Base64.decode(messageRecord.getBody()) : null,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
null);
|
|
||||||
|
|
||||||
long pushId = pushDatabase.insert(envelope);
|
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), pushId, messageRecord.getId()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
|
|
||||||
if (callback != null) callback.onClick(null, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CancelListener implements OnClickListener {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
if (callback != null) callback.onClick(null, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -20,17 +20,18 @@ import android.content.Context;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
import org.thoughtcrime.securesms.recipients.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;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -47,7 +48,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
ContactSelectionListFragment.OnContactSelectedListener,
|
ContactSelectionListFragment.OnContactSelectedListener,
|
||||||
ContactSelectionListFragment.ScrollCallback
|
ContactSelectionListFragment.ScrollCallback
|
||||||
{
|
{
|
||||||
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(ContactSelectionActivity.class);
|
||||||
|
|
||||||
public static final String EXTRA_LAYOUT_RES_ID = "layout_res_id";
|
public static final String EXTRA_LAYOUT_RES_ID = "layout_res_id";
|
||||||
|
|
||||||
@@ -55,7 +56,8 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
|
|
||||||
protected ContactSelectionListFragment contactsFragment;
|
protected ContactSelectionListFragment contactsFragment;
|
||||||
|
|
||||||
private ContactFilterToolbar toolbar;
|
private Toolbar toolbar;
|
||||||
|
private ContactFilterView contactFilterView;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreCreate() {
|
protected void onPreCreate() {
|
||||||
@@ -65,13 +67,14 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle icicle, boolean ready) {
|
protected void onCreate(Bundle icicle, boolean ready) {
|
||||||
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
||||||
int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL
|
int displayMode = Util.isDefaultSmsProvider(this) ? DisplayMode.FLAG_ALL
|
||||||
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
|
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
|
||||||
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(getIntent().getIntExtra(EXTRA_LAYOUT_RES_ID, R.layout.contact_selection_activity));
|
setContentView(getIntent().getIntExtra(EXTRA_LAYOUT_RES_ID, R.layout.contact_selection_activity));
|
||||||
|
|
||||||
|
initializeContactFilterView();
|
||||||
initializeToolbar();
|
initializeToolbar();
|
||||||
initializeResources();
|
initializeResources();
|
||||||
initializeSearch();
|
initializeSearch();
|
||||||
@@ -83,28 +86,34 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
dynamicTheme.onResume(this);
|
dynamicTheme.onResume(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ContactFilterToolbar getToolbar() {
|
protected Toolbar getToolbar() {
|
||||||
return toolbar;
|
return toolbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ContactFilterView getContactFilterView() {
|
||||||
|
return contactFilterView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeContactFilterView() {
|
||||||
|
this.contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeToolbar() {
|
private void initializeToolbar() {
|
||||||
this.toolbar = findViewById(R.id.toolbar);
|
this.toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
|
||||||
getSupportActionBar().setIcon(null);
|
getSupportActionBar().setIcon(null);
|
||||||
getSupportActionBar().setLogo(null);
|
getSupportActionBar().setLogo(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeSearch() {
|
private void initializeSearch() {
|
||||||
toolbar.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter));
|
contactFilterView.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -155,7 +164,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
|||||||
ContactSelectionActivity activity = this.activity.get();
|
ContactSelectionActivity activity = this.activity.get();
|
||||||
|
|
||||||
if (activity != null && !activity.isFinishing()) {
|
if (activity != null && !activity.isFinishing()) {
|
||||||
activity.toolbar.clear();
|
activity.contactFilterView.clear();
|
||||||
activity.contactsFragment.resetQueryFilter();
|
activity.contactsFragment.resetQueryFilter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,12 +57,14 @@ import com.pnikosis.materialishprogress.ProgressWheel;
|
|||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
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.components.recyclerview.ToolbarShadowAnimationHelper;
|
||||||
|
import org.thoughtcrime.securesms.contacts.AbstractContactsCursorLoader;
|
||||||
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;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
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.LetterHeaderDecoration;
|
||||||
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.groups.SelectionLimits;
|
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||||
@@ -73,9 +75,9 @@ 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.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;
|
||||||
@@ -110,22 +112,30 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
public static final String SELECTION_LIMITS = "selection_limits";
|
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 HIDE_COUNT = "hide_count";
|
||||||
|
public static final String CAN_SELECT_SELF = "can_select_self";
|
||||||
|
public static final String DISPLAY_CHIPS = "display_chips";
|
||||||
|
public static final String RV_PADDING_BOTTOM = "recycler_view_padding_bottom";
|
||||||
|
public static final String RV_CLIP = "recycler_view_clipping";
|
||||||
|
|
||||||
|
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 OnSelectionLimitReachedListener onSelectionLimitReachedListener;
|
||||||
|
private AbstractContactsCursorLoaderFactoryProvider cursorFactoryProvider;
|
||||||
|
private View shadowView;
|
||||||
|
private ToolbarShadowAnimationHelper toolbarShadowAnimationHelper;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
@Nullable private FixedViewsAdapter headerAdapter;
|
@Nullable private FixedViewsAdapter headerAdapter;
|
||||||
@Nullable private FixedViewsAdapter footerAdapter;
|
@Nullable private FixedViewsAdapter footerAdapter;
|
||||||
@@ -136,6 +146,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
private Set<RecipientId> currentSelection;
|
private Set<RecipientId> currentSelection;
|
||||||
private boolean isMulti;
|
private boolean isMulti;
|
||||||
private boolean hideCount;
|
private boolean hideCount;
|
||||||
|
private boolean canSelectSelf;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
@@ -145,9 +156,33 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
listCallback = (ListCallback) context;
|
listCallback = (ListCallback) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getParentFragment() instanceof ScrollCallback) {
|
||||||
|
scrollCallback = (ScrollCallback) getParentFragment();
|
||||||
|
}
|
||||||
|
|
||||||
if (context instanceof ScrollCallback) {
|
if (context instanceof ScrollCallback) {
|
||||||
scrollCallback = (ScrollCallback) context;
|
scrollCallback = (ScrollCallback) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getParentFragment() instanceof OnContactSelectedListener) {
|
||||||
|
onContactSelectedListener = (OnContactSelectedListener) getParentFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof OnContactSelectedListener) {
|
||||||
|
onContactSelectedListener = (OnContactSelectedListener) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof OnSelectionLimitReachedListener) {
|
||||||
|
onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof AbstractContactsCursorLoaderFactoryProvider) {
|
||||||
|
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getParentFragment() instanceof AbstractContactsCursorLoaderFactoryProvider) {
|
||||||
|
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -176,7 +211,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
|
|
||||||
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||||
|
|
||||||
if (activity.getIntent().getBooleanExtra(RECENTS, false)) {
|
if (safeArguments().getBoolean(RECENTS, activity.getIntent().getBooleanExtra(RECENTS, false))) {
|
||||||
LoaderManager.getInstance(this).initLoader(0, null, ContactSelectionListFragment.this);
|
LoaderManager.getInstance(this).initLoader(0, null, ContactSelectionListFragment.this);
|
||||||
} else {
|
} else {
|
||||||
initializeNoContactsPermission();
|
initializeNoContactsPermission();
|
||||||
@@ -199,9 +234,12 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
showContactsProgress = view.findViewById(R.id.progress);
|
showContactsProgress = view.findViewById(R.id.progress);
|
||||||
chipGroup = view.findViewById(R.id.chipGroup);
|
chipGroup = view.findViewById(R.id.chipGroup);
|
||||||
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
|
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
|
||||||
groupLimit = view.findViewById(R.id.group_limit);
|
|
||||||
constraintLayout = view.findViewById(R.id.container);
|
constraintLayout = view.findViewById(R.id.container);
|
||||||
|
shadowView = view.findViewById(R.id.toolbar_shadow);
|
||||||
|
|
||||||
|
toolbarShadowAnimationHelper = new ToolbarShadowAnimationHelper(shadowView);
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(toolbarShadowAnimationHelper);
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
recyclerView.setItemAnimator(new DefaultItemAnimator() {
|
recyclerView.setItemAnimator(new DefaultItemAnimator() {
|
||||||
@Override
|
@Override
|
||||||
@@ -210,13 +248,27 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Intent intent = requireActivity().getIntent();
|
Intent intent = requireActivity().getIntent();
|
||||||
|
Bundle arguments = safeArguments();
|
||||||
|
|
||||||
swipeRefresh.setEnabled(intent.getBooleanExtra(REFRESHABLE, true));
|
int recyclerViewPadBottom = arguments.getInt(RV_PADDING_BOTTOM, intent.getIntExtra(RV_PADDING_BOTTOM, -1));
|
||||||
|
boolean recyclerViewClipping = arguments.getBoolean(RV_CLIP, intent.getBooleanExtra(RV_CLIP, true));
|
||||||
|
|
||||||
hideCount = intent.getBooleanExtra(HIDE_COUNT, false);
|
if (recyclerViewPadBottom != -1) {
|
||||||
selectionLimit = intent.getParcelableExtra(SELECTION_LIMITS);
|
ViewUtil.setPaddingBottom(recyclerView, recyclerViewPadBottom);
|
||||||
isMulti = selectionLimit != null;
|
}
|
||||||
|
|
||||||
|
recyclerView.setClipToPadding(recyclerViewClipping);
|
||||||
|
|
||||||
|
swipeRefresh.setEnabled(arguments.getBoolean(REFRESHABLE, intent.getBooleanExtra(REFRESHABLE, true)));
|
||||||
|
|
||||||
|
hideCount = arguments.getBoolean(HIDE_COUNT, intent.getBooleanExtra(HIDE_COUNT, false));
|
||||||
|
selectionLimit = arguments.getParcelable(SELECTION_LIMITS);
|
||||||
|
if (selectionLimit == null) {
|
||||||
|
selectionLimit = intent.getParcelableExtra(SELECTION_LIMITS);
|
||||||
|
}
|
||||||
|
isMulti = selectionLimit != null;
|
||||||
|
canSelectSelf = arguments.getBoolean(CAN_SELECT_SELF, intent.getBooleanExtra(CAN_SELECT_SELF, !isMulti));
|
||||||
|
|
||||||
if (!isMulti) {
|
if (!isMulti) {
|
||||||
selectionLimit = SelectionLimits.NO_LIMITS;
|
selectionLimit = SelectionLimits.NO_LIMITS;
|
||||||
@@ -224,16 +276,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
|
|
||||||
currentSelection = getCurrentSelection();
|
currentSelection = getCurrentSelection();
|
||||||
|
|
||||||
updateGroupLimit(getChipCount());
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGroupLimit(int chipCount) {
|
private @NonNull Bundle safeArguments() {
|
||||||
int members = currentSelection.size() + chipCount;
|
return getArguments() != null ? getArguments() : new Bundle();
|
||||||
groupLimit.setText(getResources().getQuantityString(R.plurals.ContactSelectionListFragment_d_members, members, members));
|
|
||||||
groupLimit.setVisibility(isMulti && !hideCount ? View.VISIBLE : View.GONE);
|
|
||||||
groupLimit.setWarning(selectionWarningLimitExceeded());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -257,8 +304,19 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
return cursorRecyclerViewAdapter.getSelectedContactsCount();
|
return cursorRecyclerViewAdapter.getSelectedContactsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTotalMemberCount() {
|
||||||
|
if (cursorRecyclerViewAdapter == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursorRecyclerViewAdapter.getSelectedContactsCount() + cursorRecyclerViewAdapter.getCurrentContactsCount();
|
||||||
|
}
|
||||||
|
|
||||||
private Set<RecipientId> getCurrentSelection() {
|
private Set<RecipientId> getCurrentSelection() {
|
||||||
List<RecipientId> currentSelection = requireActivity().getIntent().getParcelableArrayListExtra(CURRENT_SELECTION);
|
List<RecipientId> currentSelection = safeArguments().getParcelableArrayList(CURRENT_SELECTION);
|
||||||
|
if (currentSelection == null) {
|
||||||
|
currentSelection = requireActivity().getIntent().getParcelableArrayListExtra(CURRENT_SELECTION);
|
||||||
|
}
|
||||||
|
|
||||||
return currentSelection == null ? Collections.emptySet()
|
return currentSelection == null ? Collections.emptySet()
|
||||||
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
|
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
|
||||||
@@ -294,8 +352,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
concatenateAdapter.addAdapter(footerAdapter);
|
concatenateAdapter.addAdapter(footerAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recyclerView.addItemDecoration(new LetterHeaderDecoration(requireContext(), this::hideLetterHeaders));
|
||||||
recyclerView.setAdapter(concatenateAdapter);
|
recyclerView.setAdapter(concatenateAdapter);
|
||||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
|
|
||||||
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) {
|
||||||
@@ -306,6 +364,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (onContactSelectedListener != null) {
|
||||||
|
onContactSelectedListener.onSelectionChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hideLetterHeaders() {
|
||||||
|
return hasQueryFilter() || shouldDisplayRecents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private View createInviteActionView(@NonNull ListCallback listCallback) {
|
private View createInviteActionView(@NonNull ListCallback listCallback) {
|
||||||
@@ -372,10 +438,15 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
FragmentActivity activity = requireActivity();
|
FragmentActivity activity = requireActivity();
|
||||||
return new ContactsCursorLoader(activity,
|
int displayMode = safeArguments().getInt(DISPLAY_MODE, activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL));
|
||||||
activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL),
|
boolean displayRecents = shouldDisplayRecents();
|
||||||
cursorFilter, activity.getIntent().getBooleanExtra(RECENTS, false));
|
|
||||||
|
if (cursorFactoryProvider != null) {
|
||||||
|
return cursorFactoryProvider.get().create();
|
||||||
|
} else {
|
||||||
|
return new ContactsCursorLoader.Factory(activity, displayMode, cursorFilter, displayRecents).create();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -415,6 +486,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
fastScroller.setVisibility(View.GONE);
|
fastScroller.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldDisplayRecents() {
|
||||||
|
return safeArguments().getBoolean(RECENTS, requireActivity().getIntent().getBooleanExtra(RECENTS, false));
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private void handleContactPermissionGranted() {
|
private void handleContactPermissionGranted() {
|
||||||
final Context context = requireContext();
|
final Context context = requireContext();
|
||||||
@@ -464,14 +539,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 (selectionHardLimitReached()) {
|
if (selectionHardLimitReached()) {
|
||||||
GroupLimitDialog.showHardLimitMessage(requireContext());
|
if (onSelectionLimitReachedListener != null) {
|
||||||
|
onSelectionLimitReachedListener.onHardLimitReached(selectionLimit.getHardLimit());
|
||||||
|
} else {
|
||||||
|
GroupLimitDialog.showHardLimitMessage(requireContext());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,11 +568,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
if (onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null)) {
|
if (onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null)) {
|
||||||
markContactSelected(selected);
|
markContactSelected(selected);
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
markContactSelected(selected);
|
markContactSelected(selected);
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
@@ -507,16 +586,16 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
if (onContactSelectedListener.onBeforeContactSelected(contact.getRecipientId(), contact.getNumber())) {
|
if (onContactSelectedListener.onBeforeContactSelected(contact.getRecipientId(), contact.getNumber())) {
|
||||||
markContactSelected(selectedContact);
|
markContactSelected(selectedContact);
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
markContactSelected(selectedContact);
|
markContactSelected(selectedContact);
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
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());
|
||||||
@@ -542,12 +621,19 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
if (isMulti) {
|
if (isMulti) {
|
||||||
addChipForSelectedContact(selectedContact);
|
addChipForSelectedContact(selectedContact);
|
||||||
}
|
}
|
||||||
|
if (onContactSelectedListener != null) {
|
||||||
|
onContactSelectedListener.onSelectionChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markContactUnselected(@NonNull SelectedContact selectedContact) {
|
private void markContactUnselected(@NonNull SelectedContact selectedContact) {
|
||||||
cursorRecyclerViewAdapter.removeFromSelectedContacts(selectedContact);
|
cursorRecyclerViewAdapter.removeFromSelectedContacts(selectedContact);
|
||||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
removeChipForContact(selectedContact);
|
removeChipForContact(selectedContact);
|
||||||
|
|
||||||
|
if (onContactSelectedListener != null) {
|
||||||
|
onContactSelectedListener.onSelectionChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeChipForContact(@NonNull SelectedContact contact) {
|
private void removeChipForContact(@NonNull SelectedContact contact) {
|
||||||
@@ -558,8 +644,6 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGroupLimit(getChipCount());
|
|
||||||
|
|
||||||
if (getChipCount() == 0) {
|
if (getChipCount() == 0) {
|
||||||
setChipGroupVisibility(ConstraintSet.GONE);
|
setChipGroupVisibility(ConstraintSet.GONE);
|
||||||
}
|
}
|
||||||
@@ -609,9 +693,12 @@ 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());
|
|
||||||
if (selectionWarningLimitReachedExactly()) {
|
if (selectionWarningLimitReachedExactly()) {
|
||||||
GroupLimitDialog.showRecommendedLimitMessage(requireContext());
|
if (onSelectionLimitReachedListener != null) {
|
||||||
|
onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit());
|
||||||
|
} else {
|
||||||
|
GroupLimitDialog.showRecommendedLimitMessage(requireContext());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,6 +720,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setChipGroupVisibility(int visibility) {
|
private void setChipGroupVisibility(int visibility) {
|
||||||
|
if (!safeArguments().getBoolean(DISPLAY_CHIPS, 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();
|
||||||
@@ -641,16 +732,12 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,6 +745,12 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
/** @return True if the contact is allowed to be selected, otherwise false. */
|
/** @return True if the contact is allowed to be selected, otherwise false. */
|
||||||
boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number);
|
boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number);
|
||||||
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
||||||
|
void onSelectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnSelectionLimitReachedListener {
|
||||||
|
void onSuggestedLimitReached(int limit);
|
||||||
|
void onHardLimitReached(int limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ListCallback {
|
public interface ListCallback {
|
||||||
@@ -668,4 +761,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
public interface ScrollCallback {
|
public interface ScrollCallback {
|
||||||
void onBeginScroll();
|
void onBeginScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface AbstractContactsCursorLoaderFactoryProvider {
|
||||||
|
@NonNull AbstractContactsCursorLoader.Factory get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ import android.widget.Button;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import org.signal.core.util.ThreadUtil;
|
||||||
import org.signal.core.util.logging.Log;
|
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;
|
||||||
@@ -26,9 +28,9 @@ 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;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
@@ -45,9 +47,9 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
|||||||
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
|
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String TAG = DeviceActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(DeviceActivity.class);
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
private DeviceAddFragment deviceAddFragment;
|
private DeviceAddFragment deviceAddFragment;
|
||||||
@@ -62,9 +64,14 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
getSupportActionBar().setHomeAsUpIndicator(ContextCompat.getDrawable(this, R.drawable.ic_arrow_left_24));
|
setContentView(R.layout.device_activity);
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
getSupportActionBar().setTitle(R.string.AndroidManifest__linked_devices);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
requireSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
requireSupportActionBar().setTitle(R.string.AndroidManifest__linked_devices);
|
||||||
|
|
||||||
this.deviceAddFragment = new DeviceAddFragment();
|
this.deviceAddFragment = new DeviceAddFragment();
|
||||||
this.deviceListFragment = new DeviceListFragment();
|
this.deviceListFragment = new DeviceListFragment();
|
||||||
this.deviceLinkFragment = new DeviceLinkFragment();
|
this.deviceLinkFragment = new DeviceLinkFragment();
|
||||||
@@ -73,20 +80,10 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
|||||||
this.deviceAddFragment.setScanListener(this);
|
this.deviceAddFragment.setScanListener(this);
|
||||||
|
|
||||||
if (getIntent().getBooleanExtra("add", false)) {
|
if (getIntent().getBooleanExtra("add", false)) {
|
||||||
initFragment(android.R.id.content, deviceAddFragment, dynamicLanguage.getCurrentLocale());
|
initFragment(R.id.fragment_container, deviceAddFragment, dynamicLanguage.getCurrentLocale());
|
||||||
} else {
|
} else {
|
||||||
initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.getCurrentLocale());
|
initFragment(R.id.fragment_container, deviceListFragment, dynamicLanguage.getCurrentLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
overridePendingTransition(R.anim.slide_from_end, R.anim.slide_to_start);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
if (isFinishing()) {
|
|
||||||
overridePendingTransition(R.anim.slide_from_start, R.anim.slide_to_end);
|
|
||||||
}
|
|
||||||
super.onPause();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -98,8 +95,9 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == android.R.id.home) {
|
||||||
case android.R.id.home: finish(); return true;
|
finish();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -113,7 +111,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
|||||||
.withPermanentDenialDialog(getString(R.string.DeviceActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code))
|
.withPermanentDenialDialog(getString(R.string.DeviceActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code))
|
||||||
.onAllGranted(() -> {
|
.onAllGranted(() -> {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(android.R.id.content, deviceAddFragment)
|
.replace(R.id.fragment_container, deviceAddFragment)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
})
|
})
|
||||||
@@ -123,12 +121,12 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onQrDataFound(final String data) {
|
public void onQrDataFound(final String data) {
|
||||||
Util.runOnMain(() -> {
|
ThreadUtil.runOnMain(() -> {
|
||||||
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
||||||
Uri uri = Uri.parse(data);
|
Uri uri = Uri.parse(data);
|
||||||
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
|
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
|
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
|
||||||
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
|
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
|
||||||
|
|
||||||
@@ -138,14 +136,14 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
|||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.addSharedElement(deviceAddFragment.getDevicesImage(), "devices")
|
.addSharedElement(deviceAddFragment.getDevicesImage(), "devices")
|
||||||
.replace(android.R.id.content, deviceLinkFragment)
|
.replace(R.id.fragment_container, deviceLinkFragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_bottom,
|
.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_bottom,
|
||||||
R.anim.slide_from_bottom, R.anim.slide_to_bottom)
|
R.anim.slide_from_bottom, R.anim.slide_to_bottom)
|
||||||
.replace(android.R.id.content, deviceLinkFragment)
|
.replace(R.id.fragment_container, deviceLinkFragment)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ public class DeviceAddFragment extends LoggingFragment {
|
|||||||
this.overlay.setOrientation(LinearLayout.VERTICAL);
|
this.overlay.setOrientation(LinearLayout.VERTICAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
this.container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
this.container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
@TargetApi(21)
|
||||||
@Override
|
@Override
|
||||||
public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
||||||
int oldLeft, int oldTop, int oldRight, int oldBottom)
|
int oldLeft, int oldTop, int oldRight, int oldBottom)
|
||||||
@@ -80,7 +80,7 @@ public class DeviceAddFragment extends LoggingFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfiguration) {
|
public void onConfigurationChanged(@NonNull Configuration newConfiguration) {
|
||||||
super.onConfigurationChanged(newConfiguration);
|
super.onConfigurationChanged(newConfiguration);
|
||||||
|
|
||||||
this.scannerView.onPause();
|
this.scannerView.onPause();
|
||||||
@@ -107,6 +107,4 @@ public class DeviceAddFragment extends LoggingFragment {
|
|||||||
this.scanningThread.setScanListener(scanListener);
|
this.scanningThread.setScanListener(scanListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class DeviceLinkFragment extends Fragment implements View.OnClickListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfiguration) {
|
public void onConfigurationChanged(@NonNull Configuration newConfiguration) {
|
||||||
super.onConfigurationChanged(newConfiguration);
|
super.onConfigurationChanged(newConfiguration);
|
||||||
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
container.setOrientation(LinearLayout.HORIZONTAL);
|
container.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import androidx.fragment.app.ListFragment;
|
|||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.melnykov.fab.FloatingActionButton;
|
import com.melnykov.fab.FloatingActionButton;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
@@ -40,7 +41,7 @@ public class DeviceListFragment extends ListFragment
|
|||||||
ListView.OnItemClickListener, Button.OnClickListener
|
ListView.OnItemClickListener, Button.OnClickListener
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
private static final String TAG = Log.tag(DeviceListFragment.class);
|
||||||
|
|
||||||
private SignalServiceAccountManager accountManager;
|
private SignalServiceAccountManager accountManager;
|
||||||
private Locale locale;
|
private Locale locale;
|
||||||
@@ -52,12 +53,12 @@ public class DeviceListFragment extends ListFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActivity.LOCALE_EXTRA);
|
this.locale = (Locale) requireArguments().getSerializable(PassphraseRequiredActivity.LOCALE_EXTRA);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(@NonNull Context context) {
|
||||||
super.onAttach(activity);
|
super.onAttach(context);
|
||||||
this.accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
this.accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,42 +122,22 @@ public class DeviceListFragment extends ListFragment
|
|||||||
final String deviceName = ((DeviceListItem)view).getDeviceName();
|
final String deviceName = ((DeviceListItem)view).getDeviceName();
|
||||||
final long deviceId = ((DeviceListItem)view).getDeviceId();
|
final long deviceId = ((DeviceListItem)view).getDeviceId();
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(requireActivity());
|
||||||
builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
|
builder.setTitle(getString(R.string.DeviceListActivity_unlink_s, deviceName));
|
||||||
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
|
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> handleDisconnectDevice(deviceId));
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
handleDisconnectDevice(deviceId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleLoaderFailed() {
|
private void handleLoaderFailed() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(requireActivity());
|
||||||
builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
|
builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
|
||||||
builder.setPositiveButton(R.string.DeviceListActivity_try_again,
|
builder.setPositiveButton(R.string.DeviceListActivity_try_again,
|
||||||
new DialogInterface.OnClickListener() {
|
(dialog, which) -> getLoaderManager().restartLoader(0, null, DeviceListFragment.this));
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> requireActivity().onBackPressed());
|
||||||
@Override
|
builder.setOnCancelListener(dialog -> requireActivity().onBackPressed());
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
DeviceListFragment.this.getActivity().onBackPressed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
|
||||||
@Override
|
|
||||||
public void onCancel(DialogInterface dialog) {
|
|
||||||
DeviceListFragment.this.getActivity().onBackPressed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import android.view.Window;
|
|||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = DeviceProvisioningActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(DeviceProvisioningActivity.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreCreate() {
|
protected void onPreCreate() {
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import cn.carbswang.android.numberpickerview.library.NumberPickerView;
|
|
||||||
|
|
||||||
public class ExpirationDialog extends AlertDialog {
|
|
||||||
|
|
||||||
protected ExpirationDialog(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ExpirationDialog(Context context, int theme) {
|
|
||||||
super(context, theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ExpirationDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
|
|
||||||
super(context, cancelable, cancelListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void show(final Context context,
|
|
||||||
final int currentExpiration,
|
|
||||||
final @NonNull OnClickListener listener)
|
|
||||||
{
|
|
||||||
final View view = createNumberPickerView(context, currentExpiration);
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
|
||||||
builder.setTitle(context.getString(R.string.ExpirationDialog_disappearing_messages));
|
|
||||||
builder.setView(view);
|
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
||||||
int selected = ((NumberPickerView)view.findViewById(R.id.expiration_number_picker)).getValue();
|
|
||||||
listener.onClick(getExpirationTimes(context, currentExpiration)[selected]);
|
|
||||||
});
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
builder.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static View createNumberPickerView(final Context context, final int currentExpiration) {
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
|
||||||
final View view = inflater.inflate(R.layout.expiration_dialog, null);
|
|
||||||
final NumberPickerView numberPickerView = view.findViewById(R.id.expiration_number_picker);
|
|
||||||
final TextView textView = view.findViewById(R.id.expiration_details);
|
|
||||||
final int[] expirationTimes = getExpirationTimes(context, currentExpiration);
|
|
||||||
final String[] expirationDisplayValues = new String[expirationTimes.length];
|
|
||||||
|
|
||||||
int selectedIndex = expirationTimes.length - 1;
|
|
||||||
|
|
||||||
for (int i=0;i<expirationTimes.length;i++) {
|
|
||||||
expirationDisplayValues[i] = ExpirationUtil.getExpirationDisplayValue(context, expirationTimes[i]);
|
|
||||||
|
|
||||||
if ((currentExpiration >= expirationTimes[i]) &&
|
|
||||||
(i == expirationTimes.length -1 || currentExpiration < expirationTimes[i+1])) {
|
|
||||||
selectedIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
numberPickerView.setDisplayedValues(expirationDisplayValues);
|
|
||||||
numberPickerView.setMinValue(0);
|
|
||||||
numberPickerView.setMaxValue(expirationTimes.length-1);
|
|
||||||
|
|
||||||
NumberPickerView.OnValueChangeListener listener = (picker, oldVal, newVal) -> {
|
|
||||||
if (newVal == 0) {
|
|
||||||
textView.setText(R.string.ExpirationDialog_your_messages_will_not_expire);
|
|
||||||
} else {
|
|
||||||
textView.setText(context.getString(R.string.ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen, picker.getDisplayedValues()[newVal]));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
numberPickerView.setOnValueChangedListener(listener);
|
|
||||||
numberPickerView.setValue(selectedIndex);
|
|
||||||
listener.onValueChange(numberPickerView, selectedIndex, selectedIndex);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int[] getExpirationTimes(Context context, int currentExpiration) {
|
|
||||||
int[] expirationTimes = context.getResources().getIntArray(R.array.expiration_times);
|
|
||||||
int location = Arrays.binarySearch(expirationTimes, currentExpiration);
|
|
||||||
if (location < 0) {
|
|
||||||
int[] temp = Arrays.copyOf(expirationTimes, expirationTimes.length + 1);
|
|
||||||
temp[temp.length - 1] = currentExpiration;
|
|
||||||
Arrays.sort(temp);
|
|
||||||
expirationTimes = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return expirationTimes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnClickListener {
|
|
||||||
public void onClick(int expirationTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -22,8 +22,8 @@ import androidx.appcompat.widget.Toolbar;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
|
import org.thoughtcrime.securesms.components.ContactFilterView.OnFilterChangedListener;
|
||||||
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;
|
||||||
@@ -40,6 +40,7 @@ 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.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@@ -97,7 +98,8 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
View shareButton = findViewById(R.id.share_button);
|
View shareButton = findViewById(R.id.share_button);
|
||||||
Button smsButton = findViewById(R.id.sms_button);
|
Button smsButton = findViewById(R.id.sms_button);
|
||||||
Button smsCancelButton = findViewById(R.id.cancel_sms_button);
|
Button smsCancelButton = findViewById(R.id.cancel_sms_button);
|
||||||
ContactFilterToolbar contactFilter = findViewById(R.id.contact_filter);
|
Toolbar smsToolbar = findViewById(R.id.sms_send_frame_toolbar);
|
||||||
|
ContactFilterView contactFilter = findViewById(R.id.contact_filter_edit_text);
|
||||||
|
|
||||||
inviteText = findViewById(R.id.invite_text);
|
inviteText = findViewById(R.id.invite_text);
|
||||||
smsSendFrame = findViewById(R.id.sms_send_frame);
|
smsSendFrame = findViewById(R.id.sms_send_frame);
|
||||||
@@ -105,13 +107,20 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
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)));
|
||||||
|
inviteText.addTextChangedListener(new AfterTextChanged(editable -> {
|
||||||
|
boolean isEnabled = editable.length() > 0;
|
||||||
|
smsButton.setEnabled(isEnabled);
|
||||||
|
shareButton.setEnabled(isEnabled);
|
||||||
|
smsButton.animate().alpha(isEnabled ? 1f : 0.5f);
|
||||||
|
shareButton.animate().alpha(isEnabled ? 1f : 0.5f);
|
||||||
|
}));
|
||||||
|
|
||||||
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||||
|
|
||||||
contactsFragment.setOnContactSelectedListener(this);
|
|
||||||
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);
|
smsToolbar.setNavigationIcon(R.drawable.ic_search_conversation_24);
|
||||||
|
|
||||||
if (Util.isDefaultSmsProvider(this)) {
|
if (Util.isDefaultSmsProvider(this)) {
|
||||||
shareButton.setOnClickListener(new ShareClickListener());
|
shareButton.setOnClickListener(new ShareClickListener());
|
||||||
@@ -140,6 +149,10 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
|||||||
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectionChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
private void sendSmsInvites() {
|
private void sendSmsInvites() {
|
||||||
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
|
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
|
||||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
@@ -15,6 +16,12 @@ public abstract class LoggingFragment extends Fragment {
|
|||||||
|
|
||||||
private static final String TAG = Log.tag(LoggingFragment.class);
|
private static final String TAG = Log.tag(LoggingFragment.class);
|
||||||
|
|
||||||
|
public LoggingFragment() { }
|
||||||
|
|
||||||
|
public LoggingFragment(@LayoutRes int contentLayoutId) {
|
||||||
|
super(contentLayoutId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
logEvent("onCreate()");
|
logEvent("onCreate()");
|
||||||
|
|||||||
@@ -9,19 +9,25 @@ import android.os.Bundle;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
|
||||||
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
|
||||||
|
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLockedDialog;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.util.AppStartup;
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
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 implements VoiceNoteMediaControllerOwner {
|
||||||
|
|
||||||
public static final int RESULT_CONFIG_CHANGED = Activity.RESULT_FIRST_USER + 901;
|
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);
|
||||||
|
|
||||||
|
private VoiceNoteMediaController mediaController;
|
||||||
|
|
||||||
public static @NonNull Intent clearTop(@NonNull Context context) {
|
public static @NonNull Intent clearTop(@NonNull Context context) {
|
||||||
Intent intent = new Intent(context, MainActivity.class);
|
Intent intent = new Intent(context, MainActivity.class);
|
||||||
|
|
||||||
@@ -38,9 +44,11 @@ public class MainActivity extends PassphraseRequiredActivity {
|
|||||||
super.onCreate(savedInstanceState, ready);
|
super.onCreate(savedInstanceState, ready);
|
||||||
setContentView(R.layout.main_activity);
|
setContentView(R.layout.main_activity);
|
||||||
|
|
||||||
|
mediaController = new VoiceNoteMediaController(this);
|
||||||
navigator.onCreate(savedInstanceState);
|
navigator.onCreate(savedInstanceState);
|
||||||
|
|
||||||
handleGroupLinkInIntent(getIntent());
|
handleGroupLinkInIntent(getIntent());
|
||||||
|
handleProxyInIntent(getIntent());
|
||||||
|
|
||||||
CachedInflater.from(this).clear();
|
CachedInflater.from(this).clear();
|
||||||
}
|
}
|
||||||
@@ -56,6 +64,7 @@ public class MainActivity extends PassphraseRequiredActivity {
|
|||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
handleGroupLinkInIntent(intent);
|
handleGroupLinkInIntent(intent);
|
||||||
|
handleProxyInIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -68,6 +77,9 @@ public class MainActivity extends PassphraseRequiredActivity {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
dynamicTheme.onResume(this);
|
dynamicTheme.onResume(this);
|
||||||
|
if (SignalStore.misc().isOldDeviceTransferLocked()) {
|
||||||
|
OldDeviceTransferLockedDialog.show(getSupportFragmentManager());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,4 +107,16 @@ public class MainActivity extends PassphraseRequiredActivity {
|
|||||||
CommunicationActions.handlePotentialGroupLinkUrl(this, data.toString());
|
CommunicationActions.handlePotentialGroupLinkUrl(this, data.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleProxyInIntent(Intent intent) {
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
CommunicationActions.handlePotentialProxyLinkUrl(this, data.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull VoiceNoteMediaController getVoiceNoteMediaController() {
|
||||||
|
return mediaController;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ 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.components.settings.DSLSettingsActivity;
|
||||||
|
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
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;
|
||||||
@@ -69,8 +71,7 @@ public class MainNavigator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void goToAppSettings() {
|
public void goToAppSettings() {
|
||||||
Intent intent = new Intent(activity, ApplicationPreferencesActivity.class);
|
activity.startActivityForResult(AppSettingsActivity.home(activity), REQUEST_CONFIG_CHANGES);
|
||||||
activity.startActivityForResult(intent, REQUEST_CONFIG_CHANGES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void goToArchiveList() {
|
public void goToArchiveList() {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
MediaPreviewFragment.Events
|
MediaPreviewFragment.Events
|
||||||
{
|
{
|
||||||
|
|
||||||
private final static String TAG = MediaPreviewActivity.class.getSimpleName();
|
private final static String TAG = Log.tag(MediaPreviewActivity.class);
|
||||||
|
|
||||||
private static final int NOT_IN_A_THREAD = -2;
|
private static final int NOT_IN_A_THREAD = -2;
|
||||||
|
|
||||||
@@ -103,6 +103,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
public static final String HIDE_ALL_MEDIA_EXTRA = "came_from_all_media";
|
public static final String HIDE_ALL_MEDIA_EXTRA = "came_from_all_media";
|
||||||
public static final String SHOW_THREAD_EXTRA = "show_thread";
|
public static final String SHOW_THREAD_EXTRA = "show_thread";
|
||||||
public static final String SORTING_EXTRA = "sorting";
|
public static final String SORTING_EXTRA = "sorting";
|
||||||
|
public static final String IS_VIDEO_GIF = "is_video_gif";
|
||||||
|
|
||||||
private ViewPager mediaPager;
|
private ViewPager mediaPager;
|
||||||
private View detailsContainer;
|
private View detailsContainer;
|
||||||
@@ -115,6 +116,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
private String initialMediaType;
|
private String initialMediaType;
|
||||||
private long initialMediaSize;
|
private long initialMediaSize;
|
||||||
private String initialCaption;
|
private String initialCaption;
|
||||||
|
private boolean initialMediaIsVideoGif;
|
||||||
private boolean leftIsRecent;
|
private boolean leftIsRecent;
|
||||||
private MediaPreviewViewModel viewModel;
|
private MediaPreviewViewModel viewModel;
|
||||||
private ViewPagerListener viewPagerListener;
|
private ViewPagerListener viewPagerListener;
|
||||||
@@ -139,6 +141,7 @@ 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.putExtra(MediaPreviewActivity.IS_VIDEO_GIF, attachment.isVideoGif());
|
||||||
intent.setDataAndType(attachment.getUri(), mediaRecord.getContentType());
|
intent.setDataAndType(attachment.getUri(), mediaRecord.getContentType());
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
@@ -296,12 +299,13 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
showThread = intent.getBooleanExtra(SHOW_THREAD_EXTRA, false);
|
showThread = intent.getBooleanExtra(SHOW_THREAD_EXTRA, false);
|
||||||
sorting = MediaDatabase.Sorting.values()[intent.getIntExtra(SORTING_EXTRA, 0)];
|
sorting = MediaDatabase.Sorting.values()[intent.getIntExtra(SORTING_EXTRA, 0)];
|
||||||
|
|
||||||
initialMediaUri = intent.getData();
|
initialMediaUri = intent.getData();
|
||||||
initialMediaType = intent.getType();
|
initialMediaType = intent.getType();
|
||||||
initialMediaSize = intent.getLongExtra(SIZE_EXTRA, 0);
|
initialMediaSize = intent.getLongExtra(SIZE_EXTRA, 0);
|
||||||
initialCaption = intent.getStringExtra(CAPTION_EXTRA);
|
initialCaption = intent.getStringExtra(CAPTION_EXTRA);
|
||||||
leftIsRecent = intent.getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
|
leftIsRecent = intent.getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
|
||||||
restartItem = -1;
|
initialMediaIsVideoGif = intent.getBooleanExtra(IS_VIDEO_GIF, false);
|
||||||
|
restartItem = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeObservers() {
|
private void initializeObservers() {
|
||||||
@@ -354,7 +358,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
if (isMediaInDb()) {
|
if (isMediaInDb()) {
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
||||||
} else {
|
} else {
|
||||||
mediaPager.setAdapter(new SingleItemPagerAdapter(getSupportFragmentManager(), initialMediaUri, initialMediaType, initialMediaSize));
|
mediaPager.setAdapter(new SingleItemPagerAdapter(getSupportFragmentManager(), initialMediaUri, initialMediaType, initialMediaSize, initialMediaIsVideoGif));
|
||||||
|
|
||||||
if (initialCaption != null) {
|
if (initialCaption != null) {
|
||||||
detailsContainer.setVisibility(View.VISIBLE);
|
detailsContainer.setVisibility(View.VISIBLE);
|
||||||
@@ -632,21 +636,24 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
|
|
||||||
private static class SingleItemPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
|
private static class SingleItemPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
|
||||||
|
|
||||||
private final Uri uri;
|
private final Uri uri;
|
||||||
private final String mediaType;
|
private final String mediaType;
|
||||||
private final long size;
|
private final long size;
|
||||||
|
private final boolean isVideoGif;
|
||||||
|
|
||||||
private MediaPreviewFragment mediaPreviewFragment;
|
private MediaPreviewFragment mediaPreviewFragment;
|
||||||
|
|
||||||
SingleItemPagerAdapter(@NonNull FragmentManager fragmentManager,
|
SingleItemPagerAdapter(@NonNull FragmentManager fragmentManager,
|
||||||
@NonNull Uri uri,
|
@NonNull Uri uri,
|
||||||
@NonNull String mediaType,
|
@NonNull String mediaType,
|
||||||
long size)
|
long size,
|
||||||
|
boolean isVideoGif)
|
||||||
{
|
{
|
||||||
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.mediaType = mediaType;
|
this.mediaType = mediaType;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
|
this.isVideoGif = isVideoGif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -657,7 +664,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
mediaPreviewFragment = MediaPreviewFragment.newInstance(uri, mediaType, size, true);
|
mediaPreviewFragment = MediaPreviewFragment.newInstance(uri, mediaType, size, true, isVideoGif);
|
||||||
return mediaPreviewFragment;
|
return mediaPreviewFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class MuteDialog extends AlertDialog {
|
public class MuteDialog extends AlertDialog {
|
||||||
@@ -29,7 +31,7 @@ public class MuteDialog extends AlertDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void show(final Context context, final @NonNull MuteSelectionListener listener, @Nullable Runnable cancelListener) {
|
public static void show(final Context context, final @NonNull MuteSelectionListener listener, @Nullable Runnable cancelListener) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
|
||||||
builder.setTitle(R.string.MuteDialog_mute_notifications);
|
builder.setTitle(R.string.MuteDialog_mute_notifications);
|
||||||
builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
|
builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -38,10 +40,10 @@ public class MuteDialog extends AlertDialog {
|
|||||||
|
|
||||||
switch (which) {
|
switch (which) {
|
||||||
case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
||||||
case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2); break;
|
case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(8); break;
|
||||||
case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
|
case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
|
||||||
case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
|
case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
|
||||||
case 4: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365); break;
|
case 4: muteUntil = Long.MAX_VALUE; break;
|
||||||
default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,13 +49,14 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
{
|
{
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = NewConversationActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(NewConversationActivity.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
super.onCreate(bundle, ready);
|
super.onCreate(bundle, ready);
|
||||||
assert getSupportActionBar() != null;
|
assert getSupportActionBar() != null;
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setTitle(R.string.NewConversationActivity__new_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -96,6 +97,10 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectionChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
private void launch(Recipient recipient) {
|
private void launch(Recipient recipient) {
|
||||||
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient.getId());
|
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient.getId());
|
||||||
Intent intent = ConversationIntents.createBuilder(this, recipient.getId(), existingThread)
|
Intent intent = ConversationIntents.createBuilder(this, recipient.getId(), existingThread)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
|||||||
*/
|
*/
|
||||||
public abstract class PassphraseActivity extends BaseActivity {
|
public abstract class PassphraseActivity extends BaseActivity {
|
||||||
|
|
||||||
private static final String TAG = PassphraseActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(PassphraseActivity.class);
|
||||||
|
|
||||||
private KeyCachingService keyCachingService;
|
private KeyCachingService keyCachingService;
|
||||||
private MasterSecret masterSecret;
|
private MasterSecret masterSecret;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -46,9 +45,11 @@ import android.widget.ImageButton;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
import androidx.biometric.BiometricManager;
|
||||||
import androidx.core.os.CancellationSignal;
|
import androidx.biometric.BiometricManager.Authenticators;
|
||||||
|
import androidx.biometric.BiometricPrompt;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||||
@@ -57,8 +58,10 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,7 +71,12 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||||||
*/
|
*/
|
||||||
public class PassphrasePromptActivity extends PassphraseActivity {
|
public class PassphrasePromptActivity extends PassphraseActivity {
|
||||||
|
|
||||||
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(PassphrasePromptActivity.class);
|
||||||
|
private static final int BIOMETRIC_AUTHENTICATORS = Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK;
|
||||||
|
private static final int ALLOWED_AUTHENTICATORS = BIOMETRIC_AUTHENTICATORS | Authenticators.DEVICE_CREDENTIAL;
|
||||||
|
private static final short AUTHENTICATE_REQUEST_CODE = 1007;
|
||||||
|
private static final String BUNDLE_ALREADY_SHOWN = "bundle_already_shown";
|
||||||
|
public static final String FROM_FOREGROUND = "from_foreground";
|
||||||
|
|
||||||
private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme();
|
private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme();
|
||||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
@@ -82,12 +90,13 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
private ImageButton hideButton;
|
private ImageButton hideButton;
|
||||||
private AnimatingToggle visibilityToggle;
|
private AnimatingToggle visibilityToggle;
|
||||||
|
|
||||||
private FingerprintManagerCompat fingerprintManager;
|
private BiometricManager biometricManager;
|
||||||
private CancellationSignal fingerprintCancellationSignal;
|
private BiometricPrompt biometricPrompt;
|
||||||
private FingerprintListener fingerprintListener;
|
private BiometricPrompt.PromptInfo biometricPromptInfo;
|
||||||
|
|
||||||
private boolean authenticated;
|
private boolean authenticated;
|
||||||
private boolean failure;
|
private boolean hadFailure;
|
||||||
|
private boolean alreadyShown;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -100,6 +109,15 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
|
|
||||||
setContentView(R.layout.prompt_passphrase_activity);
|
setContentView(R.layout.prompt_passphrase_activity);
|
||||||
initializeResources();
|
initializeResources();
|
||||||
|
|
||||||
|
alreadyShown = (savedInstanceState != null && savedInstanceState.getBoolean(BUNDLE_ALREADY_SHOWN)) ||
|
||||||
|
getIntent().getBooleanExtra(FROM_FOREGROUND, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putBoolean(BUNDLE_ALREADY_SHOWN, alreadyShown);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -110,20 +128,12 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
|
|
||||||
setLockTypeVisibility();
|
setLockTypeVisibility();
|
||||||
|
|
||||||
if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !failure) {
|
if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !hadFailure) {
|
||||||
resumeScreenLock();
|
resumeScreenLock(!alreadyShown);
|
||||||
|
alreadyShown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
failure = false;
|
hadFailure = false;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
|
|
||||||
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
|
||||||
pauseScreenLock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -137,7 +147,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
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.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
return true;
|
return true;
|
||||||
@@ -146,23 +156,28 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
@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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressLint("MissingSuperCall") // no fragments to dispatch to
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
public void onActivityResult(int requestCode, int resultcode, Intent data) {
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
if (requestCode != 1) return;
|
|
||||||
|
|
||||||
if (resultcode == RESULT_OK) {
|
if (requestCode != AUTHENTICATE_REQUEST_CODE) return;
|
||||||
|
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
handleAuthenticated();
|
handleAuthenticated();
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Authentication failed");
|
Log.w(TAG, "Authentication failed");
|
||||||
failure = true;
|
hadFailure = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,16 +228,20 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
ImageButton okButton = findViewById(R.id.ok_button);
|
ImageButton okButton = findViewById(R.id.ok_button);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
|
||||||
showButton = findViewById(R.id.passphrase_visibility);
|
showButton = findViewById(R.id.passphrase_visibility);
|
||||||
hideButton = findViewById(R.id.passphrase_visibility_off);
|
hideButton = findViewById(R.id.passphrase_visibility_off);
|
||||||
visibilityToggle = findViewById(R.id.button_toggle);
|
visibilityToggle = findViewById(R.id.button_toggle);
|
||||||
passphraseText = findViewById(R.id.passphrase_edit);
|
passphraseText = findViewById(R.id.passphrase_edit);
|
||||||
passphraseAuthContainer = findViewById(R.id.password_auth_container);
|
passphraseAuthContainer = findViewById(R.id.password_auth_container);
|
||||||
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
|
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
|
||||||
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
||||||
fingerprintManager = FingerprintManagerCompat.from(this);
|
biometricManager = BiometricManager.from(this);
|
||||||
fingerprintCancellationSignal = new CancellationSignal();
|
biometricPrompt = new BiometricPrompt(this, new BiometricAuthenticationListener());
|
||||||
fingerprintListener = new FingerprintListener();
|
biometricPromptInfo = new BiometricPrompt.PromptInfo
|
||||||
|
.Builder()
|
||||||
|
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
|
||||||
|
.setTitle(getString(R.string.PassphrasePromptActivity_unlock_signal))
|
||||||
|
.build();
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setTitle("");
|
getSupportActionBar().setTitle("");
|
||||||
@@ -242,20 +261,15 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
|
||||||
|
|
||||||
lockScreenButton.setOnClickListener(v -> resumeScreenLock());
|
lockScreenButton.setOnClickListener(v -> resumeScreenLock(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLockTypeVisibility() {
|
private void setLockTypeVisibility() {
|
||||||
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||||
passphraseAuthContainer.setVisibility(View.GONE);
|
passphraseAuthContainer.setVisibility(View.GONE);
|
||||||
|
fingerprintPrompt.setVisibility(biometricManager.canAuthenticate(BIOMETRIC_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS ? View.VISIBLE
|
||||||
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
: View.GONE);
|
||||||
fingerprintPrompt.setVisibility(View.VISIBLE);
|
lockScreenButton.setVisibility(View.VISIBLE);
|
||||||
lockScreenButton.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
fingerprintPrompt.setVisibility(View.GONE);
|
|
||||||
lockScreenButton.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
passphraseAuthContainer.setVisibility(View.VISIBLE);
|
passphraseAuthContainer.setVisibility(View.VISIBLE);
|
||||||
fingerprintPrompt.setVisibility(View.GONE);
|
fingerprintPrompt.setVisibility(View.GONE);
|
||||||
@@ -263,7 +277,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resumeScreenLock() {
|
private void resumeScreenLock(boolean force) {
|
||||||
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
||||||
|
|
||||||
assert keyguardManager != null;
|
assert keyguardManager != null;
|
||||||
@@ -274,24 +288,36 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
if (Build.VERSION.SDK_INT != 29 && biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||||
Log.i(TAG, "Listening for fingerprints...");
|
if (force) {
|
||||||
fingerprintCancellationSignal = new CancellationSignal();
|
Log.i(TAG, "Listening for biometric authentication...");
|
||||||
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
|
biometricPrompt.authenticate(biometricPromptInfo);
|
||||||
} else if (Build.VERSION.SDK_INT >= 21){
|
} else {
|
||||||
Log.i(TAG, "firing intent...");
|
Log.i(TAG, "Skipping show system biometric dialog unless forced");
|
||||||
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.PassphrasePromptActivity_unlock_signal), "");
|
}
|
||||||
startActivityForResult(intent, 1);
|
} else if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
if (force) {
|
||||||
|
Log.i(TAG, "firing intent...");
|
||||||
|
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.PassphrasePromptActivity_unlock_signal), "");
|
||||||
|
startActivityForResult(intent, AUTHENTICATE_REQUEST_CODE);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Skipping firing intent unless forced");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Not compatible...");
|
Log.w(TAG, "Not compatible...");
|
||||||
handleAuthenticated();
|
handleAuthenticated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pauseScreenLock() {
|
private void sendEmailToSupport() {
|
||||||
if (fingerprintCancellationSignal != null) {
|
String body = SupportEmailUtil.generateSupportEmailBody(this,
|
||||||
fingerprintCancellationSignal.cancel();
|
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 {
|
||||||
@@ -342,15 +368,19 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
System.gc();
|
System.gc();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FingerprintListener extends FingerprintManagerCompat.AuthenticationCallback {
|
private class BiometricAuthenticationListener extends BiometricPrompt.AuthenticationCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
public void onAuthenticationError(int errorCode, @NonNull CharSequence errorString) {
|
||||||
Log.w(TAG, "Authentication error: " + errMsgId + " " + errString);
|
Log.w(TAG, "Authentication error: " + errorCode);
|
||||||
onAuthenticationFailed();
|
hadFailure = true;
|
||||||
|
|
||||||
|
if (errorCode != BiometricPrompt.ERROR_CANCELED && errorCode != BiometricPrompt.ERROR_USER_CANCELED) {
|
||||||
|
onAuthenticationFailed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
|
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||||
Log.i(TAG, "onAuthenticationSucceeded");
|
Log.i(TAG, "onAuthenticationSucceeded");
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
|
||||||
@@ -367,8 +397,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationFailed() {
|
public void onAuthenticationFailed() {
|
||||||
Log.w(TAG, "onAuthenticatoinFailed()");
|
Log.w(TAG, "onAuthenticationFailed()");
|
||||||
FingerprintManagerCompat.AuthenticationCallback callback = this;
|
|
||||||
|
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
||||||
@@ -392,6 +421,5 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
|
|
||||||
fingerprintPrompt.startAnimation(shake);
|
fingerprintPrompt.startAnimation(shake);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.core.util.tracing.Tracer;
|
import org.signal.core.util.tracing.Tracer;
|
||||||
|
import org.signal.devicetransfer.TransferStatus;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||||
@@ -32,7 +35,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public abstract class PassphraseRequiredActivity extends BaseActivity implements MasterSecretListener {
|
public abstract class PassphraseRequiredActivity extends BaseActivity implements MasterSecretListener {
|
||||||
private static final String TAG = PassphraseRequiredActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(PassphraseRequiredActivity.class);
|
||||||
|
|
||||||
public static final String LOCALE_EXTRA = "locale_extra";
|
public static final String LOCALE_EXTRA = "locale_extra";
|
||||||
public static final String NEXT_INTENT_EXTRA = "next_intent";
|
public static final String NEXT_INTENT_EXTRA = "next_intent";
|
||||||
@@ -45,6 +48,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
private static final int STATE_ENTER_SIGNAL_PIN = 5;
|
private static final int STATE_ENTER_SIGNAL_PIN = 5;
|
||||||
private static final int STATE_CREATE_PROFILE_NAME = 6;
|
private static final int STATE_CREATE_PROFILE_NAME = 6;
|
||||||
private static final int STATE_CREATE_SIGNAL_PIN = 7;
|
private static final int STATE_CREATE_SIGNAL_PIN = 7;
|
||||||
|
private static final int STATE_TRANSFER_ONGOING = 8;
|
||||||
|
private static final int STATE_TRANSFER_LOCKED = 9;
|
||||||
|
|
||||||
private SignalServiceNetworkAccess networkAccess;
|
private SignalServiceNetworkAccess networkAccess;
|
||||||
private BroadcastReceiver clearKeyReceiver;
|
private BroadcastReceiver clearKeyReceiver;
|
||||||
@@ -78,7 +83,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if (networkAccess.isCensored(this)) {
|
if (networkAccess.isCensored(this)) {
|
||||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
|
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +96,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void onMasterSecretCleared() {
|
public void onMasterSecretCleared() {
|
||||||
Log.d(TAG, "onMasterSecretCleared()");
|
Log.d(TAG, "onMasterSecretCleared()");
|
||||||
if (ApplicationContext.getInstance(this).isAppVisible()) routeApplicationState(true);
|
if (ApplicationDependencies.getAppForegroundObserver().isForegrounded()) routeApplicationState(true);
|
||||||
else finish();
|
else finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T extends Fragment> T initFragment(@IdRes int target,
|
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||||
@@ -146,6 +151,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
case STATE_ENTER_SIGNAL_PIN: return getEnterSignalPinIntent();
|
case STATE_ENTER_SIGNAL_PIN: return getEnterSignalPinIntent();
|
||||||
case STATE_CREATE_SIGNAL_PIN: return getCreateSignalPinIntent();
|
case STATE_CREATE_SIGNAL_PIN: return getCreateSignalPinIntent();
|
||||||
case STATE_CREATE_PROFILE_NAME: return getCreateProfileNameIntent();
|
case STATE_CREATE_PROFILE_NAME: return getCreateProfileNameIntent();
|
||||||
|
case STATE_TRANSFER_ONGOING: return getOldDeviceTransferIntent();
|
||||||
|
case STATE_TRANSFER_LOCKED: return getOldDeviceTransferLockedIntent();
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,12 +166,16 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
return STATE_UI_BLOCKING_UPGRADE;
|
return STATE_UI_BLOCKING_UPGRADE;
|
||||||
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
|
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
|
||||||
return STATE_WELCOME_PUSH_SCREEN;
|
return STATE_WELCOME_PUSH_SCREEN;
|
||||||
} else if (SignalStore.storageServiceValues().needsAccountRestore()) {
|
} else if (SignalStore.storageService().needsAccountRestore()) {
|
||||||
return STATE_ENTER_SIGNAL_PIN;
|
return STATE_ENTER_SIGNAL_PIN;
|
||||||
} else if (userMustSetProfileName()) {
|
} else if (userMustSetProfileName()) {
|
||||||
return STATE_CREATE_PROFILE_NAME;
|
return STATE_CREATE_PROFILE_NAME;
|
||||||
} else if (userMustCreateSignalPin()) {
|
} else if (userMustCreateSignalPin()) {
|
||||||
return STATE_CREATE_SIGNAL_PIN;
|
return STATE_CREATE_SIGNAL_PIN;
|
||||||
|
} else if (EventBus.getDefault().getStickyEvent(TransferStatus.class) != null && getClass() != OldDeviceTransferActivity.class) {
|
||||||
|
return STATE_TRANSFER_ONGOING;
|
||||||
|
} else if (SignalStore.misc().isOldDeviceTransferLocked()) {
|
||||||
|
return STATE_TRANSFER_LOCKED;
|
||||||
} else {
|
} else {
|
||||||
return STATE_NORMAL;
|
return STATE_NORMAL;
|
||||||
}
|
}
|
||||||
@@ -183,7 +194,9 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Intent getPromptPassphraseIntent() {
|
private Intent getPromptPassphraseIntent() {
|
||||||
return getRoutedIntent(PassphrasePromptActivity.class, getIntent());
|
Intent intent = getRoutedIntent(PassphrasePromptActivity.class, getIntent());
|
||||||
|
intent.putExtra(PassphrasePromptActivity.FROM_FOREGROUND, ApplicationDependencies.getAppForegroundObserver().isForegrounded());
|
||||||
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getUiBlockingUpgradeIntent() {
|
private Intent getUiBlockingUpgradeIntent() {
|
||||||
@@ -194,7 +207,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Intent getPushRegistrationIntent() {
|
private Intent getPushRegistrationIntent() {
|
||||||
return RegistrationNavigationActivity.newIntentForNewRegistration(this);
|
return RegistrationNavigationActivity.newIntentForNewRegistration(this, getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getEnterSignalPinIntent() {
|
private Intent getEnterSignalPinIntent() {
|
||||||
@@ -217,6 +230,19 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
return getRoutedIntent(EditProfileActivity.class, getIntent());
|
return getRoutedIntent(EditProfileActivity.class, getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Intent getOldDeviceTransferIntent() {
|
||||||
|
Intent intent = new Intent(this, OldDeviceTransferActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable Intent getOldDeviceTransferLockedIntent() {
|
||||||
|
if (getClass() == MainActivity.class) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return MainActivity.clearTop(this);
|
||||||
|
}
|
||||||
|
|
||||||
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
|
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
|
||||||
final Intent intent = new Intent(this, destination);
|
final Intent intent = new Intent(this, destination);
|
||||||
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
|
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
|
||||||
@@ -232,8 +258,10 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
this.clearKeyReceiver = new BroadcastReceiver() {
|
this.clearKeyReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
Log.i(TAG, "onReceive() for clear key event");
|
Log.i(TAG, "onReceive() for clear key event. PasswordDisabled: " + TextSecurePreferences.isPasswordDisabled(context) + ", ScreenLock: " + TextSecurePreferences.isScreenLockEnabled(context));
|
||||||
onMasterSecretCleared();
|
if (TextSecurePreferences.isScreenLockEnabled(context) || !TextSecurePreferences.isPasswordDisabled(context)) {
|
||||||
|
onMasterSecretCleared();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.os.Bundle;
|
|||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ public class PushContactSelectionActivity extends ContactSelectionActivity {
|
|||||||
public static final String KEY_SELECTED_RECIPIENTS = "recipients";
|
public static final String KEY_SELECTED_RECIPIENTS = "recipients";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final static String TAG = PushContactSelectionActivity.class.getSimpleName();
|
private final static String TAG = Log.tag(PushContactSelectionActivity.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle icicle, boolean ready) {
|
protected void onCreate(Bundle icicle, boolean ready) {
|
||||||
@@ -64,4 +65,8 @@ public class PushContactSelectionActivity extends ContactSelectionActivity {
|
|||||||
setResult(RESULT_OK, resultIntent);
|
setResult(RESULT_OK, resultIntent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectionChanged() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device hardware capability lists.
|
||||||
|
* <p>
|
||||||
|
* Moved outside of ApplicationContext as the indirection was important for API19 support with desugaring: https://issuetracker.google.com/issues/183419297
|
||||||
|
*/
|
||||||
|
final class RtcDeviceLists {
|
||||||
|
|
||||||
|
private RtcDeviceLists() {}
|
||||||
|
|
||||||
|
static Set<String> hardwareAECBlockList() {
|
||||||
|
return new HashSet<String>() {{
|
||||||
|
add("Pixel");
|
||||||
|
add("Pixel XL");
|
||||||
|
add("Moto G5");
|
||||||
|
add("Moto G (5S) Plus");
|
||||||
|
add("Moto G4");
|
||||||
|
add("TA-1053");
|
||||||
|
add("Mi A1");
|
||||||
|
add("Mi A2");
|
||||||
|
add("E5823"); // Sony z5 compact
|
||||||
|
add("Redmi Note 5");
|
||||||
|
add("FP2"); // Fairphone FP2
|
||||||
|
add("MI 5");
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Set<String> openSlEsAllowList() {
|
||||||
|
return new HashSet<String>() {{
|
||||||
|
add("Pixel");
|
||||||
|
add("Pixel XL");
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean hardwareAECBlocked() {
|
||||||
|
return hardwareAECBlockList().contains(Build.MODEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean openSLESAllowed() {
|
||||||
|
return openSlEsAllowList().contains(Build.MODEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import java.net.URISyntaxException;
|
|||||||
|
|
||||||
public class SmsSendtoActivity extends Activity {
|
public class SmsSendtoActivity extends Activity {
|
||||||
|
|
||||||
private static final String TAG = SmsSendtoActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(SmsSendtoActivity.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.util.CharacterCalculator;
|
import org.thoughtcrime.securesms.util.CharacterCalculator;
|
||||||
import org.thoughtcrime.securesms.util.MmsCharacterCalculator;
|
import org.thoughtcrime.securesms.util.MmsCharacterCalculator;
|
||||||
@@ -25,7 +26,7 @@ import static org.thoughtcrime.securesms.TransportOption.Type;
|
|||||||
|
|
||||||
public class TransportOptions {
|
public class TransportOptions {
|
||||||
|
|
||||||
private static final String TAG = TransportOptions.class.getSimpleName();
|
private static final String TAG = Log.tag(TransportOptions.class);
|
||||||
|
|
||||||
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
|
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import android.graphics.BitmapFactory;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
@@ -60,9 +59,11 @@ import androidx.appcompat.widget.SwitchCompat;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
|
||||||
|
import org.signal.core.util.ThreadUtil;
|
||||||
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView;
|
import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||||
|
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
@@ -78,27 +79,22 @@ import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
import org.thoughtcrime.securesms.util.DynamicDarkActionBarTheme;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
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.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
import org.whispersystems.libsignal.fingerprint.Fingerprint;
|
import org.whispersystems.libsignal.fingerprint.Fingerprint;
|
||||||
import org.whispersystems.libsignal.fingerprint.FingerprintParsingException;
|
|
||||||
import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
|
import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
|
||||||
import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
|
import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
|
||||||
|
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for verifying identity keys.
|
* Activity for verifying identity keys.
|
||||||
*
|
*
|
||||||
@@ -113,7 +109,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||||||
private static final String IDENTITY_EXTRA = "recipient_identity";
|
private static final String IDENTITY_EXTRA = "recipient_identity";
|
||||||
private static final String VERIFIED_EXTRA = "verified_state";
|
private static final String VERIFIED_EXTRA = "verified_state";
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicDarkActionBarTheme();
|
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
|
|
||||||
private final VerifyDisplayFragment displayFragment = new VerifyDisplayFragment();
|
private final VerifyDisplayFragment displayFragment = new VerifyDisplayFragment();
|
||||||
private final VerifyScanFragment scanFragment = new VerifyScanFragment();
|
private final VerifyScanFragment scanFragment = new VerifyScanFragment();
|
||||||
@@ -161,11 +157,6 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
getSupportActionBar().setTitle(R.string.AndroidManifest__verify_safety_number);
|
getSupportActionBar().setTitle(R.string.AndroidManifest__verify_safety_number);
|
||||||
|
|
||||||
LiveRecipient recipient = Recipient.live(getIntent().getParcelableExtra(RECIPIENT_EXTRA));
|
|
||||||
recipient.observe(this, r -> setActionBarNotificationBarColor(r.getColor()));
|
|
||||||
|
|
||||||
setActionBarNotificationBarColor(recipient.get().getColor());
|
|
||||||
|
|
||||||
Bundle extras = new Bundle();
|
Bundle extras = new Bundle();
|
||||||
extras.putParcelable(VerifyDisplayFragment.RECIPIENT_ID, getIntent().getParcelableExtra(RECIPIENT_EXTRA));
|
extras.putParcelable(VerifyDisplayFragment.RECIPIENT_ID, getIntent().getParcelableExtra(RECIPIENT_EXTRA));
|
||||||
extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(IDENTITY_EXTRA));
|
extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(IDENTITY_EXTRA));
|
||||||
@@ -190,7 +181,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onQrDataFound(final String data) {
|
public void onQrDataFound(final String data) {
|
||||||
Util.runOnMain(() -> {
|
ThreadUtil.runOnMain(() -> {
|
||||||
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
||||||
|
|
||||||
getSupportFragmentManager().popBackStack();
|
getSupportFragmentManager().popBackStack();
|
||||||
@@ -222,12 +213,6 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setActionBarNotificationBarColor(MaterialColor color) {
|
|
||||||
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color.toActionBarColor(this)));
|
|
||||||
|
|
||||||
WindowUtil.setStatusBarColor(getWindow(), color.toStatusBarColor(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class VerifyDisplayFragment extends Fragment implements CompoundButton.OnCheckedChangeListener {
|
public static class VerifyDisplayFragment extends Fragment implements CompoundButton.OnCheckedChangeListener {
|
||||||
|
|
||||||
public static final String RECIPIENT_ID = "recipient_id";
|
public static final String RECIPIENT_ID = "recipient_id";
|
||||||
@@ -306,6 +291,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||||||
byte[] localId;
|
byte[] localId;
|
||||||
byte[] remoteId;
|
byte[] remoteId;
|
||||||
|
|
||||||
|
//noinspection WrongThread
|
||||||
Recipient resolved = recipient.resolve();
|
Recipient resolved = recipient.resolve();
|
||||||
|
|
||||||
if (FeatureFlags.verifyV2() && resolved.getUuid().isPresent()) {
|
if (FeatureFlags.verifyV2() && resolved.getUuid().isPresent()) {
|
||||||
@@ -423,11 +409,9 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||||||
} else {
|
} else {
|
||||||
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal, Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
} catch (FingerprintParsingException e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_the_scanned_qr_code_is_not_a_correctly_formatted_safety_number, Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_the_scanned_qr_code_is_not_a_correctly_formatted_safety_number, Toast.LENGTH_LONG).show();
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,36 +606,35 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
|
public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
|
||||||
new AsyncTask<Recipient, Void, Void>() {
|
final Recipient recipient = this.recipient.get();
|
||||||
@Override
|
final RecipientId recipientId = recipient.getId();
|
||||||
protected Void doInBackground(Recipient... params) {
|
|
||||||
synchronized (SESSION_LOCK) {
|
|
||||||
if (isChecked) {
|
|
||||||
Log.i(TAG, "Saving identity: " + params[0].getId());
|
|
||||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
|
||||||
.saveIdentity(params[0].getId(),
|
|
||||||
remoteIdentity,
|
|
||||||
VerifiedStatus.VERIFIED, false,
|
|
||||||
System.currentTimeMillis(), true);
|
|
||||||
} else {
|
|
||||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
|
||||||
.setVerified(params[0].getId(),
|
|
||||||
remoteIdentity,
|
|
||||||
VerifiedStatus.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager()
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
.add(new MultiDeviceVerifiedUpdateJob(recipient.getId(),
|
try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||||
remoteIdentity,
|
if (isChecked) {
|
||||||
isChecked ? VerifiedStatus.VERIFIED :
|
Log.i(TAG, "Saving identity: " + recipientId);
|
||||||
VerifiedStatus.DEFAULT));
|
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||||
StorageSyncHelper.scheduleSyncForDataChange();
|
.saveIdentity(recipientId,
|
||||||
|
remoteIdentity,
|
||||||
IdentityUtil.markIdentityVerified(getActivity(), recipient.get(), isChecked, false);
|
VerifiedStatus.VERIFIED, false,
|
||||||
|
System.currentTimeMillis(), true);
|
||||||
|
} else {
|
||||||
|
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||||
|
.setVerified(recipientId,
|
||||||
|
remoteIdentity,
|
||||||
|
VerifiedStatus.DEFAULT);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
ApplicationDependencies.getJobManager()
|
||||||
|
.add(new MultiDeviceVerifiedUpdateJob(recipientId,
|
||||||
|
remoteIdentity,
|
||||||
|
isChecked ? VerifiedStatus.VERIFIED
|
||||||
|
: VerifiedStatus.DEFAULT));
|
||||||
|
StorageSyncHelper.scheduleSyncForDataChange();
|
||||||
|
|
||||||
|
IdentityUtil.markIdentityVerified(getActivity(), recipient, isChecked, false);
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient.get());
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ import android.media.AudioManager;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Rational;
|
import android.util.Rational;
|
||||||
import android.view.View;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowInsetsController;
|
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -37,41 +35,43 @@ import androidx.appcompat.app.AlertDialog;
|
|||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
import androidx.transition.Transition;
|
|
||||||
import androidx.transition.TransitionListenerAdapter;
|
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||||
|
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
|
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
|
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.CallToastPopupWindow;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil;
|
import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput;
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
|
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
|
||||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
|
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
|
||||||
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.ringrtc.RemotePeer;
|
import org.thoughtcrime.securesms.service.webrtc.SignalCallManager;
|
||||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.util.EllapsedTimeFormatter;
|
import org.thoughtcrime.securesms.util.EllapsedTimeFormatter;
|
||||||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
|
||||||
|
|
||||||
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback {
|
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(WebRtcCallActivity.class);
|
private static final String TAG = Log.tag(WebRtcCallActivity.class);
|
||||||
@@ -85,6 +85,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
||||||
|
|
||||||
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
||||||
|
private DeviceOrientationMonitor deviceOrientationMonitor;
|
||||||
|
|
||||||
private FullscreenHelper fullscreenHelper;
|
private FullscreenHelper fullscreenHelper;
|
||||||
private WebRtcCallView callScreen;
|
private WebRtcCallView callScreen;
|
||||||
@@ -144,7 +145,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
Log.i(TAG, "onPause");
|
Log.i(TAG, "onPause");
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
|
||||||
if (!isInPipMode()) {
|
if (!isInPipMode() || isFinishing()) {
|
||||||
EventBus.getDefault().unregister(this);
|
EventBus.getDefault().unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,18 +162,24 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
Log.i(TAG, "onStop");
|
Log.i(TAG, "onStop");
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
||||||
EventBus.getDefault().unregister(this);
|
if (!isInPipMode() || isFinishing()) {
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
}
|
||||||
|
|
||||||
if (!viewModel.isCallStarting()) {
|
if (!viewModel.isCallStarting()) {
|
||||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||||
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().cancelPreJoin();
|
||||||
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||||
@@ -240,13 +247,19 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeViewModel() {
|
private void initializeViewModel() {
|
||||||
viewModel = ViewModelProviders.of(this).get(WebRtcCallViewModel.class);
|
deviceOrientationMonitor = new DeviceOrientationMonitor(this);
|
||||||
|
getLifecycle().addObserver(deviceOrientationMonitor);
|
||||||
|
|
||||||
|
WebRtcCallViewModel.Factory factory = new WebRtcCallViewModel.Factory(deviceOrientationMonitor);
|
||||||
|
|
||||||
|
viewModel = ViewModelProviders.of(this, factory).get(WebRtcCallViewModel.class);
|
||||||
viewModel.setIsInPipMode(isInPipMode());
|
viewModel.setIsInPipMode(isInPipMode());
|
||||||
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
|
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
|
||||||
viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls);
|
viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls);
|
||||||
viewModel.getEvents().observe(this, this::handleViewModelEvent);
|
viewModel.getEvents().observe(this, this::handleViewModelEvent);
|
||||||
viewModel.getCallTime().observe(this, this::handleCallTime);
|
viewModel.getCallTime().observe(this, this::handleCallTime);
|
||||||
viewModel.getCallParticipantsState().observe(this, callScreen::updateCallParticipants);
|
LiveDataUtil.combineLatest(viewModel.getCallParticipantsState(), viewModel.getOrientation(), (s, o) -> new Pair<>(s, o == PORTRAIT_BOTTOM_EDGE))
|
||||||
|
.observe(this, p -> callScreen.updateCallParticipants(p.first(), p.second()));
|
||||||
viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate);
|
viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate);
|
||||||
viewModel.getSafetyNumberChangeEvent().observe(this, this::handleSafetyNumberChangeEvent);
|
viewModel.getSafetyNumberChangeEvent().observe(this, this::handleSafetyNumberChangeEvent);
|
||||||
viewModel.getGroupMembers().observe(this, unused -> updateGroupMembersForGroupCall());
|
viewModel.getGroupMembers().observe(this, unused -> updateGroupMembersForGroupCall());
|
||||||
@@ -256,12 +269,25 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
if (state.needsNewRequestSizes()) {
|
if (state.needsNewRequestSizes()) {
|
||||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().updateRenderedResolutions();
|
||||||
intent.setAction(WebRtcCallService.ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
viewModel.getOrientation().observe(this, orientation -> {
|
||||||
|
ApplicationDependencies.getSignalCallManager().orientationChanged(orientation.getDegrees());
|
||||||
|
|
||||||
|
switch (orientation) {
|
||||||
|
case LANDSCAPE_LEFT_EDGE:
|
||||||
|
callScreen.rotateControls(90);
|
||||||
|
break;
|
||||||
|
case LANDSCAPE_RIGHT_EDGE:
|
||||||
|
callScreen.rotateControls(-90);
|
||||||
|
break;
|
||||||
|
case PORTRAIT_BOTTOM_EDGE:
|
||||||
|
callScreen.rotateControls(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
|
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
|
||||||
@@ -271,6 +297,12 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
} else if (event instanceof WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) {
|
} else if (event instanceof WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) {
|
||||||
SafetyNumberChangeDialog.showForGroupCall(getSupportFragmentManager(), ((WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) event).getIdentityRecords());
|
SafetyNumberChangeDialog.showForGroupCall(getSupportFragmentManager(), ((WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) event).getIdentityRecords());
|
||||||
return;
|
return;
|
||||||
|
} else if (event instanceof WebRtcCallViewModel.Event.SwitchToSpeaker) {
|
||||||
|
callScreen.switchToSpeakerView();
|
||||||
|
return;
|
||||||
|
} else if (event instanceof WebRtcCallViewModel.Event.ShowSwipeToSpeakerHint) {
|
||||||
|
CallToastPopupWindow.show(callScreen);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInPipMode()) {
|
if (isInPipMode()) {
|
||||||
@@ -308,30 +340,19 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleSetAudioHandset() {
|
private void handleSetAudioHandset() {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().setAudioSpeaker(false);
|
||||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSetAudioSpeaker() {
|
private void handleSetAudioSpeaker() {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().setAudioSpeaker(true);
|
||||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER);
|
|
||||||
intent.putExtra(WebRtcCallService.EXTRA_SPEAKER, true);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSetAudioBluetooth() {
|
private void handleSetAudioBluetooth() {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().setAudioBluetooth(true);
|
||||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_BLUETOOTH);
|
|
||||||
intent.putExtra(WebRtcCallService.EXTRA_BLUETOOTH, true);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSetMuteAudio(boolean enabled) {
|
private void handleSetMuteAudio(boolean enabled) {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().setMuteAudio(enabled);
|
||||||
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_AUDIO);
|
|
||||||
intent.putExtra(WebRtcCallService.EXTRA_MUTE, enabled);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSetMuteVideo(boolean muted) {
|
private void handleSetMuteVideo(boolean muted) {
|
||||||
@@ -345,20 +366,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
.ifNecessary()
|
.ifNecessary()
|
||||||
.withRationaleDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName), R.drawable.ic_video_solid_24_tinted)
|
.withRationaleDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName), R.drawable.ic_video_solid_24_tinted)
|
||||||
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName))
|
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName))
|
||||||
.onAllGranted(() -> {
|
.onAllGranted(() -> ApplicationDependencies.getSignalCallManager().setMuteVideo(!muted))
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
|
||||||
intent.setAction(WebRtcCallService.ACTION_SET_ENABLE_VIDEO);
|
|
||||||
intent.putExtra(WebRtcCallService.EXTRA_ENABLE, !muted);
|
|
||||||
startService(intent);
|
|
||||||
})
|
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFlipCamera() {
|
private void handleFlipCamera() {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().flipCamera();
|
||||||
intent.setAction(WebRtcCallService.ACTION_FLIP_CAMERA);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAnswerWithAudio() {
|
private void handleAnswerWithAudio() {
|
||||||
@@ -375,9 +389,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
callScreen.setRecipient(recipient);
|
callScreen.setRecipient(recipient);
|
||||||
callScreen.setStatus(getString(R.string.RedPhone_answering));
|
callScreen.setStatus(getString(R.string.RedPhone_answering));
|
||||||
|
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().acceptCall(false);
|
||||||
intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL);
|
|
||||||
startService(intent);
|
|
||||||
})
|
})
|
||||||
.onAnyDenied(this::handleDenyCall)
|
.onAnyDenied(this::handleDenyCall)
|
||||||
.execute();
|
.execute();
|
||||||
@@ -398,10 +410,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
callScreen.setRecipient(recipient);
|
callScreen.setRecipient(recipient);
|
||||||
callScreen.setStatus(getString(R.string.RedPhone_answering));
|
callScreen.setStatus(getString(R.string.RedPhone_answering));
|
||||||
|
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().acceptCall(true);
|
||||||
intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL);
|
|
||||||
intent.putExtra(WebRtcCallService.EXTRA_ANSWER_WITH_VIDEO, true);
|
|
||||||
startService(intent);
|
|
||||||
|
|
||||||
handleSetMuteVideo(false);
|
handleSetMuteVideo(false);
|
||||||
})
|
})
|
||||||
@@ -414,9 +423,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
Recipient recipient = viewModel.getRecipient().get();
|
Recipient recipient = viewModel.getRecipient().get();
|
||||||
|
|
||||||
if (!recipient.equals(Recipient.UNKNOWN)) {
|
if (!recipient.equals(Recipient.UNKNOWN)) {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().denyCall();
|
||||||
intent.setAction(WebRtcCallService.ACTION_DENY_CALL);
|
|
||||||
startService(intent);
|
|
||||||
|
|
||||||
callScreen.setRecipient(recipient);
|
callScreen.setRecipient(recipient);
|
||||||
callScreen.setStatus(getString(R.string.RedPhone_ending_call));
|
callScreen.setStatus(getString(R.string.RedPhone_ending_call));
|
||||||
@@ -426,9 +433,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
|
|
||||||
private void handleEndCall() {
|
private void handleEndCall() {
|
||||||
Log.i(TAG, "Hangup pressed, handling termination now...");
|
Log.i(TAG, "Hangup pressed, handling termination now...");
|
||||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().localHangup();
|
||||||
intent.setAction(WebRtcCallService.ACTION_LOCAL_HANGUP);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleOutgoingCall(@NonNull WebRtcViewModel event) {
|
private void handleOutgoingCall(@NonNull WebRtcViewModel event) {
|
||||||
@@ -459,7 +464,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
private void handleCallBusy() {
|
private void handleCallBusy() {
|
||||||
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||||
callScreen.setStatus(getString(R.string.RedPhone_busy));
|
callScreen.setStatus(getString(R.string.RedPhone_busy));
|
||||||
delayedFinish(WebRtcCallService.BUSY_TONE_LENGTH);
|
delayedFinish(SignalCallManager.BUSY_TONE_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCallConnected(@NonNull WebRtcViewModel event) {
|
private void handleCallConnected(@NonNull WebRtcViewModel event) {
|
||||||
@@ -497,6 +502,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
final Recipient recipient = event.getRemoteParticipants().get(0).getRecipient();
|
final Recipient recipient = event.getRemoteParticipants().get(0).getRecipient();
|
||||||
|
|
||||||
if (theirKey == null) {
|
if (theirKey == null) {
|
||||||
|
Log.w(TAG, "Untrusted identity without an identity key, terminating call.");
|
||||||
handleTerminate(recipient, HangupMessage.Type.NORMAL);
|
handleTerminate(recipient, HangupMessage.Type.NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,7 +521,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateGroupMembersForGroupCall() {
|
private void updateGroupMembersForGroupCall() {
|
||||||
startService(new Intent(this, WebRtcCallService.class).setAction(WebRtcCallService.ACTION_GROUP_REQUEST_UPDATE_MEMBERS));
|
ApplicationDependencies.getSignalCallManager().requestUpdateGroupMembers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSpeakerHint(boolean showSpeakerHint) {
|
private void updateSpeakerHint(boolean showSpeakerHint) {
|
||||||
@@ -529,11 +535,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
@Override
|
@Override
|
||||||
public void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> changedRecipients) {
|
public void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> changedRecipients) {
|
||||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||||
|
|
||||||
|
if (state == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.getGroupCallState().isConnected()) {
|
if (state.getGroupCallState().isConnected()) {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().groupApproveSafetyChange(changedRecipients);
|
||||||
intent.setAction(WebRtcCallService.ACTION_GROUP_APPROVE_SAFETY_CHANGE)
|
|
||||||
.putExtra(WebRtcCallService.EXTRA_RECIPIENT_IDS, RecipientId.toSerializedList(changedRecipients));
|
|
||||||
startService(intent);
|
|
||||||
} else {
|
} else {
|
||||||
viewModel.startCall(state.getLocalParticipant().isVideoEnabled());
|
viewModel.startCall(state.getLocalParticipant().isVideoEnabled());
|
||||||
}
|
}
|
||||||
@@ -547,9 +555,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||||
if (state != null && state.getGroupCallState().isNotIdle()) {
|
if (state != null && state.getGroupCallState().isNotIdle()) {
|
||||||
if (state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
if (state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
ApplicationDependencies.getSignalCallManager().cancelPreJoin();
|
||||||
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
|
|
||||||
startService(intent);
|
|
||||||
finish();
|
finish();
|
||||||
} else {
|
} else {
|
||||||
handleEndCall();
|
handleEndCall();
|
||||||
@@ -615,11 +621,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
private void startCall(boolean isVideoCall) {
|
private void startCall(boolean isVideoCall) {
|
||||||
enableVideoIfAvailable = isVideoCall;
|
enableVideoIfAvailable = isVideoCall;
|
||||||
|
|
||||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
if (isVideoCall) {
|
||||||
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
|
ApplicationDependencies.getSignalCallManager().startOutgoingVideoCall(viewModel.getRecipient().get());
|
||||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(viewModel.getRecipient().getId()))
|
} else {
|
||||||
.putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, (isVideoCall ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL).getCode());
|
ApplicationDependencies.getSignalCallManager().startOutgoingAudioCall(viewModel.getRecipient().get());
|
||||||
startService(intent);
|
}
|
||||||
|
|
||||||
MessageSender.onMessageSent();
|
MessageSender.onMessageSent();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation.transitions
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.transition.Transition
|
||||||
|
import androidx.transition.TransitionValues
|
||||||
|
|
||||||
|
private const val ALPHA = "signal.alpha_transition.alpha"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alpha transition that can be used with [ConstraintLayout]
|
||||||
|
*/
|
||||||
|
class AlphaTransition : Transition() {
|
||||||
|
|
||||||
|
override fun captureStartValues(transitionValues: TransitionValues) {
|
||||||
|
captureValues(transitionValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun captureEndValues(transitionValues: TransitionValues) {
|
||||||
|
captureValues(transitionValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun captureValues(transitionValues: TransitionValues) {
|
||||||
|
val view: View = transitionValues.view
|
||||||
|
if (view !is ConstraintLayout) {
|
||||||
|
transitionValues.values[ALPHA] = view.alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
|
||||||
|
if (startValues == null || endValues == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val view: View = endValues.view
|
||||||
|
val startAlpha: Float = startValues.values[ALPHA] as? Float ?: view.alpha
|
||||||
|
val endAlpha: Float = endValues.values[ALPHA] as? Float ?: view.alpha
|
||||||
|
|
||||||
|
return ObjectAnimator.ofFloat(view, "alpha", startAlpha, endAlpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation.transitions
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.animation.PropertyValuesHolder
|
||||||
|
import android.animation.TypeEvaluator
|
||||||
|
import android.content.Context
|
||||||
|
import android.transition.Transition
|
||||||
|
import android.transition.TransitionValues
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.AccelerateInterpolator
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import android.view.animation.Interpolator
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
|
|
||||||
|
private const val POSITION_ON_SCREEN = "signal.circleavatartransition.positiononscreen"
|
||||||
|
private const val WIDTH = "signal.circleavatartransition.width"
|
||||||
|
private const val HEIGHT = "signal.circleavatartransition.height"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom transition for Circular avatars, because once you have multiple things animating stuff was getting broken and weird.
|
||||||
|
*/
|
||||||
|
@RequiresApi(21)
|
||||||
|
class CircleAvatarTransition(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
|
||||||
|
override fun captureStartValues(transitionValues: TransitionValues) {
|
||||||
|
captureValues(transitionValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun captureEndValues(transitionValues: TransitionValues) {
|
||||||
|
captureValues(transitionValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun captureValues(transitionValues: TransitionValues) {
|
||||||
|
val view: View = transitionValues.view
|
||||||
|
|
||||||
|
if (view is AvatarImageView) {
|
||||||
|
val topLeft = intArrayOf(0, 0)
|
||||||
|
view.getLocationOnScreen(topLeft)
|
||||||
|
transitionValues.values[POSITION_ON_SCREEN] = topLeft
|
||||||
|
transitionValues.values[WIDTH] = view.measuredWidth
|
||||||
|
transitionValues.values[HEIGHT] = view.measuredHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
|
||||||
|
if (startValues == null || endValues == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val view: View = endValues.view
|
||||||
|
if (view !is AvatarImageView || view.transitionName != "avatar") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val startCoords: IntArray = startValues.values[POSITION_ON_SCREEN] as? IntArray ?: intArrayOf(0, 0).apply { view.getLocationOnScreen(this) }
|
||||||
|
val endCoords: IntArray = endValues.values[POSITION_ON_SCREEN] as? IntArray ?: intArrayOf(0, 0).apply { view.getLocationOnScreen(this) }
|
||||||
|
|
||||||
|
val startWidth: Int = startValues.values[WIDTH] as? Int ?: view.measuredWidth
|
||||||
|
val endWidth: Int = endValues.values[WIDTH] as? Int ?: view.measuredWidth
|
||||||
|
|
||||||
|
val startHeight: Int = startValues.values[HEIGHT] as? Int ?: view.measuredHeight
|
||||||
|
val endHeight: Int = endValues.values[HEIGHT] as? Int ?: view.measuredHeight
|
||||||
|
|
||||||
|
val startHeightOffset = (endHeight - startHeight) / 2f
|
||||||
|
val startWidthOffset = (endWidth - startWidth) / 2f
|
||||||
|
|
||||||
|
val translateXHolder = PropertyValuesHolder.ofFloat("translationX", startCoords[0] - endCoords[0] - startWidthOffset, 0f).apply {
|
||||||
|
setEvaluator(FloatInterpolatorEvaluator(DecelerateInterpolator()))
|
||||||
|
}
|
||||||
|
val translateYHolder = PropertyValuesHolder.ofFloat("translationY", startCoords[1] - endCoords[1] - startHeightOffset, 0f).apply {
|
||||||
|
setEvaluator(FloatInterpolatorEvaluator(AccelerateInterpolator()))
|
||||||
|
}
|
||||||
|
|
||||||
|
val widthRatio = startWidth.toFloat() / endWidth
|
||||||
|
val scaleXHolder = PropertyValuesHolder.ofFloat("scaleX", widthRatio, 1f)
|
||||||
|
|
||||||
|
val heightRatio = startHeight.toFloat() / endHeight
|
||||||
|
val scaleYHolder = PropertyValuesHolder.ofFloat("scaleY", heightRatio, 1f)
|
||||||
|
|
||||||
|
return ObjectAnimator.ofPropertyValuesHolder(view, translateXHolder, translateYHolder, scaleXHolder, scaleYHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FloatInterpolatorEvaluator(
|
||||||
|
private val interpolator: Interpolator
|
||||||
|
) : TypeEvaluator<Float> {
|
||||||
|
|
||||||
|
override fun evaluate(fraction: Float, startValue: Float, endValue: Float): Float {
|
||||||
|
val interpolatedFraction = interpolator.getInterpolation(fraction)
|
||||||
|
val delta = endValue - startValue
|
||||||
|
|
||||||
|
return delta * interpolatedFraction + startValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.thoughtcrime.securesms.animation.transitions
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.animation.RectEvaluator
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.transition.Transition
|
||||||
|
import android.transition.TransitionValues
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.animation.addListener
|
||||||
|
import androidx.fragment.app.FragmentContainerView
|
||||||
|
|
||||||
|
private const val BOUNDS = "signal.wipedowntransition.bottom"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WipeDownTransition will animate the bottom position of a view such that it "wipes" down the screen to a final position.
|
||||||
|
*/
|
||||||
|
@RequiresApi(21)
|
||||||
|
class WipeDownTransition(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
|
||||||
|
override fun captureStartValues(transitionValues: TransitionValues) {
|
||||||
|
captureValues(transitionValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun captureEndValues(transitionValues: TransitionValues) {
|
||||||
|
captureValues(transitionValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun captureValues(transitionValues: TransitionValues) {
|
||||||
|
val view: View = transitionValues.view
|
||||||
|
|
||||||
|
if (view is ViewGroup) {
|
||||||
|
val rect = Rect()
|
||||||
|
view.getLocalVisibleRect(rect)
|
||||||
|
transitionValues.values[BOUNDS] = rect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
|
||||||
|
if (startValues == null || endValues == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val view: View = endValues.view
|
||||||
|
if (view !is FragmentContainerView) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val startBottom: Rect = startValues.values[BOUNDS] as? Rect ?: Rect().apply { view.getLocalVisibleRect(this) }
|
||||||
|
val endBottom: Rect = endValues.values[BOUNDS] as? Rect ?: Rect().apply { view.getLocalVisibleRect(this) }
|
||||||
|
|
||||||
|
return ObjectAnimator.ofObject(view, "clipBounds", RectEvaluator(), startBottom, endBottom).apply {
|
||||||
|
addListener(
|
||||||
|
onEnd = {
|
||||||
|
view.clipBounds = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ public abstract class Attachment {
|
|||||||
|
|
||||||
private final boolean voiceNote;
|
private final boolean voiceNote;
|
||||||
private final boolean borderless;
|
private final boolean borderless;
|
||||||
|
private final boolean videoGif;
|
||||||
private final int width;
|
private final int width;
|
||||||
private final int height;
|
private final int height;
|
||||||
private final boolean quote;
|
private final boolean quote;
|
||||||
@@ -72,6 +73,7 @@ public abstract class Attachment {
|
|||||||
@Nullable String fastPreflightId,
|
@Nullable String fastPreflightId,
|
||||||
boolean voiceNote,
|
boolean voiceNote,
|
||||||
boolean borderless,
|
boolean borderless,
|
||||||
|
boolean videoGif,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
boolean quote,
|
boolean quote,
|
||||||
@@ -94,6 +96,7 @@ public abstract class Attachment {
|
|||||||
this.fastPreflightId = fastPreflightId;
|
this.fastPreflightId = fastPreflightId;
|
||||||
this.voiceNote = voiceNote;
|
this.voiceNote = voiceNote;
|
||||||
this.borderless = borderless;
|
this.borderless = borderless;
|
||||||
|
this.videoGif = videoGif;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.quote = quote;
|
this.quote = quote;
|
||||||
@@ -108,6 +111,8 @@ public abstract class Attachment {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public abstract Uri getUri();
|
public abstract Uri getUri();
|
||||||
|
|
||||||
|
public abstract @Nullable Uri getPublicUri();
|
||||||
|
|
||||||
public int getTransferState() {
|
public int getTransferState() {
|
||||||
return transferState;
|
return transferState;
|
||||||
}
|
}
|
||||||
@@ -168,6 +173,10 @@ public abstract class Attachment {
|
|||||||
return borderless;
|
return borderless;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isVideoGif() {
|
||||||
|
return videoGif;
|
||||||
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public class DatabaseAttachment extends Attachment {
|
|||||||
String fastPreflightId,
|
String fastPreflightId,
|
||||||
boolean voiceNote,
|
boolean voiceNote,
|
||||||
boolean borderless,
|
boolean borderless,
|
||||||
|
boolean videoGif,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
boolean quote,
|
boolean quote,
|
||||||
@@ -47,7 +48,7 @@ public class DatabaseAttachment extends Attachment {
|
|||||||
int displayOrder,
|
int displayOrder,
|
||||||
long uploadTimestamp)
|
long uploadTimestamp)
|
||||||
{
|
{
|
||||||
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, borderless, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
||||||
this.attachmentId = attachmentId;
|
this.attachmentId = attachmentId;
|
||||||
this.hasData = hasData;
|
this.hasData = hasData;
|
||||||
this.hasThumbnail = hasThumbnail;
|
this.hasThumbnail = hasThumbnail;
|
||||||
@@ -65,6 +66,15 @@ public class DatabaseAttachment extends Attachment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Uri getPublicUri() {
|
||||||
|
if (hasData) {
|
||||||
|
return PartAuthority.getAttachmentPublicUri(getUri());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public AttachmentId getAttachmentId() {
|
public AttachmentId getAttachmentId() {
|
||||||
return attachmentId;
|
return attachmentId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
|
|||||||
public class MmsNotificationAttachment extends Attachment {
|
public class MmsNotificationAttachment extends Attachment {
|
||||||
|
|
||||||
public MmsNotificationAttachment(int status, long size) {
|
public MmsNotificationAttachment(int status, long size) {
|
||||||
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, false, 0, 0, false, 0, null, null, null, null, null);
|
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, false, false, 0, 0, false, 0, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -20,6 +20,11 @@ public class MmsNotificationAttachment extends Attachment {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Uri getPublicUri() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static int getTransferStateFromStatus(int status) {
|
private static int getTransferStateFromStatus(int status) {
|
||||||
if (status == MmsDatabase.Status.DOWNLOAD_INITIALIZED ||
|
if (status == MmsDatabase.Status.DOWNLOAD_INITIALIZED ||
|
||||||
status == MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY)
|
status == MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public class PointerAttachment extends Attachment {
|
|||||||
@Nullable String fastPreflightId,
|
@Nullable String fastPreflightId,
|
||||||
boolean voiceNote,
|
boolean voiceNote,
|
||||||
boolean borderless,
|
boolean borderless,
|
||||||
|
boolean videoGif,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
long uploadTimestamp,
|
long uploadTimestamp,
|
||||||
@@ -37,7 +38,7 @@ public class PointerAttachment extends Attachment {
|
|||||||
@Nullable StickerLocator stickerLocator,
|
@Nullable StickerLocator stickerLocator,
|
||||||
@Nullable BlurHash blurHash)
|
@Nullable BlurHash blurHash)
|
||||||
{
|
{
|
||||||
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, borderless, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
|
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, borderless, videoGif, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -46,6 +47,11 @@ public class PointerAttachment extends Attachment {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Uri getPublicUri() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static List<Attachment> forPointers(Optional<List<SignalServiceAttachment>> pointers) {
|
public static List<Attachment> forPointers(Optional<List<SignalServiceAttachment>> pointers) {
|
||||||
List<Attachment> results = new LinkedList<>();
|
List<Attachment> results = new LinkedList<>();
|
||||||
|
|
||||||
@@ -106,6 +112,7 @@ public class PointerAttachment extends Attachment {
|
|||||||
fastPreflightId,
|
fastPreflightId,
|
||||||
pointer.get().asPointer().getVoiceNote(),
|
pointer.get().asPointer().getVoiceNote(),
|
||||||
pointer.get().asPointer().isBorderless(),
|
pointer.get().asPointer().isBorderless(),
|
||||||
|
pointer.get().asPointer().isGif(),
|
||||||
pointer.get().asPointer().getWidth(),
|
pointer.get().asPointer().getWidth(),
|
||||||
pointer.get().asPointer().getHeight(),
|
pointer.get().asPointer().getHeight(),
|
||||||
pointer.get().asPointer().getUploadTimestamp(),
|
pointer.get().asPointer().getUploadTimestamp(),
|
||||||
@@ -130,6 +137,7 @@ public class PointerAttachment extends Attachment {
|
|||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
|
thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
|
||||||
thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
|
thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
|
||||||
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
|
thumbnail != null ? thumbnail.asPointer().getUploadTimestamp() : 0,
|
||||||
|
|||||||
@@ -16,11 +16,16 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
|||||||
public class TombstoneAttachment extends Attachment {
|
public class TombstoneAttachment extends Attachment {
|
||||||
|
|
||||||
public TombstoneAttachment(@NonNull String contentType, boolean quote) {
|
public TombstoneAttachment(@NonNull String contentType, boolean quote) {
|
||||||
super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, false, false, 0, 0, quote, 0, null, null, null, null, null);
|
super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, false, false, false, 0, 0, quote, 0, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Uri getUri() {
|
public @Nullable Uri getUri() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Uri getPublicUri() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class UriAttachment extends Attachment {
|
|||||||
@Nullable String fileName,
|
@Nullable String fileName,
|
||||||
boolean voiceNote,
|
boolean voiceNote,
|
||||||
boolean borderless,
|
boolean borderless,
|
||||||
|
boolean videoGif,
|
||||||
boolean quote,
|
boolean quote,
|
||||||
@Nullable String caption,
|
@Nullable String caption,
|
||||||
@Nullable StickerLocator stickerLocator,
|
@Nullable StickerLocator stickerLocator,
|
||||||
@@ -28,7 +29,7 @@ public class UriAttachment extends Attachment {
|
|||||||
@Nullable AudioHash audioHash,
|
@Nullable AudioHash audioHash,
|
||||||
@Nullable TransformProperties transformProperties)
|
@Nullable TransformProperties transformProperties)
|
||||||
{
|
{
|
||||||
this(uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote, borderless, quote, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
this(uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote, borderless, videoGif, quote, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UriAttachment(@NonNull Uri dataUri,
|
public UriAttachment(@NonNull Uri dataUri,
|
||||||
@@ -41,6 +42,7 @@ public class UriAttachment extends Attachment {
|
|||||||
@Nullable String fastPreflightId,
|
@Nullable String fastPreflightId,
|
||||||
boolean voiceNote,
|
boolean voiceNote,
|
||||||
boolean borderless,
|
boolean borderless,
|
||||||
|
boolean videoGif,
|
||||||
boolean quote,
|
boolean quote,
|
||||||
@Nullable String caption,
|
@Nullable String caption,
|
||||||
@Nullable StickerLocator stickerLocator,
|
@Nullable StickerLocator stickerLocator,
|
||||||
@@ -48,7 +50,7 @@ public class UriAttachment extends Attachment {
|
|||||||
@Nullable AudioHash audioHash,
|
@Nullable AudioHash audioHash,
|
||||||
@Nullable TransformProperties transformProperties)
|
@Nullable TransformProperties transformProperties)
|
||||||
{
|
{
|
||||||
super(contentType, transferState, size, fileName, 0, null, null, null, null, fastPreflightId, voiceNote, borderless, width, height, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
super(contentType, transferState, size, fileName, 0, null, null, null, null, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
|
||||||
this.dataUri = dataUri;
|
this.dataUri = dataUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +60,11 @@ public class UriAttachment extends Attachment {
|
|||||||
return dataUri;
|
return dataUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Uri getPublicUri() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return other != null && other instanceof UriAttachment && ((UriAttachment) other).dataUri.equals(this.dataUri);
|
return other != null && other instanceof UriAttachment && ((UriAttachment) other).dataUri.equals(this.dataUri);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import java.nio.ByteBuffer;
|
|||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
public class AudioCodec {
|
public class AudioCodec {
|
||||||
|
|
||||||
private static final String TAG = AudioCodec.class.getSimpleName();
|
private static final String TAG = Log.tag(AudioCodec.class);
|
||||||
|
|
||||||
private static final int SAMPLE_RATE = 44100;
|
private static final int SAMPLE_RATE = 44100;
|
||||||
private static final int SAMPLE_RATE_INDEX = 4;
|
private static final int SAMPLE_RATE_INDEX = 4;
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import android.os.ParcelFileDescriptor;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.core.util.ThreadUtil;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||||
import org.whispersystems.libsignal.util.Pair;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -23,7 +23,7 @@ import java.util.concurrent.ExecutorService;
|
|||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
public class AudioRecorder {
|
public class AudioRecorder {
|
||||||
|
|
||||||
private static final String TAG = AudioRecorder.class.getSimpleName();
|
private static final String TAG = Log.tag(AudioRecorder.class);
|
||||||
|
|
||||||
private static final ExecutorService executor = SignalExecutors.newCachedSingleThreadExecutor("signal-AudioRecorder");
|
private static final ExecutorService executor = SignalExecutors.newCachedSingleThreadExecutor("signal-AudioRecorder");
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ public class AudioRecorder {
|
|||||||
captureUri = BlobProvider.getInstance()
|
captureUri = BlobProvider.getInstance()
|
||||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||||
.withMimeType(MediaUtil.AUDIO_AAC)
|
.withMimeType(MediaUtil.AUDIO_AAC)
|
||||||
.createForSingleSessionOnDiskAsync(context, () -> Log.i(TAG, "Write successful."), e -> Log.w(TAG, "Error during recording", e));
|
.createForDraftAttachmentAsync(context, () -> Log.i(TAG, "Write successful."), e -> Log.w(TAG, "Error during recording", e));
|
||||||
audioCodec = new AudioCodec();
|
audioCodec = new AudioCodec();
|
||||||
|
|
||||||
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
||||||
@@ -61,10 +61,10 @@ public class AudioRecorder {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull ListenableFuture<Pair<Uri, Long>> stopRecording() {
|
public @NonNull ListenableFuture<VoiceNoteDraft> stopRecording() {
|
||||||
Log.i(TAG, "stopRecording()");
|
Log.i(TAG, "stopRecording()");
|
||||||
|
|
||||||
final SettableFuture<Pair<Uri, Long>> future = new SettableFuture<>();
|
final SettableFuture<VoiceNoteDraft> future = new SettableFuture<>();
|
||||||
|
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
if (audioCodec == null) {
|
if (audioCodec == null) {
|
||||||
@@ -76,7 +76,7 @@ public class AudioRecorder {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
long size = MediaUtil.getMediaSize(context, captureUri);
|
long size = MediaUtil.getMediaSize(context, captureUri);
|
||||||
sendToFuture(future, new Pair<>(captureUri, size));
|
sendToFuture(future, new VoiceNoteDraft(captureUri, size));
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Log.w(TAG, ioe);
|
Log.w(TAG, ioe);
|
||||||
sendToFuture(future, ioe);
|
sendToFuture(future, ioe);
|
||||||
@@ -90,10 +90,10 @@ public class AudioRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private <T> void sendToFuture(final SettableFuture<T> future, final Exception exception) {
|
private <T> void sendToFuture(final SettableFuture<T> future, final Exception exception) {
|
||||||
Util.runOnMain(() -> future.setException(exception));
|
ThreadUtil.runOnMain(() -> future.setException(exception));
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void sendToFuture(final SettableFuture<T> future, final T result) {
|
private <T> void sendToFuture(final SettableFuture<T> future, final T result) {
|
||||||
Util.runOnMain(() -> future.set(result));
|
ThreadUtil.runOnMain(() -> future.set(result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import androidx.core.util.Consumer;
|
|||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
import org.signal.core.util.ThreadUtil;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
@@ -26,7 +27,6 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormDat
|
|||||||
import org.thoughtcrime.securesms.media.DecryptableUriMediaInput;
|
import org.thoughtcrime.securesms.media.DecryptableUriMediaInput;
|
||||||
import org.thoughtcrime.securesms.media.MediaInput;
|
import org.thoughtcrime.securesms.media.MediaInput;
|
||||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor;
|
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -61,13 +61,7 @@ public final class AudioWaveForm {
|
|||||||
|
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
Log.w(TAG, "No uri");
|
Log.w(TAG, "No uri");
|
||||||
Util.runOnMain(onFailure);
|
ThreadUtil.runOnMain(onFailure);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(attachment instanceof DatabaseAttachment)) {
|
|
||||||
Log.i(TAG, "Not yet in database");
|
|
||||||
Util.runOnMain(onFailure);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +69,7 @@ public final class AudioWaveForm {
|
|||||||
AudioFileInfo cached = WAVE_FORM_CACHE.get(cacheKey);
|
AudioFileInfo cached = WAVE_FORM_CACHE.get(cacheKey);
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
Log.i(TAG, "Loaded wave form from cache " + cacheKey);
|
Log.i(TAG, "Loaded wave form from cache " + cacheKey);
|
||||||
Util.runOnMain(() -> onSuccess.accept(cached));
|
ThreadUtil.runOnMain(() -> onSuccess.accept(cached));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +77,7 @@ public final class AudioWaveForm {
|
|||||||
AudioFileInfo cachedInExecutor = WAVE_FORM_CACHE.get(cacheKey);
|
AudioFileInfo cachedInExecutor = WAVE_FORM_CACHE.get(cacheKey);
|
||||||
if (cachedInExecutor != null) {
|
if (cachedInExecutor != null) {
|
||||||
Log.i(TAG, "Loaded wave form from cache inside executor" + cacheKey);
|
Log.i(TAG, "Loaded wave form from cache inside executor" + cacheKey);
|
||||||
Util.runOnMain(() -> onSuccess.accept(cachedInExecutor));
|
ThreadUtil.runOnMain(() -> onSuccess.accept(cachedInExecutor));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,38 +86,58 @@ public final class AudioWaveForm {
|
|||||||
AudioFileInfo audioFileInfo = AudioFileInfo.fromDatabaseProtobuf(audioHash.getAudioWaveForm());
|
AudioFileInfo audioFileInfo = AudioFileInfo.fromDatabaseProtobuf(audioHash.getAudioWaveForm());
|
||||||
if (audioFileInfo.waveForm.length == 0) {
|
if (audioFileInfo.waveForm.length == 0) {
|
||||||
Log.w(TAG, "Recovering from a wave form generation error " + cacheKey);
|
Log.w(TAG, "Recovering from a wave form generation error " + cacheKey);
|
||||||
Util.runOnMain(onFailure);
|
ThreadUtil.runOnMain(onFailure);
|
||||||
return;
|
return;
|
||||||
} else if (audioFileInfo.waveForm.length != BAR_COUNT) {
|
} else if (audioFileInfo.waveForm.length != BAR_COUNT) {
|
||||||
Log.w(TAG, "Wave form from database does not match bar count, regenerating " + cacheKey);
|
Log.w(TAG, "Wave form from database does not match bar count, regenerating " + cacheKey);
|
||||||
} else {
|
} else {
|
||||||
WAVE_FORM_CACHE.put(cacheKey, audioFileInfo);
|
WAVE_FORM_CACHE.put(cacheKey, audioFileInfo);
|
||||||
Log.i(TAG, "Loaded wave form from DB " + cacheKey);
|
Log.i(TAG, "Loaded wave form from DB " + cacheKey);
|
||||||
Util.runOnMain(() -> onSuccess.accept(audioFileInfo));
|
ThreadUtil.runOnMain(() -> onSuccess.accept(audioFileInfo));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (attachment instanceof DatabaseAttachment) {
|
||||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
try {
|
||||||
DatabaseAttachment dbAttachment = (DatabaseAttachment) attachment;
|
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||||
long startTime = System.currentTimeMillis();
|
DatabaseAttachment dbAttachment = (DatabaseAttachment) attachment;
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
attachmentDatabase.writeAudioHash(dbAttachment.getAttachmentId(), AudioWaveFormData.getDefaultInstance());
|
attachmentDatabase.writeAudioHash(dbAttachment.getAttachmentId(), AudioWaveFormData.getDefaultInstance());
|
||||||
|
|
||||||
Log.i(TAG, String.format("Starting wave form generation (%s)", cacheKey));
|
Log.i(TAG, String.format("Starting wave form generation (%s)", cacheKey));
|
||||||
|
|
||||||
AudioFileInfo fileInfo = generateWaveForm(uri);
|
AudioFileInfo fileInfo = generateWaveForm(uri);
|
||||||
|
|
||||||
Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms (%s)", System.currentTimeMillis() - startTime, cacheKey));
|
Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms (%s)", System.currentTimeMillis() - startTime, cacheKey));
|
||||||
|
|
||||||
attachmentDatabase.writeAudioHash(dbAttachment.getAttachmentId(), fileInfo.toDatabaseProtobuf());
|
attachmentDatabase.writeAudioHash(dbAttachment.getAttachmentId(), fileInfo.toDatabaseProtobuf());
|
||||||
|
|
||||||
WAVE_FORM_CACHE.put(cacheKey, fileInfo);
|
WAVE_FORM_CACHE.put(cacheKey, fileInfo);
|
||||||
Util.runOnMain(() -> onSuccess.accept(fileInfo));
|
ThreadUtil.runOnMain(() -> onSuccess.accept(fileInfo));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e);
|
Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e);
|
||||||
Util.runOnMain(onFailure);
|
ThreadUtil.runOnMain(onFailure);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Log.i(TAG, "Not in database and not cached. Generating wave form on-the-fly.");
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
Log.i(TAG, String.format("Starting wave form generation (%s)", cacheKey));
|
||||||
|
|
||||||
|
AudioFileInfo fileInfo = generateWaveForm(uri);
|
||||||
|
|
||||||
|
Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms (%s)", System.currentTimeMillis() - startTime, cacheKey));
|
||||||
|
|
||||||
|
WAVE_FORM_CACHE.put(cacheKey, fileInfo);
|
||||||
|
ThreadUtil.runOnMain(() -> onSuccess.accept(fileInfo));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e);
|
||||||
|
ThreadUtil.runOnMain(onFailure);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class BackupDialog {
|
|||||||
|
|
||||||
BackupPassphrase.set(context, Util.join(password, " "));
|
BackupPassphrase.set(context, Util.join(password, " "));
|
||||||
TextSecurePreferences.setNextBackupTime(context, 0);
|
TextSecurePreferences.setNextBackupTime(context, 0);
|
||||||
TextSecurePreferences.setBackupEnabled(context, true);
|
SignalStore.settings().setBackupEnabled(true);
|
||||||
LocalBackupListener.schedule(context);
|
LocalBackupListener.schedule(context);
|
||||||
|
|
||||||
onBackupsEnabled.run();
|
onBackupsEnabled.run();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.backup;
|
|||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -11,8 +10,8 @@ import androidx.annotation.StringRes;
|
|||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationCancellationHelper;
|
import org.thoughtcrime.securesms.notifications.NotificationCancellationHelper;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
|
|
||||||
@@ -39,11 +38,7 @@ public enum BackupFileIOError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void postNotification(@NonNull Context context) {
|
public void postNotification(@NonNull Context context) {
|
||||||
Intent intent = new Intent(context, ApplicationPreferencesActivity.class);
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, AppSettingsActivity.backups(context), 0);
|
||||||
|
|
||||||
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_BACKUPS_FRAGMENT, true);
|
|
||||||
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, intent, 0);
|
|
||||||
Notification backupFailedNotification = new NotificationCompat.Builder(context, NotificationChannels.FAILURES)
|
Notification backupFailedNotification = new NotificationCompat.Builder(context, NotificationChannels.FAILURES)
|
||||||
.setSmallIcon(R.drawable.ic_signal_backup)
|
.setSmallIcon(R.drawable.ic_signal_backup)
|
||||||
.setContentTitle(context.getString(titleId))
|
.setContentTitle(context.getString(titleId))
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public final class BackupPassphrase {
|
|||||||
private BackupPassphrase() {
|
private BackupPassphrase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String TAG = BackupPassphrase.class.getSimpleName();
|
private static final String TAG = Log.tag(BackupPassphrase.class);
|
||||||
|
|
||||||
public static @Nullable String get(@NonNull Context context) {
|
public static @Nullable String get(@NonNull Context context) {
|
||||||
String passphrase = TextSecurePreferences.getBackupPassphrase(context);
|
String passphrase = TextSecurePreferences.getBackupPassphrase(context);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.whispersystems.libsignal.util.ByteUtil;
|
import org.whispersystems.libsignal.util.ByteUtil;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
@@ -13,7 +14,7 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
public abstract class FullBackupBase {
|
public abstract class FullBackupBase {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = FullBackupBase.class.getSimpleName();
|
private static final String TAG = Log.tag(FullBackupBase.class);
|
||||||
|
|
||||||
static class BackupStream {
|
static class BackupStream {
|
||||||
static @NonNull byte[] getBackupKey(@NonNull String passphrase, @Nullable byte[] salt) {
|
static @NonNull byte[] getBackupKey(@NonNull String passphrase, @Nullable byte[] salt) {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import com.annimon.stream.function.Consumer;
|
|
||||||
import com.annimon.stream.function.Predicate;
|
import com.annimon.stream.function.Predicate;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
@@ -25,20 +24,28 @@ import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
|||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
|
||||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||||
import org.thoughtcrime.securesms.database.JobDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.PendingRetryReceiptDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.SenderKeyDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.SenderKeySharedDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SessionDatabase;
|
import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||||
|
import org.thoughtcrime.securesms.service.PendingRetryReceiptManager;
|
||||||
import org.thoughtcrime.securesms.util.SetUtil;
|
import org.thoughtcrime.securesms.util.SetUtil;
|
||||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.kdf.HKDFv3;
|
import org.whispersystems.libsignal.kdf.HKDFv3;
|
||||||
import org.whispersystems.libsignal.util.ByteUtil;
|
import org.whispersystems.libsignal.util.ByteUtil;
|
||||||
@@ -66,26 +73,30 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
|
|
||||||
public class FullBackupExporter extends FullBackupBase {
|
public class FullBackupExporter extends FullBackupBase {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
private static final String TAG = Log.tag(FullBackupExporter.class);
|
||||||
private static final String TAG = FullBackupExporter.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final Set<String> BLACKLISTED_TABLES = SetUtil.newHashSet(
|
private static final Set<String> BLACKLISTED_TABLES = SetUtil.newHashSet(
|
||||||
SignedPreKeyDatabase.TABLE_NAME,
|
SignedPreKeyDatabase.TABLE_NAME,
|
||||||
OneTimePreKeyDatabase.TABLE_NAME,
|
OneTimePreKeyDatabase.TABLE_NAME,
|
||||||
SessionDatabase.TABLE_NAME,
|
SessionDatabase.TABLE_NAME,
|
||||||
SearchDatabase.SMS_FTS_TABLE_NAME,
|
SearchDatabase.SMS_FTS_TABLE_NAME,
|
||||||
SearchDatabase.MMS_FTS_TABLE_NAME
|
SearchDatabase.MMS_FTS_TABLE_NAME,
|
||||||
|
EmojiSearchDatabase.TABLE_NAME,
|
||||||
|
SenderKeyDatabase.TABLE_NAME,
|
||||||
|
SenderKeySharedDatabase.TABLE_NAME,
|
||||||
|
PendingRetryReceiptDatabase.TABLE_NAME
|
||||||
);
|
);
|
||||||
|
|
||||||
public static void export(@NonNull Context context,
|
public static void export(@NonNull Context context,
|
||||||
@NonNull AttachmentSecret attachmentSecret,
|
@NonNull AttachmentSecret attachmentSecret,
|
||||||
@NonNull SQLiteDatabase input,
|
@NonNull SQLiteDatabase input,
|
||||||
@NonNull File output,
|
@NonNull File output,
|
||||||
@NonNull String passphrase)
|
@NonNull String passphrase,
|
||||||
|
@NonNull BackupCancellationSignal cancellationSignal)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
try (OutputStream outputStream = new FileOutputStream(output)) {
|
try (OutputStream outputStream = new FileOutputStream(output)) {
|
||||||
internalExport(context, attachmentSecret, input, outputStream, passphrase);
|
internalExport(context, attachmentSecret, input, outputStream, passphrase, true, cancellationSignal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,19 +105,32 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
@NonNull AttachmentSecret attachmentSecret,
|
@NonNull AttachmentSecret attachmentSecret,
|
||||||
@NonNull SQLiteDatabase input,
|
@NonNull SQLiteDatabase input,
|
||||||
@NonNull DocumentFile output,
|
@NonNull DocumentFile output,
|
||||||
@NonNull String passphrase)
|
@NonNull String passphrase,
|
||||||
|
@NonNull BackupCancellationSignal cancellationSignal)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
try (OutputStream outputStream = Objects.requireNonNull(context.getContentResolver().openOutputStream(output.getUri()))) {
|
try (OutputStream outputStream = Objects.requireNonNull(context.getContentResolver().openOutputStream(output.getUri()))) {
|
||||||
internalExport(context, attachmentSecret, input, outputStream, passphrase);
|
internalExport(context, attachmentSecret, input, outputStream, passphrase, true, cancellationSignal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void transfer(@NonNull Context context,
|
||||||
|
@NonNull AttachmentSecret attachmentSecret,
|
||||||
|
@NonNull SQLiteDatabase input,
|
||||||
|
@NonNull OutputStream outputStream,
|
||||||
|
@NonNull String passphrase)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
internalExport(context, attachmentSecret, input, outputStream, passphrase, false, () -> false);
|
||||||
|
}
|
||||||
|
|
||||||
private static void internalExport(@NonNull Context context,
|
private static void internalExport(@NonNull Context context,
|
||||||
@NonNull AttachmentSecret attachmentSecret,
|
@NonNull AttachmentSecret attachmentSecret,
|
||||||
@NonNull SQLiteDatabase input,
|
@NonNull SQLiteDatabase input,
|
||||||
@NonNull OutputStream fileOutputStream,
|
@NonNull OutputStream fileOutputStream,
|
||||||
@NonNull String passphrase)
|
@NonNull String passphrase,
|
||||||
|
boolean closeOutputStream,
|
||||||
|
@NonNull BackupCancellationSignal cancellationSignal)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase);
|
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase);
|
||||||
@@ -114,36 +138,51 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
outputStream.writeDatabaseVersion(input.getVersion());
|
outputStream.writeDatabaseVersion(input.getVersion());
|
||||||
|
count++;
|
||||||
|
|
||||||
List<String> tables = exportSchema(input, outputStream);
|
List<String> tables = exportSchema(input, outputStream);
|
||||||
|
count += tables.size() * 3;
|
||||||
|
|
||||||
Stopwatch stopwatch = new Stopwatch("Backup");
|
Stopwatch stopwatch = new Stopwatch("Backup");
|
||||||
|
|
||||||
for (String table : tables) {
|
for (String table : tables) {
|
||||||
|
throwIfCanceled(cancellationSignal);
|
||||||
if (table.equals(MmsDatabase.TABLE_NAME)) {
|
if (table.equals(MmsDatabase.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count);
|
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, cancellationSignal);
|
||||||
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
|
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count);
|
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count, cancellationSignal);
|
||||||
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
|
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count);
|
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count, cancellationSignal);
|
||||||
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
|
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), cursor -> exportAttachment(attachmentSecret, cursor, outputStream), count);
|
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount), count, cancellationSignal);
|
||||||
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
|
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> true, cursor -> exportSticker(attachmentSecret, cursor, outputStream), count);
|
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount), count, cancellationSignal);
|
||||||
} else if (!BLACKLISTED_TABLES.contains(table) && !table.startsWith("sqlite_")) {
|
} else if (!BLACKLISTED_TABLES.contains(table) && !table.startsWith("sqlite_")) {
|
||||||
count = exportTable(table, input, outputStream, null, null, count);
|
count = exportTable(table, input, outputStream, null, null, count, cancellationSignal);
|
||||||
}
|
}
|
||||||
stopwatch.split("table::" + table);
|
stopwatch.split("table::" + table);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (BackupProtos.SharedPreference preference : IdentityKeyUtil.getBackupRecord(context)) {
|
for (BackupProtos.SharedPreference preference : IdentityKeyUtil.getBackupRecord(context)) {
|
||||||
|
throwIfCanceled(cancellationSignal);
|
||||||
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||||
|
outputStream.write(preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BackupProtos.SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) {
|
||||||
|
throwIfCanceled(cancellationSignal);
|
||||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||||
outputStream.write(preference);
|
outputStream.write(preference);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopwatch.split("prefs");
|
stopwatch.split("prefs");
|
||||||
|
|
||||||
|
count = exportKeyValues(outputStream, SignalStore.getKeysToIncludeInBackup(), count, cancellationSignal);
|
||||||
|
|
||||||
|
stopwatch.split("key_values");
|
||||||
|
|
||||||
for (AvatarHelper.Avatar avatar : AvatarHelper.getAvatars(context)) {
|
for (AvatarHelper.Avatar avatar : AvatarHelper.getAvatars(context)) {
|
||||||
|
throwIfCanceled(cancellationSignal);
|
||||||
if (avatar != null) {
|
if (avatar != null) {
|
||||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||||
outputStream.write(avatar.getFilename(), avatar.getInputStream(), avatar.getLength());
|
outputStream.write(avatar.getFilename(), avatar.getInputStream(), avatar.getLength());
|
||||||
@@ -155,11 +194,19 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
|
|
||||||
outputStream.writeEnd();
|
outputStream.writeEnd();
|
||||||
} finally {
|
} finally {
|
||||||
outputStream.close();
|
if (closeOutputStream) {
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count));
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void throwIfCanceled(@NonNull BackupCancellationSignal cancellationSignal) throws BackupCanceledException {
|
||||||
|
if (cancellationSignal.isCanceled()) {
|
||||||
|
throw new BackupCanceledException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static List<String> exportSchema(@NonNull SQLiteDatabase input, @NonNull BackupFrameOutputStream outputStream)
|
private static List<String> exportSchema(@NonNull SQLiteDatabase input, @NonNull BackupFrameOutputStream outputStream)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
@@ -172,11 +219,11 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
String type = cursor.getString(2);
|
String type = cursor.getString(2);
|
||||||
|
|
||||||
if (sql != null) {
|
if (sql != null) {
|
||||||
|
boolean isSmsFtsSecretTable = name != null && !name.equals(SearchDatabase.SMS_FTS_TABLE_NAME) && name.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME);
|
||||||
|
boolean isMmsFtsSecretTable = name != null && !name.equals(SearchDatabase.MMS_FTS_TABLE_NAME) && name.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME);
|
||||||
|
boolean isEmojiFtsSecretTable = name != null && !name.equals(EmojiSearchDatabase.TABLE_NAME) && name.startsWith(EmojiSearchDatabase.TABLE_NAME);
|
||||||
|
|
||||||
boolean isSmsFtsSecretTable = name != null && !name.equals(SearchDatabase.SMS_FTS_TABLE_NAME) && name.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME);
|
if (!isSmsFtsSecretTable && !isMmsFtsSecretTable && !isEmojiFtsSecretTable) {
|
||||||
boolean isMmsFtsSecretTable = name != null && !name.equals(SearchDatabase.MMS_FTS_TABLE_NAME) && name.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME);
|
|
||||||
|
|
||||||
if (!isSmsFtsSecretTable && !isMmsFtsSecretTable) {
|
|
||||||
if ("table".equals(type)) {
|
if ("table".equals(type)) {
|
||||||
tables.add(name);
|
tables.add(name);
|
||||||
}
|
}
|
||||||
@@ -190,19 +237,20 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
return tables;
|
return tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int exportTable(@NonNull String table,
|
private static int exportTable(@NonNull String table,
|
||||||
@NonNull SQLiteDatabase input,
|
@NonNull SQLiteDatabase input,
|
||||||
@NonNull BackupFrameOutputStream outputStream,
|
@NonNull BackupFrameOutputStream outputStream,
|
||||||
@Nullable Predicate<Cursor> predicate,
|
@Nullable Predicate<Cursor> predicate,
|
||||||
@Nullable Consumer<Cursor> postProcess,
|
@Nullable PostProcessor postProcess,
|
||||||
int count)
|
int count,
|
||||||
|
@NonNull BackupCancellationSignal cancellationSignal)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
String template = "INSERT INTO " + table + " VALUES ";
|
String template = "INSERT INTO " + table + " VALUES ";
|
||||||
|
|
||||||
try (Cursor cursor = input.rawQuery("SELECT * FROM " + table, null)) {
|
try (Cursor cursor = input.rawQuery("SELECT * FROM " + table, null)) {
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
throwIfCanceled(cancellationSignal);
|
||||||
|
|
||||||
if (predicate == null || predicate.test(cursor)) {
|
if (predicate == null || predicate.test(cursor)) {
|
||||||
StringBuilder statement = new StringBuilder(template);
|
StringBuilder statement = new StringBuilder(template);
|
||||||
@@ -234,9 +282,12 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
|
|
||||||
statement.append(')');
|
statement.append(')');
|
||||||
|
|
||||||
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||||
outputStream.write(statementBuilder.setStatement(statement.toString()).build());
|
outputStream.write(statementBuilder.setStatement(statement.toString()).build());
|
||||||
|
|
||||||
if (postProcess != null) postProcess.accept(cursor);
|
if (postProcess != null) {
|
||||||
|
count = postProcess.postProcess(cursor, count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,7 +295,7 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream) {
|
private static int exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count) {
|
||||||
try {
|
try {
|
||||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID));
|
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID));
|
||||||
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID));
|
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID));
|
||||||
@@ -269,14 +320,17 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
if (random != null && random.length == 32) inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
|
if (random != null && random.length == 32) inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
|
||||||
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
|
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
|
||||||
|
|
||||||
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||||
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
|
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream) {
|
private static int exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count) {
|
||||||
try {
|
try {
|
||||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID));
|
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID));
|
||||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH));
|
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH));
|
||||||
@@ -285,12 +339,15 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM));
|
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM));
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(data) && size > 0) {
|
if (!TextUtils.isEmpty(data) && size > 0) {
|
||||||
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||||
InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
|
InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
|
||||||
outputStream.writeSticker(rowId, inputStream, size);
|
outputStream.writeSticker(rowId, inputStream, size);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long calculateVeryOldStreamLength(@NonNull AttachmentSecret attachmentSecret, @Nullable byte[] random, @NonNull String data) throws IOException {
|
private static long calculateVeryOldStreamLength(@NonNull AttachmentSecret attachmentSecret, @Nullable byte[] random, @NonNull String data) throws IOException {
|
||||||
@@ -310,6 +367,46 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int exportKeyValues(@NonNull BackupFrameOutputStream outputStream,
|
||||||
|
@NonNull List<String> keysToIncludeInBackup,
|
||||||
|
int count,
|
||||||
|
BackupCancellationSignal cancellationSignal) throws IOException
|
||||||
|
{
|
||||||
|
KeyValueDataSet dataSet = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication())
|
||||||
|
.getDataSet();
|
||||||
|
|
||||||
|
for (String key : keysToIncludeInBackup) {
|
||||||
|
throwIfCanceled(cancellationSignal);
|
||||||
|
if (!dataSet.containsKey(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BackupProtos.KeyValue.Builder builder = BackupProtos.KeyValue.newBuilder()
|
||||||
|
.setKey(key);
|
||||||
|
|
||||||
|
Class<?> type = dataSet.getType(key);
|
||||||
|
if (type == byte[].class) {
|
||||||
|
builder.setBlobValue(ByteString.copyFrom(dataSet.getBlob(key, null)));
|
||||||
|
} else if (type == Boolean.class) {
|
||||||
|
builder.setBooleanValue(dataSet.getBoolean(key, false));
|
||||||
|
} else if (type == Float.class) {
|
||||||
|
builder.setFloatValue(dataSet.getFloat(key, 0));
|
||||||
|
} else if (type == Integer.class) {
|
||||||
|
builder.setIntegerValue(dataSet.getInteger(key, 0));
|
||||||
|
} else if (type == Long.class) {
|
||||||
|
builder.setLongValue(dataSet.getLong(key, 0));
|
||||||
|
} else if (type == String.class) {
|
||||||
|
builder.setStringValue(dataSet.getString(key, null));
|
||||||
|
} else {
|
||||||
|
throw new AssertionError("Unknown type: " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||||
|
outputStream.write(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isNonExpiringMmsMessage(@NonNull Cursor cursor) {
|
private static boolean isNonExpiringMmsMessage(@NonNull Cursor cursor) {
|
||||||
return cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 &&
|
return cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 &&
|
||||||
cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) <= 0;
|
cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) <= 0;
|
||||||
@@ -381,6 +478,10 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
write(outputStream, BackupProtos.BackupFrame.newBuilder().setPreference(preference).build());
|
write(outputStream, BackupProtos.BackupFrame.newBuilder().setPreference(preference).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void write(BackupProtos.KeyValue keyValue) throws IOException {
|
||||||
|
write(outputStream, BackupProtos.BackupFrame.newBuilder().setKeyValue(keyValue).build());
|
||||||
|
}
|
||||||
|
|
||||||
public void write(BackupProtos.SqlStatement statement) throws IOException {
|
public void write(BackupProtos.SqlStatement statement) throws IOException {
|
||||||
write(outputStream, BackupProtos.BackupFrame.newBuilder().setStatement(statement).build());
|
write(outputStream, BackupProtos.BackupFrame.newBuilder().setStatement(statement).build());
|
||||||
}
|
}
|
||||||
@@ -495,4 +596,14 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
outputStream.close();
|
outputStream.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface PostProcessor {
|
||||||
|
int postProcess(@NonNull Cursor cursor, int count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface BackupCancellationSignal {
|
||||||
|
boolean isCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class BackupCanceledException extends IOException { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,12 @@ import org.thoughtcrime.securesms.backup.BackupProtos.Sticker;
|
|||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||||
@@ -45,6 +49,8 @@ import java.security.InvalidAlgorithmParameterException;
|
|||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -60,25 +66,37 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
public class FullBackupImporter extends FullBackupBase {
|
public class FullBackupImporter extends FullBackupBase {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = FullBackupImporter.class.getSimpleName();
|
private static final String TAG = Log.tag(FullBackupImporter.class);
|
||||||
|
|
||||||
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
||||||
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
|
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
|
||||||
throws IOException
|
throws IOException
|
||||||
|
{
|
||||||
|
try (InputStream is = getInputStream(context, uri)) {
|
||||||
|
importFile(context, attachmentSecret, db, is, passphrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
||||||
|
@NonNull SQLiteDatabase db, @NonNull InputStream is, @NonNull String passphrase)
|
||||||
|
throws IOException
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
try (InputStream is = getInputStream(context, uri)) {
|
SQLiteDatabase keyValueDatabase = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).getSqlCipherDatabase();
|
||||||
|
try {
|
||||||
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
|
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
|
||||||
|
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
|
keyValueDatabase.beginTransaction();
|
||||||
|
|
||||||
dropAllTables(db);
|
dropAllTables(db);
|
||||||
|
|
||||||
BackupFrame frame;
|
BackupFrame frame;
|
||||||
|
|
||||||
while (!(frame = inputStream.readFrame()).getEnd()) {
|
while (!(frame = inputStream.readFrame()).getEnd()) {
|
||||||
if (count++ % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count));
|
if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count));
|
||||||
|
count++;
|
||||||
|
|
||||||
if (frame.hasVersion()) processVersion(db, frame.getVersion());
|
if (frame.hasVersion()) processVersion(db, frame.getVersion());
|
||||||
else if (frame.hasStatement()) processStatement(db, frame.getStatement());
|
else if (frame.hasStatement()) processStatement(db, frame.getStatement());
|
||||||
@@ -86,18 +104,22 @@ public class FullBackupImporter extends FullBackupBase {
|
|||||||
else if (frame.hasAttachment()) processAttachment(context, attachmentSecret, db, frame.getAttachment(), inputStream);
|
else if (frame.hasAttachment()) processAttachment(context, attachmentSecret, db, frame.getAttachment(), inputStream);
|
||||||
else if (frame.hasSticker()) processSticker(context, attachmentSecret, db, frame.getSticker(), inputStream);
|
else if (frame.hasSticker()) processSticker(context, attachmentSecret, db, frame.getSticker(), inputStream);
|
||||||
else if (frame.hasAvatar()) processAvatar(context, db, frame.getAvatar(), inputStream);
|
else if (frame.hasAvatar()) processAvatar(context, db, frame.getAvatar(), inputStream);
|
||||||
|
else if (frame.hasKeyValue()) processKeyValue(frame.getKeyValue());
|
||||||
|
else count--;
|
||||||
}
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
keyValueDatabase.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
|
keyValueDatabase.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, count));
|
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull InputStream getInputStream(@NonNull Context context, @NonNull Uri uri) throws IOException{
|
private static @NonNull InputStream getInputStream(@NonNull Context context, @NonNull Uri uri) throws IOException{
|
||||||
if (BackupUtil.isUserSelectionRequired(context)) {
|
if (BackupUtil.isUserSelectionRequired(context) || uri.getScheme().equals("content")) {
|
||||||
return Objects.requireNonNull(context.getContentResolver().openInputStream(uri));
|
return Objects.requireNonNull(context.getContentResolver().openInputStream(uri));
|
||||||
} else {
|
} else {
|
||||||
return new FileInputStream(new File(Objects.requireNonNull(uri.getPath())));
|
return new FileInputStream(new File(Objects.requireNonNull(uri.getPath())));
|
||||||
@@ -115,9 +137,10 @@ public class FullBackupImporter extends FullBackupBase {
|
|||||||
private static void processStatement(@NonNull SQLiteDatabase db, SqlStatement statement) {
|
private static void processStatement(@NonNull SQLiteDatabase db, SqlStatement statement) {
|
||||||
boolean isForSmsFtsSecretTable = statement.getStatement().contains(SearchDatabase.SMS_FTS_TABLE_NAME + "_");
|
boolean isForSmsFtsSecretTable = statement.getStatement().contains(SearchDatabase.SMS_FTS_TABLE_NAME + "_");
|
||||||
boolean isForMmsFtsSecretTable = statement.getStatement().contains(SearchDatabase.MMS_FTS_TABLE_NAME + "_");
|
boolean isForMmsFtsSecretTable = statement.getStatement().contains(SearchDatabase.MMS_FTS_TABLE_NAME + "_");
|
||||||
|
boolean isForEmojiSecretTable = statement.getStatement().contains(EmojiSearchDatabase.TABLE_NAME + "_");
|
||||||
boolean isForSqliteSecretTable = statement.getStatement().toLowerCase().startsWith("create table sqlite_");
|
boolean isForSqliteSecretTable = statement.getStatement().toLowerCase().startsWith("create table sqlite_");
|
||||||
|
|
||||||
if (isForSmsFtsSecretTable || isForMmsFtsSecretTable || isForSqliteSecretTable) {
|
if (isForSmsFtsSecretTable || isForMmsFtsSecretTable || isForEmojiSecretTable || isForSqliteSecretTable) {
|
||||||
Log.i(TAG, "Ignoring import for statement: " + statement.getStatement());
|
Log.i(TAG, "Ignoring import for statement: " + statement.getStatement());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -201,10 +224,40 @@ public class FullBackupImporter extends FullBackupBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void processKeyValue(BackupProtos.KeyValue keyValue) {
|
||||||
|
KeyValueDataSet dataSet = new KeyValueDataSet();
|
||||||
|
|
||||||
|
if (keyValue.hasBlobValue()) {
|
||||||
|
dataSet.putBlob(keyValue.getKey(), keyValue.getBlobValue().toByteArray());
|
||||||
|
} else if (keyValue.hasBooleanValue()) {
|
||||||
|
dataSet.putBoolean(keyValue.getKey(), keyValue.getBooleanValue());
|
||||||
|
} else if (keyValue.hasFloatValue()) {
|
||||||
|
dataSet.putFloat(keyValue.getKey(), keyValue.getFloatValue());
|
||||||
|
} else if (keyValue.hasIntegerValue()) {
|
||||||
|
dataSet.putInteger(keyValue.getKey(), keyValue.getIntegerValue());
|
||||||
|
} else if (keyValue.hasLongValue()) {
|
||||||
|
dataSet.putLong(keyValue.getKey(), keyValue.getLongValue());
|
||||||
|
} else if (keyValue.hasStringValue()) {
|
||||||
|
dataSet.putString(keyValue.getKey(), keyValue.getStringValue());
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Unknown KeyValue backup value, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).writeDataSet(dataSet, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
@SuppressLint("ApplySharedPref")
|
||||||
private static void processPreference(@NonNull Context context, SharedPreference preference) {
|
private static void processPreference(@NonNull Context context, SharedPreference preference) {
|
||||||
SharedPreferences preferences = context.getSharedPreferences(preference.getFile(), 0);
|
SharedPreferences preferences = context.getSharedPreferences(preference.getFile(), 0);
|
||||||
preferences.edit().putString(preference.getKey(), preference.getValue()).commit();
|
|
||||||
|
if (preference.hasValue()) {
|
||||||
|
preferences.edit().putString(preference.getKey(), preference.getValue()).commit();
|
||||||
|
} else if (preference.hasBooleanValue()) {
|
||||||
|
preferences.edit().putBoolean(preference.getKey(), preference.getBooleanValue()).commit();
|
||||||
|
} else if (preference.hasIsStringSetValue() && preference.getIsStringSetValue()) {
|
||||||
|
preferences.edit().putStringSet(preference.getKey(), new HashSet<>(preference.getStringSetValueList())).commit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dropAllTables(@NonNull SQLiteDatabase db) {
|
private static void dropAllTables(@NonNull SQLiteDatabase db) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.content.Intent;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ViewSwitcher;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
@@ -18,7 +17,7 @@ import com.google.android.material.snackbar.Snackbar;
|
|||||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
import org.thoughtcrime.securesms.components.ContactFilterView;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
@@ -47,27 +46,26 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
|||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, factory).get(BlockedUsersViewModel.class);
|
viewModel = ViewModelProviders.of(this, factory).get(BlockedUsersViewModel.class);
|
||||||
|
|
||||||
ViewSwitcher viewSwitcher = findViewById(R.id.toolbar_switcher);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
ContactFilterView contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
||||||
ContactFilterToolbar contactFilterToolbar = findViewById(R.id.filter_toolbar);
|
View container = findViewById(R.id.fragment_container);
|
||||||
View container = findViewById(R.id.fragment_container);
|
|
||||||
|
|
||||||
toolbar.setNavigationOnClickListener(unused -> onBackPressed());
|
toolbar.setNavigationOnClickListener(unused -> onBackPressed());
|
||||||
contactFilterToolbar.setNavigationOnClickListener(unused -> onBackPressed());
|
contactFilterView.setOnFilterChangedListener(query -> {
|
||||||
contactFilterToolbar.setOnFilterChangedListener(query -> {
|
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(CONTACT_SELECTION_FRAGMENT);
|
Fragment fragment = getSupportFragmentManager().findFragmentByTag(CONTACT_SELECTION_FRAGMENT);
|
||||||
if (fragment != null) {
|
if (fragment != null) {
|
||||||
((ContactSelectionListFragment) fragment).setQueryFilter(query);
|
((ContactSelectionListFragment) fragment).setQueryFilter(query);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
contactFilterToolbar.setHint(R.string.BlockedUsersActivity__add_blocked_user);
|
contactFilterView.setHint(R.string.BlockedUsersActivity__add_blocked_user);
|
||||||
|
|
||||||
//noinspection CodeBlock2Expr
|
//noinspection CodeBlock2Expr
|
||||||
getSupportFragmentManager().addOnBackStackChangedListener(() -> {
|
getSupportFragmentManager().addOnBackStackChangedListener(() -> {
|
||||||
viewSwitcher.setDisplayedChild(getSupportFragmentManager().getBackStackEntryCount());
|
|
||||||
|
|
||||||
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
||||||
contactFilterToolbar.focusAndShowKeyboard();
|
contactFilterView.setVisibility(View.VISIBLE);
|
||||||
|
contactFilterView.focusAndShowKeyboard();
|
||||||
|
} else {
|
||||||
|
contactFilterView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,13 +117,15 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectionChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleAddUserToBlockedList() {
|
public void handleAddUserToBlockedList() {
|
||||||
ContactSelectionListFragment fragment = new ContactSelectionListFragment();
|
ContactSelectionListFragment fragment = new ContactSelectionListFragment();
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
|
|
||||||
fragment.setOnContactSelectedListener(this);
|
|
||||||
|
|
||||||
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
||||||
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, 1);
|
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, 1);
|
||||||
intent.putExtra(ContactSelectionListFragment.HIDE_COUNT, true);
|
intent.putExtra(ContactSelectionListFragment.HIDE_COUNT, true);
|
||||||
@@ -166,6 +166,6 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
|||||||
throw new IllegalArgumentException("Unsupported event type " + event);
|
throw new IllegalArgumentException("Unsupported event type " + event);
|
||||||
}
|
}
|
||||||
|
|
||||||
Snackbar.make(view, getString(messageResId, displayName), Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(view, getString(messageResId, displayName), Snackbar.LENGTH_SHORT).setTextColor(Color.WHITE).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,14 +91,14 @@ public enum MaterialColor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @ColorRes int toQuoteBarColorResource(@NonNull Context context, boolean outgoing) {
|
public @ColorRes int toQuoteBarColorResource(@NonNull Context context, boolean outgoing) {
|
||||||
if (outgoing) {
|
if (!outgoing) {
|
||||||
return isDarkTheme(context) ? tintColor : shadeColor ;
|
return isDarkTheme(context) ? tintColor : shadeColor ;
|
||||||
}
|
}
|
||||||
return R.color.core_white;
|
return R.color.core_white;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @ColorInt int toQuoteBackgroundColor(@NonNull Context context, boolean outgoing) {
|
public @ColorInt int toQuoteBackgroundColor(@NonNull Context context, boolean outgoing) {
|
||||||
if (outgoing) {
|
if (!outgoing) {
|
||||||
int color = toConversationColor(context);
|
int color = toConversationColor(context);
|
||||||
int alpha = isDarkTheme(context) ? (int) (0.2 * 255) : (int) (0.4 * 255);
|
int alpha = isDarkTheme(context) ? (int) (0.2 * 255) : (int) (0.4 * 255);
|
||||||
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||||
@@ -108,7 +108,7 @@ public enum MaterialColor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @ColorInt int toQuoteFooterColor(@NonNull Context context, boolean outgoing) {
|
public @ColorInt int toQuoteFooterColor(@NonNull Context context, boolean outgoing) {
|
||||||
if (outgoing) {
|
if (!outgoing) {
|
||||||
int color = toConversationColor(context);
|
int color = toConversationColor(context);
|
||||||
int alpha = isDarkTheme(context) ? (int) (0.4 * 255) : (int) (0.6 * 255);
|
int alpha = isDarkTheme(context) ? (int) (0.4 * 255) : (int) (0.6 * 255);
|
||||||
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import android.view.View;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
public class AlertView extends LinearLayout {
|
public class AlertView extends LinearLayout {
|
||||||
|
|
||||||
private static final String TAG = AlertView.class.getSimpleName();
|
private static final String TAG = Log.tag(AlertView.class);
|
||||||
|
|
||||||
private ImageView approvalIndicator;
|
private ImageView approvalIndicator;
|
||||||
private ImageView failedIndicator;
|
private ImageView failedIndicator;
|
||||||
@@ -68,4 +69,10 @@ public class AlertView extends LinearLayout {
|
|||||||
approvalIndicator.setVisibility(View.GONE);
|
approvalIndicator.setVisibility(View.GONE);
|
||||||
failedIndicator.setVisibility(View.VISIBLE);
|
failedIndicator.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRateLimited() {
|
||||||
|
this.setVisibility(View.VISIBLE);
|
||||||
|
approvalIndicator.setVisibility(View.VISIBLE);
|
||||||
|
failedIndicator.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
|
|||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.audio.AudioWaveForm;
|
import org.thoughtcrime.securesms.audio.AudioWaveForm;
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
||||||
@@ -42,19 +43,23 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
public final class AudioView extends FrameLayout {
|
public final class AudioView extends FrameLayout {
|
||||||
|
|
||||||
private static final String TAG = AudioView.class.getSimpleName();
|
private static final String TAG = Log.tag(AudioView.class);
|
||||||
|
|
||||||
|
private static final int MODE_NORMAL = 0;
|
||||||
|
private static final int MODE_SMALL = 1;
|
||||||
|
private static final int MODE_DRAFT = 2;
|
||||||
|
|
||||||
private static final int FORWARDS = 1;
|
private static final int FORWARDS = 1;
|
||||||
private static final int REVERSE = -1;
|
private static final int REVERSE = -1;
|
||||||
|
|
||||||
@NonNull private final AnimatingToggle controlToggle;
|
@NonNull private final AnimatingToggle controlToggle;
|
||||||
@NonNull private final View progressAndPlay;
|
@NonNull private final View progressAndPlay;
|
||||||
@NonNull private final LottieAnimationView playPauseButton;
|
@NonNull private final LottieAnimationView playPauseButton;
|
||||||
@NonNull private final ImageView downloadButton;
|
@NonNull private final ImageView downloadButton;
|
||||||
@NonNull private final ProgressWheel circleProgress;
|
@Nullable private final ProgressWheel circleProgress;
|
||||||
@NonNull private final SeekBar seekBar;
|
@NonNull private final SeekBar seekBar;
|
||||||
private final boolean smallView;
|
private final boolean smallView;
|
||||||
private final boolean autoRewind;
|
private final boolean autoRewind;
|
||||||
|
|
||||||
@Nullable private final TextView duration;
|
@Nullable private final TextView duration;
|
||||||
|
|
||||||
@@ -86,10 +91,23 @@ public final class AudioView extends FrameLayout {
|
|||||||
try {
|
try {
|
||||||
typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AudioView, 0, 0);
|
typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AudioView, 0, 0);
|
||||||
|
|
||||||
smallView = typedArray.getBoolean(R.styleable.AudioView_small, false);
|
int mode = typedArray.getInteger(R.styleable.AudioView_audioView_mode, MODE_NORMAL);
|
||||||
|
smallView = mode == MODE_SMALL;
|
||||||
autoRewind = typedArray.getBoolean(R.styleable.AudioView_autoRewind, false);
|
autoRewind = typedArray.getBoolean(R.styleable.AudioView_autoRewind, false);
|
||||||
|
|
||||||
inflate(context, smallView ? R.layout.audio_view_small : R.layout.audio_view, this);
|
switch (mode) {
|
||||||
|
case MODE_NORMAL:
|
||||||
|
inflate(context, R.layout.audio_view, this);
|
||||||
|
break;
|
||||||
|
case MODE_SMALL:
|
||||||
|
inflate(context, R.layout.audio_view_small, this);
|
||||||
|
break;
|
||||||
|
case MODE_DRAFT:
|
||||||
|
inflate(context, R.layout.audio_view_draft, this);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unsupported mode: " + mode);
|
||||||
|
}
|
||||||
|
|
||||||
this.controlToggle = findViewById(R.id.control_toggle);
|
this.controlToggle = findViewById(R.id.control_toggle);
|
||||||
this.playPauseButton = findViewById(R.id.play);
|
this.playPauseButton = findViewById(R.id.play);
|
||||||
@@ -109,7 +127,7 @@ public final class AudioView extends FrameLayout {
|
|||||||
this.waveFormUnplayedBarsColor = typedArray.getColor(R.styleable.AudioView_waveformUnplayedBarsColor, Color.WHITE);
|
this.waveFormUnplayedBarsColor = typedArray.getColor(R.styleable.AudioView_waveformUnplayedBarsColor, Color.WHITE);
|
||||||
this.waveFormThumbTint = typedArray.getColor(R.styleable.AudioView_waveformThumbTint, Color.WHITE);
|
this.waveFormThumbTint = typedArray.getColor(R.styleable.AudioView_waveformThumbTint, Color.WHITE);
|
||||||
|
|
||||||
progressAndPlay.getBackground().setColorFilter(typedArray.getColor(R.styleable.AudioView_progressAndPlayTint, Color.BLACK), PorterDuff.Mode.SRC_IN);
|
setProgressAndPlayBackgroundTint(typedArray.getColor(R.styleable.AudioView_progressAndPlayTint, Color.BLACK));
|
||||||
} finally {
|
} finally {
|
||||||
if (typedArray != null) {
|
if (typedArray != null) {
|
||||||
typedArray.recycle();
|
typedArray.recycle();
|
||||||
@@ -129,6 +147,10 @@ public final class AudioView extends FrameLayout {
|
|||||||
EventBus.getDefault().unregister(this);
|
EventBus.getDefault().unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setProgressAndPlayBackgroundTint(@ColorInt int color) {
|
||||||
|
progressAndPlay.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||||
|
}
|
||||||
|
|
||||||
public Observer<VoiceNotePlaybackState> getPlaybackStateObserver() {
|
public Observer<VoiceNotePlaybackState> getPlaybackStateObserver() {
|
||||||
return playbackStateObserver;
|
return playbackStateObserver;
|
||||||
}
|
}
|
||||||
@@ -157,16 +179,20 @@ public final class AudioView extends FrameLayout {
|
|||||||
controlToggle.displayQuick(downloadButton);
|
controlToggle.displayQuick(downloadButton);
|
||||||
seekBar.setEnabled(false);
|
seekBar.setEnabled(false);
|
||||||
downloadButton.setOnClickListener(new DownloadClickedListener(audio));
|
downloadButton.setOnClickListener(new DownloadClickedListener(audio));
|
||||||
if (circleProgress.isSpinning()) circleProgress.stopSpinning();
|
if (circleProgress != null) {
|
||||||
circleProgress.setVisibility(View.GONE);
|
if (circleProgress.isSpinning()) circleProgress.stopSpinning();
|
||||||
|
circleProgress.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
} else if (showControls && audio.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
|
} else if (showControls && audio.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
|
||||||
controlToggle.displayQuick(progressAndPlay);
|
controlToggle.displayQuick(progressAndPlay);
|
||||||
seekBar.setEnabled(false);
|
seekBar.setEnabled(false);
|
||||||
circleProgress.setVisibility(View.VISIBLE);
|
if (circleProgress != null) {
|
||||||
circleProgress.spin();
|
circleProgress.setVisibility(View.VISIBLE);
|
||||||
|
circleProgress.spin();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
seekBar.setEnabled(true);
|
seekBar.setEnabled(true);
|
||||||
if (circleProgress.isSpinning()) circleProgress.stopSpinning();
|
if (circleProgress != null && circleProgress.isSpinning()) circleProgress.stopSpinning();
|
||||||
showPlayButton();
|
showPlayButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,10 +236,11 @@ public final class AudioView extends FrameLayout {
|
|||||||
|
|
||||||
private void onPlaybackState(@NonNull VoiceNotePlaybackState voiceNotePlaybackState) {
|
private void onPlaybackState(@NonNull VoiceNotePlaybackState voiceNotePlaybackState) {
|
||||||
onDuration(voiceNotePlaybackState.getUri(), voiceNotePlaybackState.getTrackDuration());
|
onDuration(voiceNotePlaybackState.getUri(), voiceNotePlaybackState.getTrackDuration());
|
||||||
onStart(voiceNotePlaybackState.getUri(), voiceNotePlaybackState.isAutoReset());
|
|
||||||
onProgress(voiceNotePlaybackState.getUri(),
|
onProgress(voiceNotePlaybackState.getUri(),
|
||||||
(double) voiceNotePlaybackState.getPlayheadPositionMillis() / voiceNotePlaybackState.getTrackDuration(),
|
(double) voiceNotePlaybackState.getPlayheadPositionMillis() / voiceNotePlaybackState.getTrackDuration(),
|
||||||
voiceNotePlaybackState.getPlayheadPositionMillis());
|
voiceNotePlaybackState.getPlayheadPositionMillis());
|
||||||
|
onSpeedChanged(voiceNotePlaybackState.getUri(), voiceNotePlaybackState.getSpeed());
|
||||||
|
onStart(voiceNotePlaybackState.getUri(), voiceNotePlaybackState.isPlaying(), voiceNotePlaybackState.isAutoReset());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDuration(@NonNull Uri uri, long durationMillis) {
|
private void onDuration(@NonNull Uri uri, long durationMillis) {
|
||||||
@@ -222,8 +249,8 @@ public final class AudioView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onStart(@NonNull Uri uri, boolean autoReset) {
|
private void onStart(@NonNull Uri uri, boolean statePlaying, boolean autoReset) {
|
||||||
if (!isTarget(uri)) {
|
if (!isTarget(uri) || !statePlaying) {
|
||||||
if (hasAudioUri()) {
|
if (hasAudioUri()) {
|
||||||
onStop(audioSlide.getUri(), autoReset);
|
onStop(audioSlide.getUri(), autoReset);
|
||||||
}
|
}
|
||||||
@@ -273,6 +300,12 @@ public final class AudioView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onSpeedChanged(@NonNull Uri uri, float speed) {
|
||||||
|
if (callbacks != null) {
|
||||||
|
callbacks.onSpeedChanged(speed, isTarget(uri));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isTarget(@NonNull Uri uri) {
|
private boolean isTarget(@NonNull Uri uri) {
|
||||||
return hasAudioUri() && Objects.equals(uri, audioSlide.getUri());
|
return hasAudioUri() && Objects.equals(uri, audioSlide.getUri());
|
||||||
}
|
}
|
||||||
@@ -317,7 +350,7 @@ public final class AudioView extends FrameLayout {
|
|||||||
duration.setText(getResources().getString(R.string.AudioView_duration, remainingSecs / 60, remainingSecs % 60));
|
duration.setText(getResources().getString(R.string.AudioView_duration, remainingSecs / 60, remainingSecs % 60));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smallView) {
|
if (smallView && circleProgress != null) {
|
||||||
circleProgress.setInstantProgress(seekBar.getProgress() == 0 ? 1 : progress);
|
circleProgress.setInstantProgress(seekBar.getProgress() == 0 ? 1 : progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +361,10 @@ public final class AudioView extends FrameLayout {
|
|||||||
new LottieValueCallback<>(new SimpleColorFilter(foregroundTint))));
|
new LottieValueCallback<>(new SimpleColorFilter(foregroundTint))));
|
||||||
|
|
||||||
this.downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
|
this.downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
|
||||||
this.circleProgress.setBarColor(foregroundTint);
|
|
||||||
|
if (circleProgress != null) {
|
||||||
|
this.circleProgress.setBarColor(foregroundTint);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.duration != null) {
|
if (this.duration != null) {
|
||||||
this.duration.setTextColor(foregroundTint);
|
this.duration.setTextColor(foregroundTint);
|
||||||
@@ -371,11 +407,14 @@ public final class AudioView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showPlayButton() {
|
private void showPlayButton() {
|
||||||
if (!smallView) {
|
if (circleProgress != null) {
|
||||||
circleProgress.setVisibility(GONE);
|
if (!smallView) {
|
||||||
} else if (seekBar.getProgress() == 0) {
|
circleProgress.setVisibility(GONE);
|
||||||
circleProgress.setInstantProgress(1);
|
} else if (seekBar.getProgress() == 0) {
|
||||||
|
circleProgress.setInstantProgress(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playPauseButton.setVisibility(VISIBLE);
|
playPauseButton.setVisibility(VISIBLE);
|
||||||
controlToggle.displayQuick(progressAndPlay);
|
controlToggle.displayQuick(progressAndPlay);
|
||||||
}
|
}
|
||||||
@@ -450,6 +489,8 @@ public final class AudioView extends FrameLayout {
|
|||||||
if (callbacks != null) {
|
if (callbacks != null) {
|
||||||
if (wasPlaying) {
|
if (wasPlaying) {
|
||||||
callbacks.onSeekTo(audioSlide.getUri(), getProgress());
|
callbacks.onSeekTo(audioSlide.getUri(), getProgress());
|
||||||
|
} else {
|
||||||
|
callbacks.onProgressUpdated(durationMillis, Math.round(durationMillis * getProgress()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,7 +505,7 @@ public final class AudioView extends FrameLayout {
|
|||||||
|
|
||||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
public void onEventAsync(final PartProgressEvent event) {
|
public void onEventAsync(final PartProgressEvent event) {
|
||||||
if (audioSlide != null && event.attachment.equals(audioSlide.asAttachment())) {
|
if (audioSlide != null && circleProgress != null && event.attachment.equals(audioSlide.asAttachment())) {
|
||||||
circleProgress.setInstantProgress(((float) event.progress) / event.total);
|
circleProgress.setInstantProgress(((float) event.progress) / event.total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,6 +515,7 @@ public final class AudioView extends FrameLayout {
|
|||||||
void onPause(@NonNull Uri audioUri);
|
void onPause(@NonNull Uri audioUri);
|
||||||
void onSeekTo(@NonNull Uri audioUri, double progress);
|
void onSeekTo(@NonNull Uri audioUri, double progress);
|
||||||
void onStopAndReset(@NonNull Uri audioUri);
|
void onStopAndReset(@NonNull Uri audioUri);
|
||||||
|
void onSpeedChanged(float speed, boolean isPlaying);
|
||||||
void onProgressUpdated(long durationMillis, long playheadMillis);
|
void onProgressUpdated(long durationMillis, long playheadMillis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,35 +2,51 @@ package org.thoughtcrime.securesms.components;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.Px;
|
||||||
import androidx.appcompat.widget.AppCompatImageView;
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.MultiTransformation;
|
||||||
|
import com.bumptech.glide.load.Transformation;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.CircleCrop;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
|
||||||
|
import com.bumptech.glide.request.target.SimpleTarget;
|
||||||
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||||
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.groups.ui.managegroup.ManageGroupActivity;
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
||||||
|
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequest;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
|
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
|
||||||
import org.thoughtcrime.securesms.recipients.ui.managerecipient.ManageRecipientActivity;
|
|
||||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.BlurTransformation;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class AvatarImageView extends AppCompatImageView {
|
public final class AvatarImageView extends AppCompatImageView {
|
||||||
@@ -39,7 +55,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
private static final int SIZE_SMALL = 2;
|
private static final int SIZE_SMALL = 2;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = AvatarImageView.class.getSimpleName();
|
private static final String TAG = Log.tag(AvatarImageView.class);
|
||||||
|
|
||||||
private static final Paint LIGHT_THEME_OUTLINE_PAINT = new Paint();
|
private static final Paint LIGHT_THEME_OUTLINE_PAINT = new Paint();
|
||||||
private static final Paint DARK_THEME_OUTLINE_PAINT = new Paint();
|
private static final Paint DARK_THEME_OUTLINE_PAINT = new Paint();
|
||||||
@@ -61,6 +77,9 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
private Paint outlinePaint;
|
private Paint outlinePaint;
|
||||||
private OnClickListener listener;
|
private OnClickListener listener;
|
||||||
private Recipient.FallbackPhotoProvider fallbackPhotoProvider;
|
private Recipient.FallbackPhotoProvider fallbackPhotoProvider;
|
||||||
|
private boolean blurred;
|
||||||
|
private ChatColors chatColors;
|
||||||
|
private FixedSizeTarget fixedSizeTarget;
|
||||||
|
|
||||||
private @Nullable RecipientContactPhoto recipientContactPhoto;
|
private @Nullable RecipientContactPhoto recipientContactPhoto;
|
||||||
private @NonNull Drawable unknownRecipientDrawable;
|
private @NonNull Drawable unknownRecipientDrawable;
|
||||||
@@ -80,23 +99,30 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
|
|
||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0);
|
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0);
|
||||||
inverted = typedArray.getBoolean(R.styleable.AvatarImageView_inverted, false);
|
inverted = typedArray.getBoolean(R.styleable.AvatarImageView_inverted, false);
|
||||||
size = typedArray.getInt(R.styleable.AvatarImageView_fallbackImageSize, SIZE_LARGE);
|
size = typedArray.getInt(R.styleable.AvatarImageView_fallbackImageSize, SIZE_LARGE);
|
||||||
typedArray.recycle();
|
typedArray.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
outlinePaint = ThemeUtil.isDarkTheme(getContext()) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
|
outlinePaint = ThemeUtil.isDarkTheme(context) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
|
||||||
|
|
||||||
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted);
|
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20).asDrawable(context, AvatarColor.UNKNOWN.colorInt(), inverted);
|
||||||
|
blurred = false;
|
||||||
|
chatColors = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClipBounds(Rect clipBounds) {
|
||||||
|
super.setClipBounds(clipBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
|
|
||||||
float width = getWidth() - getPaddingRight() - getPaddingLeft();
|
float width = getWidth() - getPaddingRight() - getPaddingLeft();
|
||||||
float height = getHeight() - getPaddingBottom() - getPaddingTop();
|
float height = getHeight() - getPaddingBottom() - getPaddingTop();
|
||||||
float cx = width / 2f;
|
float cx = width / 2f;
|
||||||
float cy = height / 2f;
|
float cy = height / 2f;
|
||||||
float radius = Math.min(cx, cy) - (outlinePaint.getStrokeWidth() / 2f);
|
float radius = Math.min(cx, cy) - (outlinePaint.getStrokeWidth() / 2f);
|
||||||
|
|
||||||
@@ -118,14 +144,25 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
* Shows self as the actual profile picture.
|
* Shows self as the actual profile picture.
|
||||||
*/
|
*/
|
||||||
public void setRecipient(@NonNull Recipient recipient) {
|
public void setRecipient(@NonNull Recipient recipient) {
|
||||||
|
setRecipient(recipient, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows self as the actual profile picture.
|
||||||
|
*/
|
||||||
|
public void setRecipient(@NonNull Recipient recipient, boolean quickContactEnabled) {
|
||||||
if (recipient.isSelf()) {
|
if (recipient.isSelf()) {
|
||||||
setAvatar(GlideApp.with(this), null, false);
|
setAvatar(GlideApp.with(this), null, quickContactEnabled);
|
||||||
AvatarUtil.loadIconIntoImageView(recipient, this);
|
AvatarUtil.loadIconIntoImageView(recipient, this);
|
||||||
} else {
|
} else {
|
||||||
setAvatar(GlideApp.with(this), recipient, false);
|
setAvatar(GlideApp.with(this), recipient, quickContactEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AvatarOptions.Builder buildOptions() {
|
||||||
|
return new AvatarOptions.Builder(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows self as the note to self icon.
|
* Shows self as the note to self icon.
|
||||||
*/
|
*/
|
||||||
@@ -145,44 +182,78 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled, boolean useSelfProfileAvatar) {
|
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled, boolean useSelfProfileAvatar) {
|
||||||
if (recipient != null) {
|
setAvatar(requestManager, recipient, new AvatarOptions.Builder(this)
|
||||||
RecipientContactPhoto photo = (recipient.isSelf() && useSelfProfileAvatar) ? new RecipientContactPhoto(recipient,
|
.withUseSelfProfileAvatar(useSelfProfileAvatar)
|
||||||
new ProfileContactPhoto(Recipient.self(),
|
.withQuickContactEnabled(quickContactEnabled)
|
||||||
Recipient.self().getProfileAvatar()))
|
.build());
|
||||||
: new RecipientContactPhoto(recipient);
|
}
|
||||||
|
|
||||||
if (!photo.equals(recipientContactPhoto)) {
|
private void setAvatar(@Nullable Recipient recipient, @NonNull AvatarOptions avatarOptions) {
|
||||||
|
setAvatar(GlideApp.with(this), recipient, avatarOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, @NonNull AvatarOptions avatarOptions) {
|
||||||
|
if (recipient != null) {
|
||||||
|
RecipientContactPhoto photo = (recipient.isSelf() && avatarOptions.useSelfProfileAvatar) ? new RecipientContactPhoto(recipient,
|
||||||
|
new ProfileContactPhoto(Recipient.self(),
|
||||||
|
Recipient.self().getProfileAvatar()))
|
||||||
|
: new RecipientContactPhoto(recipient);
|
||||||
|
|
||||||
|
boolean shouldBlur = recipient.shouldBlurAvatar();
|
||||||
|
ChatColors chatColors = recipient.getChatColors();
|
||||||
|
|
||||||
|
if (!photo.equals(recipientContactPhoto) || shouldBlur != blurred || !Objects.equals(chatColors, this.chatColors)) {
|
||||||
requestManager.clear(this);
|
requestManager.clear(this);
|
||||||
|
this.chatColors = chatColors;
|
||||||
recipientContactPhoto = photo;
|
recipientContactPhoto = photo;
|
||||||
|
|
||||||
Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL
|
Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL ? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider)
|
||||||
? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider)
|
: photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider);
|
||||||
: photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider);
|
|
||||||
|
if (fixedSizeTarget != null) {
|
||||||
|
requestManager.clear(fixedSizeTarget);
|
||||||
|
}
|
||||||
|
|
||||||
if (photo.contactPhoto != null) {
|
if (photo.contactPhoto != null) {
|
||||||
requestManager.load(photo.contactPhoto)
|
|
||||||
.fallback(fallbackContactPhotoDrawable)
|
List<Transformation<Bitmap>> transforms = new ArrayList<>();
|
||||||
.error(fallbackContactPhotoDrawable)
|
if (shouldBlur) {
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
transforms.add(new BlurTransformation(ApplicationDependencies.getApplication(), 0.25f, BlurTransformation.MAX_RADIUS));
|
||||||
.circleCrop()
|
}
|
||||||
.into(this);
|
transforms.add(new CircleCrop());
|
||||||
|
blurred = shouldBlur;
|
||||||
|
|
||||||
|
GlideRequest<Drawable> request = requestManager.load(photo.contactPhoto)
|
||||||
|
.fallback(fallbackContactPhotoDrawable)
|
||||||
|
.error(fallbackContactPhotoDrawable)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
|
.downsample(DownsampleStrategy.CENTER_INSIDE)
|
||||||
|
.transform(new MultiTransformation<>(transforms));
|
||||||
|
|
||||||
|
if (avatarOptions.fixedSize > 0) {
|
||||||
|
fixedSizeTarget = new FixedSizeTarget(avatarOptions.fixedSize);
|
||||||
|
request.into(fixedSizeTarget);
|
||||||
|
} else {
|
||||||
|
request.into(this);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
setImageDrawable(fallbackContactPhotoDrawable);
|
setImageDrawable(fallbackContactPhotoDrawable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setAvatarClickHandler(recipient, quickContactEnabled);
|
setAvatarClickHandler(recipient, avatarOptions.quickContactEnabled);
|
||||||
} else {
|
} else {
|
||||||
recipientContactPhoto = null;
|
recipientContactPhoto = null;
|
||||||
requestManager.clear(this);
|
requestManager.clear(this);
|
||||||
if (fallbackPhotoProvider != null) {
|
if (fallbackPhotoProvider != null) {
|
||||||
setImageDrawable(fallbackPhotoProvider.getPhotoForRecipientWithoutName()
|
setImageDrawable(fallbackPhotoProvider.getPhotoForRecipientWithoutName()
|
||||||
.asDrawable(getContext(), MaterialColor.STEEL.toAvatarColor(getContext()), inverted));
|
.asDrawable(getContext(), AvatarColor.UNKNOWN.colorInt(), inverted));
|
||||||
} else {
|
} else {
|
||||||
setImageDrawable(unknownRecipientDrawable);
|
setImageDrawable(unknownRecipientDrawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setOnClickListener(listener);
|
disableQuickContact();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,31 +262,30 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
super.setOnClickListener(v -> {
|
super.setOnClickListener(v -> {
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
if (recipient.isPushGroup()) {
|
if (recipient.isPushGroup()) {
|
||||||
context.startActivity(ManageGroupActivity.newIntent(context, recipient.requireGroupId().requirePush()),
|
context.startActivity(ConversationSettingsActivity.forGroup(context, recipient.requireGroupId().requirePush()),
|
||||||
ManageGroupActivity.createTransitionBundle(context, this));
|
ConversationSettingsActivity.createTransitionBundle(context, this));
|
||||||
} else {
|
} else {
|
||||||
if (context instanceof FragmentActivity) {
|
if (context instanceof FragmentActivity) {
|
||||||
RecipientBottomSheetDialogFragment.create(recipient.getId(), null)
|
RecipientBottomSheetDialogFragment.create(recipient.getId(), null)
|
||||||
.show(((FragmentActivity) context).getSupportFragmentManager(), "BOTTOM");
|
.show(((FragmentActivity) context).getSupportFragmentManager(), "BOTTOM");
|
||||||
} else {
|
} else {
|
||||||
context.startActivity(ManageRecipientActivity.newIntent(context, recipient.getId()),
|
context.startActivity(ConversationSettingsActivity.forRecipient(context, recipient.getId()),
|
||||||
ManageRecipientActivity.createTransitionBundle(context, this));
|
ConversationSettingsActivity.createTransitionBundle(context, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
super.setOnClickListener(listener);
|
disableQuickContact();
|
||||||
setClickable(listener != null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImageBytesForGroup(@Nullable byte[] avatarBytes,
|
public void setImageBytesForGroup(@Nullable byte[] avatarBytes,
|
||||||
@Nullable Recipient.FallbackPhotoProvider fallbackPhotoProvider,
|
@Nullable Recipient.FallbackPhotoProvider fallbackPhotoProvider,
|
||||||
@NonNull MaterialColor color)
|
@NonNull AvatarColor color)
|
||||||
{
|
{
|
||||||
Drawable fallback = Util.firstNonNull(fallbackPhotoProvider, Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER)
|
Drawable fallback = Util.firstNonNull(fallbackPhotoProvider, Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER)
|
||||||
.getPhotoForGroup()
|
.getPhotoForGroup()
|
||||||
.asDrawable(getContext(), color.toAvatarColor(getContext()));
|
.asDrawable(getContext(), color.colorInt());
|
||||||
|
|
||||||
GlideApp.with(this)
|
GlideApp.with(this)
|
||||||
.load(avatarBytes)
|
.load(avatarBytes)
|
||||||
@@ -226,6 +296,16 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
.into(this);
|
.into(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNonAvatarImageResource(@DrawableRes int imageResource) {
|
||||||
|
recipientContactPhoto = null;
|
||||||
|
setImageResource(imageResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableQuickContact() {
|
||||||
|
super.setOnClickListener(listener);
|
||||||
|
setClickable(listener != null);
|
||||||
|
}
|
||||||
|
|
||||||
private static class RecipientContactPhoto {
|
private static class RecipientContactPhoto {
|
||||||
|
|
||||||
private final @NonNull Recipient recipient;
|
private final @NonNull Recipient recipient;
|
||||||
@@ -246,9 +326,70 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
if (other == null) return false;
|
if (other == null) return false;
|
||||||
|
|
||||||
return other.recipient.equals(recipient) &&
|
return other.recipient.equals(recipient) &&
|
||||||
other.recipient.getColor().equals(recipient.getColor()) &&
|
other.recipient.getChatColors().equals(recipient.getChatColors()) &&
|
||||||
other.ready == ready &&
|
other.ready == ready &&
|
||||||
Objects.equals(other.contactPhoto, contactPhoto);
|
Objects.equals(other.contactPhoto, contactPhoto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class FixedSizeTarget extends SimpleTarget<Drawable> {
|
||||||
|
|
||||||
|
FixedSizeTarget(int size) {
|
||||||
|
super(size, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
|
||||||
|
setImageDrawable(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class AvatarOptions {
|
||||||
|
|
||||||
|
private final boolean quickContactEnabled;
|
||||||
|
private final boolean useSelfProfileAvatar;
|
||||||
|
private final int fixedSize;
|
||||||
|
|
||||||
|
private AvatarOptions(@NonNull Builder builder) {
|
||||||
|
this.quickContactEnabled = builder.quickContactEnabled;
|
||||||
|
this.useSelfProfileAvatar = builder.useSelfProfileAvatar;
|
||||||
|
this.fixedSize = builder.fixedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
private final AvatarImageView avatarImageView;
|
||||||
|
|
||||||
|
private boolean quickContactEnabled = false;
|
||||||
|
private boolean useSelfProfileAvatar = false;
|
||||||
|
private int fixedSize = -1;
|
||||||
|
|
||||||
|
private Builder(@NonNull AvatarImageView avatarImageView) {
|
||||||
|
this.avatarImageView = avatarImageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull Builder withQuickContactEnabled(boolean quickContactEnabled) {
|
||||||
|
this.quickContactEnabled = quickContactEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull Builder withUseSelfProfileAvatar(boolean useSelfProfileAvatar) {
|
||||||
|
this.useSelfProfileAvatar = useSelfProfileAvatar;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull Builder withFixedSize(@Px @IntRange(from = 1) int fixedSize) {
|
||||||
|
this.fixedSize = fixedSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvatarOptions build() {
|
||||||
|
return new AvatarOptions(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(@Nullable Recipient recipient) {
|
||||||
|
avatarImageView.setAvatar(recipient, build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,11 +35,13 @@ import org.thoughtcrime.securesms.components.mention.MentionDeleter;
|
|||||||
import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
|
import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
|
||||||
import org.thoughtcrime.securesms.components.mention.MentionValidatorWatcher;
|
import org.thoughtcrime.securesms.components.mention.MentionValidatorWatcher;
|
||||||
import org.thoughtcrime.securesms.database.model.Mention;
|
import org.thoughtcrime.securesms.database.model.Mention;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.StringUtil;
|
import org.thoughtcrime.securesms.util.StringUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.database.MentionUtil.MENTION_STARTER;
|
import static org.thoughtcrime.securesms.database.MentionUtil.MENTION_STARTER;
|
||||||
|
|
||||||
@@ -84,13 +86,13 @@ public class ComposeText extends EmojiEditText {
|
|||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(hint)) {
|
if (getLayout() != null && !TextUtils.isEmpty(hint)) {
|
||||||
if (!TextUtils.isEmpty(subHint)) {
|
if (!TextUtils.isEmpty(subHint)) {
|
||||||
setHint(new SpannableStringBuilder().append(ellipsizeToWidth(hint))
|
setHintWithChecks(new SpannableStringBuilder().append(ellipsizeToWidth(hint))
|
||||||
.append("\n")
|
.append("\n")
|
||||||
.append(ellipsizeToWidth(subHint)));
|
.append(ellipsizeToWidth(subHint)));
|
||||||
} else {
|
} else {
|
||||||
setHint(ellipsizeToWidth(hint));
|
setHintWithChecks(ellipsizeToWidth(hint));
|
||||||
}
|
}
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
}
|
}
|
||||||
@@ -160,14 +162,14 @@ public class ComposeText extends EmojiEditText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.subHint != null) {
|
if (this.subHint != null) {
|
||||||
super.setHint(new SpannableStringBuilder().append(ellipsizeToWidth(this.hint))
|
setHintWithChecks(new SpannableStringBuilder().append(ellipsizeToWidth(this.hint))
|
||||||
.append("\n")
|
.append("\n")
|
||||||
.append(ellipsizeToWidth(this.subHint)));
|
.append(ellipsizeToWidth(this.subHint)));
|
||||||
} else {
|
} else {
|
||||||
super.setHint(ellipsizeToWidth(this.hint));
|
setHintWithChecks(ellipsizeToWidth(this.hint));
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setHint(hint);
|
setHintWithChecks(hint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void appendInvite(String invite) {
|
public void appendInvite(String invite) {
|
||||||
@@ -200,7 +202,7 @@ public class ComposeText extends EmojiEditText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setTransport(TransportOption transport) {
|
public void setTransport(TransportOption transport) {
|
||||||
final boolean useSystemEmoji = TextSecurePreferences.isSystemEmojiPreferred(getContext());
|
final boolean useSystemEmoji = SignalStore.settings().isPreferSystemEmoji();
|
||||||
|
|
||||||
int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND;
|
int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND;
|
||||||
int inputType = getInputType();
|
int inputType = getInputType();
|
||||||
@@ -224,7 +226,7 @@ public class ComposeText extends EmojiEditText {
|
|||||||
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
|
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
|
||||||
InputConnection inputConnection = super.onCreateInputConnection(editorInfo);
|
InputConnection inputConnection = super.onCreateInputConnection(editorInfo);
|
||||||
|
|
||||||
if(TextSecurePreferences.isEnterSendsEnabled(getContext())) {
|
if(SignalStore.settings().isEnterKeySends()) {
|
||||||
editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
|
editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,6 +266,14 @@ public class ComposeText extends EmojiEditText {
|
|||||||
addTextChangedListener(mentionValidatorWatcher);
|
addTextChangedListener(mentionValidatorWatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setHintWithChecks(@Nullable CharSequence newHint) {
|
||||||
|
if (getLayout() == null || Objects.equals(getHint(), newHint)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHint(newHint);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean changeSelectionForPartialMentions(@NonNull Spanned spanned, int selectionStart, int selectionEnd) {
|
private boolean changeSelectionForPartialMentions(@NonNull Spanned spanned, int selectionStart, int selectionEnd) {
|
||||||
Annotation[] annotations = spanned.getSpans(0, spanned.length(), Annotation.class);
|
Annotation[] annotations = spanned.getSpans(0, spanned.length(), Annotation.class);
|
||||||
for (Annotation annotation : annotations) {
|
for (Annotation annotation : annotations) {
|
||||||
@@ -365,7 +375,7 @@ public class ComposeText extends EmojiEditText {
|
|||||||
|
|
||||||
private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener {
|
private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener {
|
||||||
|
|
||||||
private static final String TAG = CommitContentListener.class.getSimpleName();
|
private static final String TAG = Log.tag(CommitContentListener.class);
|
||||||
|
|
||||||
private final InputPanel.MediaListener mediaListener;
|
private final InputPanel.MediaListener mediaListener;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.util.AttributeSet;
|
|||||||
import android.view.TouchDelegate;
|
import android.view.TouchDelegate;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
@@ -20,9 +21,8 @@ import androidx.core.widget.TextViewCompat;
|
|||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.views.DarkOverflowToolbar;
|
|
||||||
|
|
||||||
public final class ContactFilterToolbar extends DarkOverflowToolbar {
|
public final class ContactFilterView extends FrameLayout {
|
||||||
private OnFilterChangedListener listener;
|
private OnFilterChangedListener listener;
|
||||||
|
|
||||||
private final EditText searchText;
|
private final EditText searchText;
|
||||||
@@ -32,17 +32,17 @@ public final class ContactFilterToolbar extends DarkOverflowToolbar {
|
|||||||
private final ImageView clearToggle;
|
private final ImageView clearToggle;
|
||||||
private final LinearLayout toggleContainer;
|
private final LinearLayout toggleContainer;
|
||||||
|
|
||||||
public ContactFilterToolbar(Context context) {
|
public ContactFilterView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactFilterToolbar(Context context, AttributeSet attrs) {
|
public ContactFilterView(Context context, AttributeSet attrs) {
|
||||||
this(context, attrs, R.attr.toolbarStyle);
|
this(context, attrs, R.attr.toolbarStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactFilterToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
|
public ContactFilterView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
inflate(context, R.layout.contact_filter_toolbar, this);
|
inflate(context, R.layout.contact_filter_view, this);
|
||||||
|
|
||||||
this.searchText = findViewById(R.id.search_view);
|
this.searchText = findViewById(R.id.search_view);
|
||||||
this.toggle = findViewById(R.id.button_toggle);
|
this.toggle = findViewById(R.id.button_toggle);
|
||||||
@@ -99,8 +99,6 @@ public final class ContactFilterToolbar extends DarkOverflowToolbar {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setLogo(null);
|
|
||||||
setContentInsetStartWithNavigation(0);
|
|
||||||
expandTapArea(toggleContainer, dialpadToggle);
|
expandTapArea(toggleContainer, dialpadToggle);
|
||||||
applyAttributes(searchText, context, attrs, defStyleAttr);
|
applyAttributes(searchText, context, attrs, defStyleAttr);
|
||||||
searchText.requestFocus();
|
searchText.requestFocus();
|
||||||
@@ -4,6 +4,9 @@ import android.content.Context;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -15,6 +18,8 @@ public class ControllableTabLayout extends TabLayout {
|
|||||||
|
|
||||||
private List<View> touchables;
|
private List<View> touchables;
|
||||||
|
|
||||||
|
private NewTabListener newTabListener;
|
||||||
|
|
||||||
public ControllableTabLayout(Context context) {
|
public ControllableTabLayout(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
@@ -39,4 +44,28 @@ public class ControllableTabLayout extends TabLayout {
|
|||||||
|
|
||||||
super.setEnabled(enabled);
|
super.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNewTabListener(@Nullable NewTabListener newTabListener) {
|
||||||
|
this.newTabListener = newTabListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Tab newTab() {
|
||||||
|
Tab tab = super.newTab();
|
||||||
|
|
||||||
|
if (newTabListener != null) {
|
||||||
|
newTabListener.onNewTab(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows implementor to modify tabs when they are created, before they are added to the tab layout.
|
||||||
|
* This is useful for loading custom views, to ensure that time is not spent inflating these views
|
||||||
|
* as the user is switching between pages.
|
||||||
|
*/
|
||||||
|
public interface NewTabListener {
|
||||||
|
void onNewTab(@NonNull Tab tab);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,41 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.animation.Animator;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintSet;
|
||||||
|
|
||||||
import com.airbnb.lottie.LottieAnimationView;
|
import com.airbnb.lottie.LottieAnimationView;
|
||||||
import com.airbnb.lottie.LottieProperty;
|
import com.airbnb.lottie.LottieProperty;
|
||||||
import com.airbnb.lottie.model.KeyPath;
|
import com.airbnb.lottie.model.KeyPath;
|
||||||
|
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
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.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.Projection;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
||||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
||||||
@@ -37,17 +44,24 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class ConversationItemFooter extends LinearLayout {
|
public class ConversationItemFooter extends ConstraintLayout {
|
||||||
|
|
||||||
private TextView dateView;
|
private TextView dateView;
|
||||||
private TextView simView;
|
private TextView simView;
|
||||||
private ExpirationTimerView timerView;
|
private ExpirationTimerView timerView;
|
||||||
private ImageView insecureIndicatorView;
|
private ImageView insecureIndicatorView;
|
||||||
private DeliveryStatusView deliveryStatusView;
|
private DeliveryStatusView deliveryStatusView;
|
||||||
private boolean onlyShowSendingStatus;
|
private boolean onlyShowSendingStatus;
|
||||||
private View audioSpace;
|
private TextView audioDuration;
|
||||||
private TextView audioDuration;
|
private LottieAnimationView revealDot;
|
||||||
private LottieAnimationView revealDot;
|
private PlaybackSpeedToggleTextView playbackSpeedToggleTextView;
|
||||||
|
private boolean isOutgoing;
|
||||||
|
private boolean hasShrunkDate;
|
||||||
|
|
||||||
|
private OnTouchDelegateChangedListener onTouchDelegateChangedListener;
|
||||||
|
|
||||||
|
private final Rect speedToggleHitRect = new Rect();
|
||||||
|
private final int touchTargetSize = ViewUtil.dpToPx(48);
|
||||||
|
|
||||||
public ConversationItemFooter(Context context) {
|
public ConversationItemFooter(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -65,24 +79,55 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void init(@Nullable AttributeSet attrs) {
|
private void init(@Nullable AttributeSet attrs) {
|
||||||
inflate(getContext(), R.layout.conversation_item_footer, this);
|
final TypedArray typedArray;
|
||||||
|
|
||||||
dateView = findViewById(R.id.footer_date);
|
|
||||||
simView = findViewById(R.id.footer_sim_info);
|
|
||||||
timerView = findViewById(R.id.footer_expiration_timer);
|
|
||||||
insecureIndicatorView = findViewById(R.id.footer_insecure_indicator);
|
|
||||||
deliveryStatusView = findViewById(R.id.footer_delivery_status);
|
|
||||||
audioDuration = findViewById(R.id.footer_audio_duration);
|
|
||||||
audioSpace = findViewById(R.id.footer_audio_duration_space);
|
|
||||||
revealDot = findViewById(R.id.footer_revealed_dot);
|
|
||||||
|
|
||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemFooter, 0, 0);
|
typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemFooter, 0, 0);
|
||||||
|
} else {
|
||||||
|
typedArray = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final @LayoutRes int contentId;
|
||||||
|
if (typedArray != null) {
|
||||||
|
int mode = typedArray.getInt(R.styleable.ConversationItemFooter_footer_mode, 0);
|
||||||
|
isOutgoing = mode == 0;
|
||||||
|
|
||||||
|
if (isOutgoing) {
|
||||||
|
contentId = R.layout.conversation_item_footer_outgoing;
|
||||||
|
} else {
|
||||||
|
contentId = R.layout.conversation_item_footer_incoming;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
contentId = R.layout.conversation_item_footer_outgoing;
|
||||||
|
isOutgoing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inflate(getContext(), contentId, this);
|
||||||
|
|
||||||
|
dateView = findViewById(R.id.footer_date);
|
||||||
|
simView = findViewById(R.id.footer_sim_info);
|
||||||
|
timerView = findViewById(R.id.footer_expiration_timer);
|
||||||
|
insecureIndicatorView = findViewById(R.id.footer_insecure_indicator);
|
||||||
|
deliveryStatusView = findViewById(R.id.footer_delivery_status);
|
||||||
|
audioDuration = findViewById(R.id.footer_audio_duration);
|
||||||
|
revealDot = findViewById(R.id.footer_revealed_dot);
|
||||||
|
playbackSpeedToggleTextView = findViewById(R.id.footer_audio_playback_speed_toggle);
|
||||||
|
|
||||||
|
if (typedArray != null) {
|
||||||
setTextColor(typedArray.getInt(R.styleable.ConversationItemFooter_footer_text_color, getResources().getColor(R.color.core_white)));
|
setTextColor(typedArray.getInt(R.styleable.ConversationItemFooter_footer_text_color, getResources().getColor(R.color.core_white)));
|
||||||
setIconColor(typedArray.getInt(R.styleable.ConversationItemFooter_footer_icon_color, getResources().getColor(R.color.core_white)));
|
setIconColor(typedArray.getInt(R.styleable.ConversationItemFooter_footer_icon_color, getResources().getColor(R.color.core_white)));
|
||||||
setRevealDotColor(typedArray.getInt(R.styleable.ConversationItemFooter_footer_reveal_dot_color, getResources().getColor(R.color.core_white)));
|
setRevealDotColor(typedArray.getInt(R.styleable.ConversationItemFooter_footer_reveal_dot_color, getResources().getColor(R.color.core_white)));
|
||||||
typedArray.recycle();
|
typedArray.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dateView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||||
|
if (oldLeft != left || oldRight != right) {
|
||||||
|
notifyTouchDelegateChanged(getPlaybackSpeedToggleTouchDelegateRect(), playbackSpeedToggleTextView);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnTouchDelegateChangedListener(@Nullable OnTouchDelegateChangedListener onTouchDelegateChangedListener) {
|
||||||
|
this.onTouchDelegateChangedListener = onTouchDelegateChangedListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -97,7 +142,7 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
presentTimer(messageRecord);
|
presentTimer(messageRecord);
|
||||||
presentInsecureIndicator(messageRecord);
|
presentInsecureIndicator(messageRecord);
|
||||||
presentDeliveryStatus(messageRecord);
|
presentDeliveryStatus(messageRecord);
|
||||||
hideAudioDurationViews();
|
presentAudioDuration(messageRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAudioDuration(long totalDurationMillis, long currentPostionMillis) {
|
public void setAudioDuration(long totalDurationMillis, long currentPostionMillis) {
|
||||||
@@ -105,6 +150,20 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
audioDuration.setText(getResources().getString(R.string.AudioView_duration, remainingSecs / 60, remainingSecs % 60));
|
audioDuration.setText(getResources().getString(R.string.AudioView_duration, remainingSecs / 60, remainingSecs % 60));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPlaybackSpeedListener(@Nullable PlaybackSpeedToggleTextView.PlaybackSpeedListener playbackSpeedListener) {
|
||||||
|
playbackSpeedToggleTextView.setPlaybackSpeedListener(playbackSpeedListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAudioPlaybackSpeed(float playbackSpeed, boolean isPlaying) {
|
||||||
|
if (isPlaying) {
|
||||||
|
showPlaybackSpeedToggle();
|
||||||
|
} else {
|
||||||
|
hidePlaybackSpeedToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
playbackSpeedToggleTextView.setCurrentSpeed(playbackSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
public void setTextColor(int color) {
|
public void setTextColor(int color) {
|
||||||
dateView.setTextColor(color);
|
dateView.setTextColor(color);
|
||||||
simView.setTextColor(color);
|
simView.setTextColor(color);
|
||||||
@@ -121,7 +180,7 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
revealDot.addValueCallback(
|
revealDot.addValueCallback(
|
||||||
new KeyPath("**"),
|
new KeyPath("**"),
|
||||||
LottieProperty.COLOR_FILTER,
|
LottieProperty.COLOR_FILTER,
|
||||||
frameInfo -> new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
|
frameInfo -> new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +189,106 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
presentDeliveryStatus(messageRecord);
|
presentDeliveryStatus(messageRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void enableBubbleBackground(@DrawableRes int drawableRes, @Nullable Integer tint) {
|
||||||
|
setBackgroundResource(drawableRes);
|
||||||
|
|
||||||
|
if (tint != null) {
|
||||||
|
getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY);
|
||||||
|
} else {
|
||||||
|
getBackground().clearColorFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableBubbleBackground() {
|
||||||
|
setBackground(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Projection getProjection() {
|
||||||
|
if (getVisibility() == VISIBLE) {
|
||||||
|
return Projection.relativeToViewRoot(this, new Projection.Corners(ViewUtil.dpToPx(11)));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyTouchDelegateChanged(@NonNull Rect rect, @NonNull View touchDelegate) {
|
||||||
|
if (onTouchDelegateChangedListener != null) {
|
||||||
|
onTouchDelegateChangedListener.onTouchDelegateChanged(rect, touchDelegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPlaybackSpeedToggle() {
|
||||||
|
if (hasShrunkDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasShrunkDate = true;
|
||||||
|
|
||||||
|
playbackSpeedToggleTextView.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.scaleX(1f)
|
||||||
|
.scaleY(1f)
|
||||||
|
.setDuration(150L)
|
||||||
|
.setListener(new AnimationCompleteListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
playbackSpeedToggleTextView.setClickable(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isOutgoing) {
|
||||||
|
dateView.setMaxWidth(ViewUtil.dpToPx(28));
|
||||||
|
} else {
|
||||||
|
ConstraintSet constraintSet = new ConstraintSet();
|
||||||
|
constraintSet.clone(this);
|
||||||
|
constraintSet.constrainMaxWidth(R.id.date_and_expiry_wrapper, ViewUtil.dpToPx(40));
|
||||||
|
constraintSet.applyTo(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hidePlaybackSpeedToggle() {
|
||||||
|
if (!hasShrunkDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasShrunkDate = false;
|
||||||
|
|
||||||
|
playbackSpeedToggleTextView.animate()
|
||||||
|
.alpha(0f)
|
||||||
|
.scaleX(0.5f)
|
||||||
|
.scaleY(0.5f)
|
||||||
|
.setDuration(150L).setListener(new AnimationCompleteListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
playbackSpeedToggleTextView.setClickable(false);
|
||||||
|
playbackSpeedToggleTextView.clearRequestedSpeed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isOutgoing) {
|
||||||
|
dateView.setMaxWidth(Integer.MAX_VALUE);
|
||||||
|
} else {
|
||||||
|
ConstraintSet constraintSet = new ConstraintSet();
|
||||||
|
constraintSet.clone(this);
|
||||||
|
constraintSet.constrainMaxWidth(R.id.date_and_expiry_wrapper, -1);
|
||||||
|
constraintSet.applyTo(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull Rect getPlaybackSpeedToggleTouchDelegateRect() {
|
||||||
|
playbackSpeedToggleTextView.getHitRect(speedToggleHitRect);
|
||||||
|
|
||||||
|
int widthOffset = (touchTargetSize - speedToggleHitRect.width()) / 2;
|
||||||
|
int heightOffset = (touchTargetSize - speedToggleHitRect.height()) / 2;
|
||||||
|
|
||||||
|
speedToggleHitRect.top -= heightOffset;
|
||||||
|
speedToggleHitRect.left -= widthOffset;
|
||||||
|
speedToggleHitRect.right += widthOffset;
|
||||||
|
speedToggleHitRect.bottom += heightOffset;
|
||||||
|
|
||||||
|
return speedToggleHitRect;
|
||||||
|
}
|
||||||
|
|
||||||
private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale) {
|
private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale) {
|
||||||
dateView.forceLayout();
|
dateView.forceLayout();
|
||||||
if (messageRecord.isFailed()) {
|
if (messageRecord.isFailed()) {
|
||||||
@@ -145,6 +304,8 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
dateView.setText(errorMsg);
|
dateView.setText(errorMsg);
|
||||||
} else if (messageRecord.isPendingInsecureSmsFallback()) {
|
} else if (messageRecord.isPendingInsecureSmsFallback()) {
|
||||||
dateView.setText(R.string.ConversationItem_click_to_approve_unencrypted);
|
dateView.setText(R.string.ConversationItem_click_to_approve_unencrypted);
|
||||||
|
} else if (messageRecord.isRateLimited()) {
|
||||||
|
dateView.setText(R.string.ConversationItem_send_paused);
|
||||||
} else {
|
} else {
|
||||||
dateView.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getTimestamp()));
|
dateView.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getTimestamp()));
|
||||||
}
|
}
|
||||||
@@ -162,7 +323,7 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
simView.setText(getContext().getString(R.string.ConversationItem_from_s, subscriptionInfo.get().getDisplayName()));
|
simView.setText(getContext().getString(R.string.ConversationItem_from_s, subscriptionInfo.get().getDisplayName()));
|
||||||
simView.setVisibility(View.VISIBLE);
|
simView.setVisibility(View.VISIBLE);
|
||||||
} else if (subscriptionInfo.isPresent()) {
|
} else if (subscriptionInfo.isPresent()) {
|
||||||
simView.setText(getContext().getString(R.string.ConversationItem_to_s, subscriptionInfo.get().getDisplayName()));
|
simView.setText(getContext().getString(R.string.ConversationItem_to_s, subscriptionInfo.get().getDisplayName()));
|
||||||
simView.setVisibility(View.VISIBLE);
|
simView.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
simView.setVisibility(View.GONE);
|
simView.setVisibility(View.GONE);
|
||||||
@@ -182,16 +343,16 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
this.timerView.startAnimation();
|
this.timerView.startAnimation();
|
||||||
|
|
||||||
if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= System.currentTimeMillis()) {
|
if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= System.currentTimeMillis()) {
|
||||||
ApplicationContext.getInstance(getContext()).getExpiringMessageManager().checkSchedule();
|
ApplicationDependencies.getExpiringMessageManager().checkSchedule();
|
||||||
}
|
}
|
||||||
} else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) {
|
} else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) {
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(getContext()).getExpiringMessageManager();
|
ExpiringMessageManager expirationManager = ApplicationDependencies.getExpiringMessageManager();
|
||||||
long id = messageRecord.getId();
|
long id = messageRecord.getId();
|
||||||
boolean mms = messageRecord.isMms();
|
boolean mms = messageRecord.isMms();
|
||||||
|
|
||||||
if (mms) DatabaseFactory.getMmsDatabase(getContext()).markExpireStarted(id);
|
if (mms) DatabaseFactory.getMmsDatabase(getContext()).markExpireStarted(id);
|
||||||
else DatabaseFactory.getSmsDatabase(getContext()).markExpireStarted(id);
|
else DatabaseFactory.getSmsDatabase(getContext()).markExpireStarted(id);
|
||||||
|
|
||||||
expirationManager.scheduleDeletion(id, mms, messageRecord.getExpiresIn());
|
expirationManager.scheduleDeletion(id, mms, messageRecord.getExpiresIn());
|
||||||
});
|
});
|
||||||
@@ -218,7 +379,7 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
deliveryStatusView.setNone();
|
deliveryStatusView.setNone();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!messageRecord.isOutgoing()) {
|
if (!messageRecord.isOutgoing()) {
|
||||||
deliveryStatusView.setNone();
|
deliveryStatusView.setNone();
|
||||||
} else if (messageRecord.isPending()) {
|
} else if (messageRecord.isPending()) {
|
||||||
deliveryStatusView.setPending();
|
deliveryStatusView.setPending();
|
||||||
@@ -237,12 +398,13 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord;
|
MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord;
|
||||||
|
|
||||||
if (mmsMessageRecord.getSlideDeck().getAudioSlide() != null) {
|
if (mmsMessageRecord.getSlideDeck().getAudioSlide() != null) {
|
||||||
if (messageRecord.isOutgoing()) {
|
|
||||||
moveAudioViewsForOutgoing();
|
|
||||||
} else {
|
|
||||||
moveAudioViewsForIncoming();
|
|
||||||
}
|
|
||||||
showAudioDurationViews();
|
showAudioDurationViews();
|
||||||
|
|
||||||
|
if (messageRecord.getViewedReceiptCount() > 0) {
|
||||||
|
revealDot.setProgress(1f);
|
||||||
|
} else {
|
||||||
|
revealDot.setProgress(0f);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
hideAudioDurationViews();
|
hideAudioDurationViews();
|
||||||
}
|
}
|
||||||
@@ -251,44 +413,19 @@ public class ConversationItemFooter extends LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveAudioViewsForOutgoing() {
|
|
||||||
removeView(audioSpace);
|
|
||||||
removeView(audioDuration);
|
|
||||||
removeView(revealDot);
|
|
||||||
addView(audioSpace, 0);
|
|
||||||
addView(revealDot, 0);
|
|
||||||
addView(audioDuration, 0);
|
|
||||||
|
|
||||||
int padStart = ViewUtil.dpToPx(60);
|
|
||||||
int padLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR ? padStart : 0;
|
|
||||||
int padRight = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? padStart : 0;
|
|
||||||
|
|
||||||
audioDuration.setPadding(padLeft, 0, padRight, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void moveAudioViewsForIncoming() {
|
|
||||||
removeView(audioSpace);
|
|
||||||
removeView(audioDuration);
|
|
||||||
removeView(revealDot);
|
|
||||||
addView(audioSpace);
|
|
||||||
addView(revealDot);
|
|
||||||
addView(audioDuration);
|
|
||||||
|
|
||||||
audioDuration.setPadding(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showAudioDurationViews() {
|
private void showAudioDurationViews() {
|
||||||
audioSpace.setVisibility(View.VISIBLE);
|
|
||||||
audioDuration.setVisibility(View.VISIBLE);
|
audioDuration.setVisibility(View.VISIBLE);
|
||||||
|
revealDot.setVisibility(View.VISIBLE);
|
||||||
if (FeatureFlags.viewedReceipts()) {
|
playbackSpeedToggleTextView.setVisibility(View.VISIBLE);
|
||||||
revealDot.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideAudioDurationViews() {
|
private void hideAudioDurationViews() {
|
||||||
audioSpace.setVisibility(View.GONE);
|
|
||||||
audioDuration.setVisibility(View.GONE);
|
audioDuration.setVisibility(View.GONE);
|
||||||
revealDot.setVisibility(View.GONE);
|
revealDot.setVisibility(View.GONE);
|
||||||
|
playbackSpeedToggleTextView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnTouchDelegateChangedListener {
|
||||||
|
void onTouchDelegateChanged(@NonNull Rect delegateRect, @NonNull View delegateView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
|||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||||
|
import org.thoughtcrime.securesms.util.Projection;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -32,6 +34,9 @@ public class ConversationItemThumbnail extends FrameLayout {
|
|||||||
private Outliner outliner;
|
private Outliner outliner;
|
||||||
private Outliner pulseOutliner;
|
private Outliner pulseOutliner;
|
||||||
private boolean borderless;
|
private boolean borderless;
|
||||||
|
private int[] normalBounds;
|
||||||
|
private int[] gifBounds;
|
||||||
|
private int minimumThumbnailWidth;
|
||||||
|
|
||||||
public ConversationItemThumbnail(Context context) {
|
public ConversationItemThumbnail(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -60,14 +65,30 @@ public class ConversationItemThumbnail extends FrameLayout {
|
|||||||
|
|
||||||
outliner.setColor(ContextCompat.getColor(getContext(), R.color.signal_inverse_transparent_20));
|
outliner.setColor(ContextCompat.getColor(getContext(), R.color.signal_inverse_transparent_20));
|
||||||
|
|
||||||
|
int gifWidth = ViewUtil.dpToPx(260);
|
||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0);
|
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0);
|
||||||
thumbnail.setBounds(typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minWidth, 0),
|
normalBounds = new int[]{
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxWidth, 0),
|
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minWidth, 0),
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minHeight, 0),
|
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxWidth, 0),
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxHeight, 0));
|
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minHeight, 0),
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxHeight, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
gifWidth = typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_gifWidth, gifWidth);
|
||||||
typedArray.recycle();
|
typedArray.recycle();
|
||||||
|
} else {
|
||||||
|
normalBounds = new int[]{0, 0, 0, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gifBounds = new int[]{
|
||||||
|
gifWidth,
|
||||||
|
gifWidth,
|
||||||
|
1,
|
||||||
|
Integer.MAX_VALUE
|
||||||
|
};
|
||||||
|
|
||||||
|
minimumThumbnailWidth = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SuspiciousNameCombination")
|
@SuppressWarnings("SuspiciousNameCombination")
|
||||||
@@ -88,6 +109,18 @@ public class ConversationItemThumbnail extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void hideThumbnailView() {
|
||||||
|
thumbnail.setAlpha(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showThumbnailView() {
|
||||||
|
thumbnail.setAlpha(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull Projection.Corners getCorners() {
|
||||||
|
return new Projection.Corners(cornerMask.getRadii());
|
||||||
|
}
|
||||||
|
|
||||||
public void setPulseOutliner(@NonNull Outliner outliner) {
|
public void setPulseOutliner(@NonNull Outliner outliner) {
|
||||||
this.pulseOutliner = outliner;
|
this.pulseOutliner = outliner;
|
||||||
}
|
}
|
||||||
@@ -121,6 +154,7 @@ public class ConversationItemThumbnail extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setMinimumThumbnailWidth(int width) {
|
public void setMinimumThumbnailWidth(int width) {
|
||||||
|
minimumThumbnailWidth = width;
|
||||||
thumbnail.setMinimumThumbnailWidth(width);
|
thumbnail.setMinimumThumbnailWidth(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +171,17 @@ public class ConversationItemThumbnail extends FrameLayout {
|
|||||||
boolean showControls, boolean isPreview)
|
boolean showControls, boolean isPreview)
|
||||||
{
|
{
|
||||||
if (slides.size() == 1) {
|
if (slides.size() == 1) {
|
||||||
|
Slide slide = slides.get(0);
|
||||||
|
if (slide.isVideoGif()) {
|
||||||
|
setThumbnailBounds(gifBounds);
|
||||||
|
} else {
|
||||||
|
setThumbnailBounds(normalBounds);
|
||||||
|
|
||||||
|
if (minimumThumbnailWidth != -1) {
|
||||||
|
thumbnail.setMinimumThumbnailWidth(minimumThumbnailWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
thumbnail.setVisibility(VISIBLE);
|
thumbnail.setVisibility(VISIBLE);
|
||||||
album.setVisibility(GONE);
|
album.setVisibility(GONE);
|
||||||
|
|
||||||
@@ -167,4 +212,8 @@ public class ConversationItemThumbnail extends FrameLayout {
|
|||||||
thumbnail.setDownloadClickListener(listener);
|
thumbnail.setDownloadClickListener(listener);
|
||||||
album.setDownloadClickListener(listener);
|
album.setDownloadClickListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setThumbnailBounds(@NonNull int[] bounds) {
|
||||||
|
thumbnail.setBounds(bounds[0], bounds[1], bounds[2], bounds[3]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,22 +4,31 @@ import android.content.Context;
|
|||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ConversationTypingView extends LinearLayout {
|
public class ConversationTypingView extends ConstraintLayout {
|
||||||
|
|
||||||
private AvatarImageView avatar;
|
private AvatarImageView avatar1;
|
||||||
|
private AvatarImageView avatar2;
|
||||||
|
private AvatarImageView avatar3;
|
||||||
private View bubble;
|
private View bubble;
|
||||||
private TypingIndicatorView indicator;
|
private TypingIndicatorView indicator;
|
||||||
|
private TextView typistCount;
|
||||||
|
|
||||||
public ConversationTypingView(Context context, @Nullable AttributeSet attrs) {
|
public ConversationTypingView(Context context, @Nullable AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
@@ -29,27 +38,58 @@ public class ConversationTypingView extends LinearLayout {
|
|||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
|
|
||||||
avatar = findViewById(R.id.typing_avatar);
|
avatar1 = findViewById(R.id.typing_avatar_1);
|
||||||
bubble = findViewById(R.id.typing_bubble);
|
avatar2 = findViewById(R.id.typing_avatar_2);
|
||||||
indicator = findViewById(R.id.typing_indicator);
|
avatar3 = findViewById(R.id.typing_avatar_3);
|
||||||
|
typistCount = findViewById(R.id.typing_count);
|
||||||
|
bubble = findViewById(R.id.typing_bubble);
|
||||||
|
indicator = findViewById(R.id.typing_indicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTypists(@NonNull GlideRequests glideRequests, @NonNull List<Recipient> typists, boolean isGroupThread) {
|
public void setTypists(@NonNull GlideRequests glideRequests, @NonNull List<Recipient> typists, boolean isGroupThread, boolean hasWallpaper) {
|
||||||
if (typists.isEmpty()) {
|
if (typists.isEmpty()) {
|
||||||
indicator.stopAnimation();
|
indicator.stopAnimation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Recipient typist = typists.get(0);
|
avatar1.setVisibility(GONE);
|
||||||
bubble.getBackground().setColorFilter(typist.getColor().toConversationColor(getContext()), PorterDuff.Mode.MULTIPLY);
|
avatar2.setVisibility(GONE);
|
||||||
|
avatar3.setVisibility(GONE);
|
||||||
|
typistCount.setVisibility(GONE);
|
||||||
|
|
||||||
if (isGroupThread) {
|
if (isGroupThread) {
|
||||||
avatar.setAvatar(glideRequests, typist, false);
|
presentGroupThreadAvatars(glideRequests, typists);
|
||||||
avatar.setVisibility(VISIBLE);
|
}
|
||||||
|
|
||||||
|
if (hasWallpaper) {
|
||||||
|
bubble.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.conversation_item_wallpaper_bubble_color));
|
||||||
|
typistCount.getBackground().setColorFilter(ContextCompat.getColor(getContext(), R.color.conversation_item_wallpaper_bubble_color), PorterDuff.Mode.SRC_IN);
|
||||||
} else {
|
} else {
|
||||||
avatar.setVisibility(GONE);
|
bubble.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.signal_background_secondary));
|
||||||
|
typistCount.getBackground().setColorFilter(ContextCompat.getColor(getContext(), R.color.signal_background_secondary), PorterDuff.Mode.SRC_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
indicator.startAnimation();
|
indicator.startAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void presentGroupThreadAvatars(@NonNull GlideRequests glideRequests, @NonNull List<Recipient> typists) {
|
||||||
|
avatar1.setAvatar(glideRequests, typists.get(0), typists.size() == 1);
|
||||||
|
avatar1.setVisibility(VISIBLE);
|
||||||
|
|
||||||
|
if (typists.size() > 1) {
|
||||||
|
avatar2.setAvatar(glideRequests, typists.get(1), false);
|
||||||
|
avatar2.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typists.size() == 3) {
|
||||||
|
avatar3.setAvatar(glideRequests, typists.get(2), false);
|
||||||
|
avatar3.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typists.size() > 3) {
|
||||||
|
typistCount.setText(getResources().getString(R.string.ConversationTypingView__plus_d, typists.size() - 2));
|
||||||
|
typistCount.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import android.graphics.Path;
|
|||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffXfermode;
|
import android.graphics.PorterDuffXfermode;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public class CornerMask {
|
public class CornerMask {
|
||||||
|
|
||||||
@@ -29,10 +31,7 @@ public class CornerMask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void mask(Canvas canvas) {
|
public void mask(Canvas canvas) {
|
||||||
bounds.left = 0;
|
bounds.set(canvas.getClipBounds());
|
||||||
bounds.top = 0;
|
|
||||||
bounds.right = canvas.getWidth();
|
|
||||||
bounds.bottom = canvas.getHeight();
|
|
||||||
|
|
||||||
corners.reset();
|
corners.reset();
|
||||||
corners.addRoundRect(bounds, radii, Path.Direction.CW);
|
corners.addRoundRect(bounds, radii, Path.Direction.CW);
|
||||||
@@ -57,6 +56,13 @@ public class CornerMask {
|
|||||||
radii[6] = radii[7] = bottomLeft;
|
radii[6] = radii[7] = bottomLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRadii(float topLeft, float topRight, float bottomRight, float bottomLeft) {
|
||||||
|
radii[0] = radii[1] = topLeft;
|
||||||
|
radii[2] = radii[3] = topRight;
|
||||||
|
radii[4] = radii[5] = bottomRight;
|
||||||
|
radii[6] = radii[7] = bottomLeft;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTopLeftRadius(int radius) {
|
public void setTopLeftRadius(int radius) {
|
||||||
radii[0] = radii[1] = radius;
|
radii[0] = radii[1] = radius;
|
||||||
}
|
}
|
||||||
@@ -72,4 +78,8 @@ public class CornerMask {
|
|||||||
public void setBottomLeftRadius(int radius) {
|
public void setBottomLeftRadius(int radius) {
|
||||||
radii[6] = radii[7] = radius;
|
radii[6] = radii[7] = radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float[] getRadii() {
|
||||||
|
return radii;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import java.net.URISyntaxException;
|
|||||||
|
|
||||||
public class CustomDefaultPreference extends DialogPreference {
|
public class CustomDefaultPreference extends DialogPreference {
|
||||||
|
|
||||||
private static final String TAG = CustomDefaultPreference.class.getSimpleName();
|
private static final String TAG = Log.tag(CustomDefaultPreference.class);
|
||||||
|
|
||||||
private final int inputType;
|
private final int inputType;
|
||||||
private final String customPreference;
|
private final String customPreference;
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import androidx.annotation.AttrRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom styled search view that we can insert into ActionBar menus
|
|
||||||
*/
|
|
||||||
public class DarkSearchView extends androidx.appcompat.widget.SearchView {
|
|
||||||
public DarkSearchView(@NonNull Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DarkSearchView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
||||||
this(context, attrs, R.attr.search_view_style_dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DarkSearchView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
|
|
||||||
EditText searchText = findViewById(androidx.appcompat.R.id.search_src_text);
|
|
||||||
searchText.setTextColor(ContextCompat.getColor(context, R.color.signal_text_toolbar_subtitle));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,11 +10,12 @@ import android.view.animation.RotateAnimation;
|
|||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
public class DeliveryStatusView extends FrameLayout {
|
public class DeliveryStatusView extends FrameLayout {
|
||||||
|
|
||||||
private static final String TAG = DeliveryStatusView.class.getSimpleName();
|
private static final String TAG = Log.tag(DeliveryStatusView.class);
|
||||||
|
|
||||||
private static final RotateAnimation ROTATION_ANIMATION = new RotateAnimation(0, 360f,
|
private static final RotateAnimation ROTATION_ANIMATION = new RotateAnimation(0, 360f,
|
||||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
|
|||||||
|
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||||
@@ -29,7 +30,7 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
|
|
||||||
public class DocumentView extends FrameLayout {
|
public class DocumentView extends FrameLayout {
|
||||||
|
|
||||||
private static final String TAG = DocumentView.class.getSimpleName();
|
private static final String TAG = Log.tag(DocumentView.class);
|
||||||
|
|
||||||
private final @NonNull AnimatingToggle controlToggle;
|
private final @NonNull AnimatingToggle controlToggle;
|
||||||
private final @NonNull ImageView downloadButton;
|
private final @NonNull ImageView downloadButton;
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import android.util.AttributeSet;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.signal.core.util.ThreadUtil;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -67,7 +67,7 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag
|
|||||||
else stopped = false;
|
else stopped = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Util.runOnMainDelayed(new AnimationUpdateRunnable(this), calculateAnimationDelay(this.startedAt, this.expiresIn));
|
ThreadUtil.runOnMainDelayed(new AnimationUpdateRunnable(this), calculateAnimationDelay(this.startedAt, this.expiresIn));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopAnimation() {
|
public void stopAnimation() {
|
||||||
@@ -116,7 +116,7 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn));
|
ThreadUtil.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||