mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-17 15:33:30 +01:00
Compare commits
256 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09b9349f6c | ||
|
|
aeb5a9cf57 | ||
|
|
aaf8bf3280 | ||
|
|
b6d7271858 | ||
|
|
9498a34293 | ||
|
|
0cae15b7fd | ||
|
|
11e4fd7f34 | ||
|
|
31f31534ce | ||
|
|
0e4bec3977 | ||
|
|
7fef1b060f | ||
|
|
0312dfcfcd | ||
|
|
b05f4430f6 | ||
|
|
8703707d62 | ||
|
|
04eeb434c9 | ||
|
|
a8a773db43 | ||
|
|
daf78b31b5 | ||
|
|
20ce3e68f8 | ||
|
|
92d065050f | ||
|
|
1b53f09687 | ||
|
|
f4d0bf900c | ||
|
|
c652d83f81 | ||
|
|
7167ad331f | ||
|
|
9bb089d198 | ||
|
|
866853ff99 | ||
|
|
931b9f8831 | ||
|
|
e8c10cd550 | ||
|
|
1049f8bd2f | ||
|
|
9929e6549e | ||
|
|
ff28ff0e6b | ||
|
|
2a82db2b02 | ||
|
|
457c3c0526 | ||
|
|
4f803c695b | ||
|
|
bdbdcccaff | ||
|
|
8d7393e4b5 | ||
|
|
533dcfb828 | ||
|
|
e67ac95890 | ||
|
|
1b63ed0b20 | ||
|
|
07d9e29e7c | ||
|
|
c47a724654 | ||
|
|
8ca94eb3d5 | ||
|
|
11b1c9655c | ||
|
|
cf3dd70600 | ||
|
|
0bf5f15cf9 | ||
|
|
ea3fb774f8 | ||
|
|
25c0dc801f | ||
|
|
c29922a575 | ||
|
|
e676f324f1 | ||
|
|
c6bfdeb4b0 | ||
|
|
80a6e0f781 | ||
|
|
bc7b0b40b0 | ||
|
|
1cea615675 | ||
|
|
9dd96148d1 | ||
|
|
9e094dfc2b | ||
|
|
a39b09c314 | ||
|
|
6c4c299b28 | ||
|
|
a98cc5706f | ||
|
|
7451ee1403 | ||
|
|
b9f4dc3fe9 | ||
|
|
2566d6f61f | ||
|
|
8eebdaf451 | ||
|
|
b1dacf4acd | ||
|
|
9326c1726a | ||
|
|
654b602cef | ||
|
|
a642876bda | ||
|
|
2b8041d779 | ||
|
|
8141b53c15 | ||
|
|
115d1fcf63 | ||
|
|
ffa249885e | ||
|
|
9a21f5abca | ||
|
|
552592db39 | ||
|
|
75af1b69e8 | ||
|
|
c96fec9537 | ||
|
|
a457d1f569 | ||
|
|
87f206fdc4 | ||
|
|
e845860c7c | ||
|
|
e351c74ddb | ||
|
|
aeeaef567f | ||
|
|
78a9206898 | ||
|
|
aab8bd1261 | ||
|
|
db16155b0d | ||
|
|
1b254ca185 | ||
|
|
9a6ed9bcb3 | ||
|
|
c8f0bd7b82 | ||
|
|
f6b7b9e913 | ||
|
|
840a56cbb4 | ||
|
|
aa268fc3ba | ||
|
|
889d1183b2 | ||
|
|
a8706f65d5 | ||
|
|
26bebb9811 | ||
|
|
9331e9ce89 | ||
|
|
6417f5cce0 | ||
|
|
a340ebf74a | ||
|
|
4882a4d11c | ||
|
|
b5300c877c | ||
|
|
c2b94274b0 | ||
|
|
46ec45b985 | ||
|
|
beee3b7dc3 | ||
|
|
e2a7ed86e4 | ||
|
|
95b0639ab4 | ||
|
|
d7f9582bc4 | ||
|
|
176a705079 | ||
|
|
8e9f311fca | ||
|
|
977af2c2f3 | ||
|
|
7e45fc4a3e | ||
|
|
58489bab61 | ||
|
|
0685cf4e51 | ||
|
|
9b9453734c | ||
|
|
ca0e52e141 | ||
|
|
24b7593178 | ||
|
|
993e49db48 | ||
|
|
d458ddba55 | ||
|
|
bd5747b7f6 | ||
|
|
a335130ad4 | ||
|
|
9558513190 | ||
|
|
27a3015d4f | ||
|
|
f751f9afa8 | ||
|
|
2e2b31aa79 | ||
|
|
135d002f02 | ||
|
|
a45ede9348 | ||
|
|
e4b2e5022f | ||
|
|
286010ce90 | ||
|
|
13eb89746b | ||
|
|
d2f639c57f | ||
|
|
ad587606b7 | ||
|
|
9fd5e2057d | ||
|
|
8f63b850fc | ||
|
|
199d04b663 | ||
|
|
658741be52 | ||
|
|
f1bcc756d3 | ||
|
|
cdcb1de3d4 | ||
|
|
7d11a6207a | ||
|
|
e608ad24c2 | ||
|
|
4fe382398e | ||
|
|
b6546f3ae3 | ||
|
|
4620eade58 | ||
|
|
23a328f12d | ||
|
|
83905dd6a6 | ||
|
|
3eb4eb3c09 | ||
|
|
2eba9a8d72 | ||
|
|
9b17e7a7e2 | ||
|
|
3eb9e4a035 | ||
|
|
3edc97eb38 | ||
|
|
cb0208af4d | ||
|
|
cdd311f741 | ||
|
|
8543325d59 | ||
|
|
a1a677a3e2 | ||
|
|
3705465ef2 | ||
|
|
c80999839b | ||
|
|
936212e684 | ||
|
|
1cc39fb89b | ||
|
|
37d3a953c8 | ||
|
|
5a1a23d9ac | ||
|
|
6cb359b2d0 | ||
|
|
8bd89d1e63 | ||
|
|
f111ac7cf2 | ||
|
|
f6e000ab97 | ||
|
|
29869c93b2 | ||
|
|
3aae5ce1de | ||
|
|
e379cf6127 | ||
|
|
0c23cb5ca8 | ||
|
|
d26ba27069 | ||
|
|
e918178694 | ||
|
|
3d075bdd65 | ||
|
|
4a3b8af6af | ||
|
|
2743492076 | ||
|
|
6ebc453e4b | ||
|
|
75bd950b9b | ||
|
|
0b0c4eb8c0 | ||
|
|
e7dbc874bb | ||
|
|
17426f1dbb | ||
|
|
e00ce48517 | ||
|
|
cba1caa5be | ||
|
|
5f6b073cb6 | ||
|
|
51647a5017 | ||
|
|
fae2ceab39 | ||
|
|
553346629a | ||
|
|
726f48bc33 | ||
|
|
397793064d | ||
|
|
534af3c1a0 | ||
|
|
f551a700fe | ||
|
|
d0c737779a | ||
|
|
497b38ddbf | ||
|
|
cdad45096b | ||
|
|
f8aedca08e | ||
|
|
490ca1d74c | ||
|
|
cf9ddf3960 | ||
|
|
61498037f3 | ||
|
|
1e499fd12f | ||
|
|
a9fc5622cd | ||
|
|
777a91abc7 | ||
|
|
372f939a67 | ||
|
|
716229719a | ||
|
|
b57b160660 | ||
|
|
40c52a31c9 | ||
|
|
05c16e4c70 | ||
|
|
7a7c4c28c2 | ||
|
|
8e2ab40b4c | ||
|
|
bcef73c2e0 | ||
|
|
f0a109245b | ||
|
|
c6c30f25a2 | ||
|
|
8036aaa985 | ||
|
|
a56dd5ca87 | ||
|
|
40ac0f4e89 | ||
|
|
1aa9aa97ac | ||
|
|
96a75a7f7f | ||
|
|
5009bd4e6a | ||
|
|
62ea82a2ba | ||
|
|
fa55062510 | ||
|
|
4b195c67cb | ||
|
|
f36aa09a81 | ||
|
|
e0f16548cf | ||
|
|
577971c7a9 | ||
|
|
6bbd941158 | ||
|
|
b92dd19a4c | ||
|
|
13f3a8cf8a | ||
|
|
60da8116be | ||
|
|
1b7c873ea5 | ||
|
|
b18ecfdffd | ||
|
|
da286329f7 | ||
|
|
db69603b5d | ||
|
|
a2b73bf979 | ||
|
|
dc503e3406 | ||
|
|
ab3e0b87c6 | ||
|
|
7751ce3ae0 | ||
|
|
796e98be10 | ||
|
|
9c266e7995 | ||
|
|
b4ae13fe8a | ||
|
|
8ffad4cc6f | ||
|
|
f341e02fb7 | ||
|
|
15e52a8b88 | ||
|
|
84717b95f7 | ||
|
|
b1d1e92dbb | ||
|
|
cca35ec687 | ||
|
|
95fc9d6c3c | ||
|
|
cb057968ee | ||
|
|
deed8ac6c9 | ||
|
|
fe44f8e369 | ||
|
|
e517232172 | ||
|
|
28310a88f5 | ||
|
|
3252871ed5 | ||
|
|
2740b5e300 | ||
|
|
a46faebb67 | ||
|
|
16a4c321c4 | ||
|
|
056ef84817 | ||
|
|
820d76990a | ||
|
|
01e4a7fd79 | ||
|
|
8d4f87641d | ||
|
|
afb248c57c | ||
|
|
62871c1bdd | ||
|
|
c6be427883 | ||
|
|
7873ec2b67 | ||
|
|
64e6b492ab | ||
|
|
c1cd893a4a | ||
|
|
8a1e033efa | ||
|
|
b2c974a684 | ||
|
|
57e476988e |
@@ -57,8 +57,8 @@ ktlint {
|
|||||||
version = "0.43.2"
|
version = "0.43.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 1109
|
def canonicalVersionCode = 1130
|
||||||
def canonicalVersionName = "5.46.4"
|
def canonicalVersionName = "5.51.1"
|
||||||
|
|
||||||
def postFixSize = 100
|
def postFixSize = 100
|
||||||
def abiPostFix = ['universal' : 0,
|
def abiPostFix = ['universal' : 0,
|
||||||
@@ -201,11 +201,13 @@ android {
|
|||||||
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
|
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
|
||||||
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
||||||
buildConfigField "String", "CDS_MRENCLAVE", "\"74778bb0f93ae1f78c26e67152bab0bbeb693cd56d1bb9b4e9244157acc58081\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"74778bb0f93ae1f78c26e67152bab0bbeb693cd56d1bb9b4e9244157acc58081\""
|
||||||
buildConfigField "String", "CDSI_MRENCLAVE", "\"7b75dd6e862decef9b37132d54be082441917a7790e82fe44f9cf653de03a75f\""
|
buildConfigField "String", "CDSI_MRENCLAVE", "\"ef4787a56a154ac6d009138cac17155acd23cfe4329281252365dd7c252e7fbf\""
|
||||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " +
|
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
|
||||||
"\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " +
|
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
|
||||||
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")"
|
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
|
||||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[0]"
|
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " +
|
||||||
|
"\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " +
|
||||||
|
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
|
||||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
||||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P\""
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P\""
|
||||||
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
||||||
@@ -354,10 +356,12 @@ android {
|
|||||||
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
|
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
||||||
buildConfigField "String", "CDS_MRENCLAVE", "\"74778bb0f93ae1f78c26e67152bab0bbeb693cd56d1bb9b4e9244157acc58081\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"74778bb0f93ae1f78c26e67152bab0bbeb693cd56d1bb9b4e9244157acc58081\""
|
||||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99\", " +
|
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
|
||||||
"\"4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a\", " +
|
"\"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef\", " +
|
||||||
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")"
|
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
|
||||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[0]"
|
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99\", " +
|
||||||
|
"\"4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a\", " +
|
||||||
|
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
|
||||||
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+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj\""
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj\""
|
||||||
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\""
|
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\""
|
||||||
@@ -413,10 +417,11 @@ dependencies {
|
|||||||
|
|
||||||
implementation (libs.androidx.appcompat) {
|
implementation (libs.androidx.appcompat) {
|
||||||
version {
|
version {
|
||||||
strictly '1.2.0'
|
strictly '1.5.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
implementation libs.androidx.window
|
implementation libs.androidx.window.window
|
||||||
|
implementation libs.androidx.window.java
|
||||||
implementation libs.androidx.recyclerview
|
implementation libs.androidx.recyclerview
|
||||||
implementation libs.material.material
|
implementation libs.material.material
|
||||||
implementation libs.androidx.legacy.support
|
implementation libs.androidx.legacy.support
|
||||||
@@ -429,7 +434,9 @@ dependencies {
|
|||||||
implementation libs.androidx.multidex
|
implementation libs.androidx.multidex
|
||||||
implementation libs.androidx.navigation.fragment.ktx
|
implementation libs.androidx.navigation.fragment.ktx
|
||||||
implementation libs.androidx.navigation.ui.ktx
|
implementation libs.androidx.navigation.ui.ktx
|
||||||
implementation libs.androidx.lifecycle.extensions
|
implementation libs.androidx.lifecycle.viewmodel.ktx
|
||||||
|
implementation libs.androidx.lifecycle.livedata.ktx
|
||||||
|
implementation libs.androidx.lifecycle.process
|
||||||
implementation libs.androidx.lifecycle.viewmodel.savedstate
|
implementation libs.androidx.lifecycle.viewmodel.savedstate
|
||||||
implementation libs.androidx.lifecycle.common.java8
|
implementation libs.androidx.lifecycle.common.java8
|
||||||
implementation libs.androidx.lifecycle.reactivestreams.ktx
|
implementation libs.androidx.lifecycle.reactivestreams.ktx
|
||||||
@@ -466,6 +473,7 @@ dependencies {
|
|||||||
implementation project(':donations')
|
implementation project(':donations')
|
||||||
implementation project(':contacts')
|
implementation project(':contacts')
|
||||||
implementation project(':qr')
|
implementation project(':qr')
|
||||||
|
implementation project(':sms-exporter')
|
||||||
|
|
||||||
implementation libs.libsignal.android
|
implementation libs.libsignal.android
|
||||||
implementation libs.google.protobuf.javalite
|
implementation libs.google.protobuf.javalite
|
||||||
@@ -556,6 +564,10 @@ dependencies {
|
|||||||
androidTestImplementation testLibs.mockito.kotlin
|
androidTestImplementation testLibs.mockito.kotlin
|
||||||
androidTestImplementation testLibs.square.okhttp.mockserver
|
androidTestImplementation testLibs.square.okhttp.mockserver
|
||||||
|
|
||||||
|
instrumentationImplementation (libs.androidx.fragment.testing) {
|
||||||
|
exclude group: 'androidx.test', module: 'core'
|
||||||
|
}
|
||||||
|
|
||||||
testImplementation testLibs.espresso.core
|
testImplementation testLibs.espresso.core
|
||||||
|
|
||||||
implementation libs.kotlin.stdlib.jdk8
|
implementation libs.kotlin.stdlib.jdk8
|
||||||
@@ -566,7 +578,7 @@ dependencies {
|
|||||||
implementation libs.rxjava3.rxkotlin
|
implementation libs.rxjava3.rxkotlin
|
||||||
implementation libs.rxdogtag
|
implementation libs.rxdogtag
|
||||||
|
|
||||||
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
androidTestUtil testLibs.androidx.test.orchestrator
|
||||||
}
|
}
|
||||||
|
|
||||||
def getLastCommitTimestamp() {
|
def getLastCommitTimestamp() {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.testing.success
|
|||||||
import org.thoughtcrime.securesms.testing.timeout
|
import org.thoughtcrime.securesms.testing.timeout
|
||||||
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import org.whispersystems.signalservice.internal.push.MismatchedDevices
|
||||||
import org.whispersystems.signalservice.internal.push.PreKeyState
|
import org.whispersystems.signalservice.internal.push.PreKeyState
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -249,6 +250,109 @@ class ChangeNumberViewModelTest {
|
|||||||
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
|
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
|
||||||
|
// GIVEN
|
||||||
|
val aci = Recipient.self().requireServiceId()
|
||||||
|
val newPni = ServiceId.from(UUID.randomUUID())
|
||||||
|
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||||
|
lateinit var setPreKeysRequest: PreKeyState
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
|
||||||
|
Put("/v1/accounts/number") { r ->
|
||||||
|
changeNumberRequest = r.parsedRequestBody()
|
||||||
|
if (changeNumberRequest.deviceMessages.isEmpty()) {
|
||||||
|
MockResponse().failure(
|
||||||
|
409,
|
||||||
|
MismatchedDevices().apply {
|
||||||
|
missingDevices = listOf(2)
|
||||||
|
extraDevices = emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Get("/v2/keys/$aci/2") {
|
||||||
|
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
|
||||||
|
},
|
||||||
|
Put("/v2/keys") { r ->
|
||||||
|
setPreKeysRequest = r.parsedRequestBody()
|
||||||
|
MockResponse().success()
|
||||||
|
},
|
||||||
|
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
|
||||||
|
// GIVEN
|
||||||
|
val aci = Recipient.self().requireServiceId()
|
||||||
|
val newPni = ServiceId.from(UUID.randomUUID())
|
||||||
|
|
||||||
|
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||||
|
lateinit var setPreKeysRequest: PreKeyState
|
||||||
|
|
||||||
|
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Put("/v1/accounts/number") { r ->
|
||||||
|
changeNumberRequest = r.parsedRequestBody()
|
||||||
|
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
|
||||||
|
MockResponse().failure(423, MockProvider.lockedFailure)
|
||||||
|
} else if (changeNumberRequest.deviceMessages.isEmpty()) {
|
||||||
|
MockResponse().failure(
|
||||||
|
409,
|
||||||
|
MismatchedDevices().apply {
|
||||||
|
missingDevices = listOf(2)
|
||||||
|
extraDevices = emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if (changeNumberRequest.deviceMessages.size == 1) {
|
||||||
|
MockResponse().failure(
|
||||||
|
409,
|
||||||
|
MismatchedDevices().apply {
|
||||||
|
missingDevices = listOf(2, 3)
|
||||||
|
extraDevices = emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Get("/v2/keys/$aci/2") {
|
||||||
|
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
|
||||||
|
},
|
||||||
|
Get("/v2/keys/$aci/3") {
|
||||||
|
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 3))
|
||||||
|
},
|
||||||
|
Put("/v2/keys") { r ->
|
||||||
|
setPreKeysRequest = r.parsedRequestBody()
|
||||||
|
MockResponse().success()
|
||||||
|
},
|
||||||
|
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
|
||||||
|
processor.registrationLock() assertIs true
|
||||||
|
Recipient.self().requirePni() assertIsNot newPni
|
||||||
|
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
|
||||||
|
}
|
||||||
|
|
||||||
private fun assertSuccess(newPni: ServiceId, changeNumberRequest: ChangePhoneNumberRequest, setPreKeysRequest: PreKeyState) {
|
private fun assertSuccess(newPni: ServiceId, changeNumberRequest: ChangePhoneNumberRequest, setPreKeysRequest: PreKeyState) {
|
||||||
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
|
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
|
||||||
val pniMetadataStore = SignalStore.account().pniPreKeys
|
val pniMetadataStore = SignalStore.account().pniPreKeys
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.junit.Assert.assertFalse
|
|||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||||
@@ -116,6 +117,7 @@ class MmsDatabaseTest_stories {
|
|||||||
assertTrue(messageAfterMark.incomingStoryViewedAtTimestamp > 0)
|
assertTrue(messageAfterMark.incomingStoryViewedAtTimestamp > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
fun given5ViewedStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectLatestViewedFirst() {
|
fun given5ViewedStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectLatestViewedFirst() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
@@ -257,12 +259,13 @@ class MmsDatabaseTest_stories {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
val result = mms.hasSelfReplyInGroupStory(groupStoryId)
|
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertFalse(result)
|
assertFalse(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
fun givenAGroupStoryWithAReplyFromSelf_whenICheckHasSelfReplyInGroupStory_thenIExpectTrue() {
|
fun givenAGroupStoryWithAReplyFromSelf_whenICheckHasSelfReplyInGroupStory_thenIExpectTrue() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
@@ -281,7 +284,7 @@ class MmsDatabaseTest_stories {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
val result = mms.hasSelfReplyInGroupStory(groupStoryId)
|
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertTrue(result)
|
assertTrue(result)
|
||||||
@@ -306,7 +309,7 @@ class MmsDatabaseTest_stories {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
val result = mms.hasSelfReplyInGroupStory(groupStoryId)
|
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertFalse(result)
|
assertFalse(result)
|
||||||
@@ -334,7 +337,7 @@ class MmsDatabaseTest_stories {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
val result = mms.hasSelfReplyInGroupStory(groupStoryId)
|
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertFalse(result)
|
assertFalse(result)
|
||||||
|
|||||||
@@ -18,6 +18,77 @@ class RecipientDatabaseTest {
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
val harness = SignalActivityRule()
|
val harness = SignalActivityRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||||
|
val hiddenRecipient = harness.others[0]
|
||||||
|
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||||
|
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||||
|
|
||||||
|
val results = SignalDatabase.recipients.queryAllContacts("Hidden")!!
|
||||||
|
|
||||||
|
assertEquals(0, results.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAHiddenRecipient_whenIGetSignalContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||||
|
val hiddenRecipient = harness.others[0]
|
||||||
|
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||||
|
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||||
|
|
||||||
|
val results: MutableList<RecipientId> = SignalDatabase.recipients.getSignalContacts(false)?.use {
|
||||||
|
val ids = mutableListOf<RecipientId>()
|
||||||
|
while (it.moveToNext()) {
|
||||||
|
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientDatabase.ID)))
|
||||||
|
}
|
||||||
|
|
||||||
|
ids
|
||||||
|
}!!
|
||||||
|
|
||||||
|
assertNotEquals(0, results.size)
|
||||||
|
assertFalse(hiddenRecipient in results)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAHiddenRecipient_whenIQuerySignalContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||||
|
val hiddenRecipient = harness.others[0]
|
||||||
|
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||||
|
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||||
|
|
||||||
|
val results = SignalDatabase.recipients.querySignalContacts("Hidden", false)!!
|
||||||
|
|
||||||
|
assertEquals(0, results.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAHiddenRecipient_whenIQueryNonGroupContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||||
|
val hiddenRecipient = harness.others[0]
|
||||||
|
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||||
|
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||||
|
|
||||||
|
val results = SignalDatabase.recipients.queryNonGroupContacts("Hidden", false)!!
|
||||||
|
|
||||||
|
assertEquals(0, results.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenAHiddenRecipient_whenIGetNonGroupContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||||
|
val hiddenRecipient = harness.others[0]
|
||||||
|
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||||
|
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||||
|
|
||||||
|
val results: MutableList<RecipientId> = SignalDatabase.recipients.getNonGroupContacts(false)?.use {
|
||||||
|
val ids = mutableListOf<RecipientId>()
|
||||||
|
while (it.moveToNext()) {
|
||||||
|
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientDatabase.ID)))
|
||||||
|
}
|
||||||
|
|
||||||
|
ids
|
||||||
|
}!!
|
||||||
|
|
||||||
|
assertNotEquals(0, results.size)
|
||||||
|
assertFalse(hiddenRecipient in results)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun givenABlockedRecipient_whenIQueryAllContacts_thenIDoNotExpectBlockedToBeReturned() {
|
fun givenABlockedRecipient_whenIQueryAllContacts_thenIDoNotExpectBlockedToBeReturned() {
|
||||||
val blockedRecipient = harness.others[0]
|
val blockedRecipient = harness.others[0]
|
||||||
|
|||||||
@@ -60,13 +60,6 @@ class RecipientDatabaseTest_processPnpTuple {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException::class)
|
|
||||||
fun noMatch_pniOnly() {
|
|
||||||
test {
|
|
||||||
process(null, PNI_A, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalStateException::class)
|
@Test(expected = IllegalStateException::class)
|
||||||
fun noMatch_noData() {
|
fun noMatch_noData() {
|
||||||
test {
|
test {
|
||||||
@@ -417,7 +410,7 @@ class RecipientDatabaseTest_processPnpTuple {
|
|||||||
fun process(e164: String?, pni: PNI?, aci: ACI?) {
|
fun process(e164: String?, pni: PNI?, aci: ACI?) {
|
||||||
SignalDatabase.rawDatabase.beginTransaction()
|
SignalDatabase.rawDatabase.beginTransaction()
|
||||||
try {
|
try {
|
||||||
generatedIds += recipientDatabase.processPnpTuple(e164, pni, aci, pniVerified = false, pnpEnabled = true).finalId
|
generatedIds += recipientDatabase.processPnpTuple(e164, pni, aci, pniVerified = false).finalId
|
||||||
SignalDatabase.rawDatabase.setTransactionSuccessful()
|
SignalDatabase.rawDatabase.setTransactionSuccessful()
|
||||||
} finally {
|
} finally {
|
||||||
SignalDatabase.rawDatabase.endTransaction()
|
SignalDatabase.rawDatabase.endTransaction()
|
||||||
|
|||||||
@@ -67,11 +67,6 @@ class RecipientDatabaseTest_processPnpTupleToChangeSet {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException::class)
|
|
||||||
fun noMatch_pniOnly() {
|
|
||||||
db.processPnpTupleToChangeSet(null, PNI_A, null, pniVerified = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalStateException::class)
|
@Test(expected = IllegalStateException::class)
|
||||||
fun noMatch_noData() {
|
fun noMatch_noData() {
|
||||||
db.processPnpTupleToChangeSet(null, null, null, pniVerified = false)
|
db.processPnpTupleToChangeSet(null, null, null, pniVerified = false)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class MyStoryMigrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun runMigration() {
|
private fun runMigration() {
|
||||||
MyStoryMigration.migrate(
|
V151_MyStoryMigration.migrate(
|
||||||
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application,
|
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application,
|
||||||
SignalDatabase.rawDatabase,
|
SignalDatabase.rawDatabase,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
|
|||||||
serviceNetworkAccessMock = mock {
|
serviceNetworkAccessMock = mock {
|
||||||
on { getConfiguration() } doReturn uncensoredConfiguration
|
on { getConfiguration() } doReturn uncensoredConfiguration
|
||||||
on { getConfiguration(any()) } doReturn uncensoredConfiguration
|
on { getConfiguration(any()) } doReturn uncensoredConfiguration
|
||||||
|
on { uncensoredConfiguration } doReturn uncensoredConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
keyBackupService = mock()
|
keyBackupService = mock()
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.signal.libsignal.protocol.ecc.Curve
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
|
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
import org.thoughtcrime.securesms.testing.Get
|
||||||
|
import org.thoughtcrime.securesms.testing.Put
|
||||||
|
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||||
|
import org.thoughtcrime.securesms.testing.assertIs
|
||||||
|
import org.thoughtcrime.securesms.testing.assertIsNot
|
||||||
|
import org.thoughtcrime.securesms.testing.parsedRequestBody
|
||||||
|
import org.thoughtcrime.securesms.testing.success
|
||||||
|
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
|
||||||
|
import org.whispersystems.signalservice.internal.push.PreKeyState
|
||||||
|
import org.whispersystems.signalservice.internal.push.PreKeyStatus
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class PreKeysSyncJobTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val harness = SignalActivityRule()
|
||||||
|
|
||||||
|
private val aciPreKeyMeta: PreKeyMetadataStore
|
||||||
|
get() = SignalStore.account().aciPreKeys
|
||||||
|
|
||||||
|
private val pniPreKeyMeta: PreKeyMetadataStore
|
||||||
|
get() = SignalStore.account().pniPreKeys
|
||||||
|
|
||||||
|
private lateinit var job: PreKeysSyncJob
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
job = PreKeysSyncJob()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
InstrumentationApplicationDependencyProvider.clearHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create signed prekeys for both identities when both do not have registered prekeys according
|
||||||
|
* to our local state.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun runWithoutRegisteredKeysForBothIdentities() {
|
||||||
|
// GIVEN
|
||||||
|
aciPreKeyMeta.isSignedPreKeyRegistered = false
|
||||||
|
pniPreKeyMeta.isSignedPreKeyRegistered = false
|
||||||
|
|
||||||
|
lateinit var aciSignedPreKey: SignedPreKeyEntity
|
||||||
|
lateinit var pniSignedPreKey: SignedPreKeyEntity
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Put("/v2/keys/signed?identity=aci") { r ->
|
||||||
|
aciSignedPreKey = r.parsedRequestBody()
|
||||||
|
MockResponse().success()
|
||||||
|
},
|
||||||
|
Put("/v2/keys/signed?identity=pni") { r ->
|
||||||
|
pniSignedPreKey = r.parsedRequestBody()
|
||||||
|
MockResponse().success()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result: Job.Result = job.run()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
result.isSuccess assertIs true
|
||||||
|
|
||||||
|
aciPreKeyMeta.isSignedPreKeyRegistered assertIs true
|
||||||
|
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
|
||||||
|
|
||||||
|
val aciVerifySignatureResult = Curve.verifySignature(
|
||||||
|
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.publicKey,
|
||||||
|
aciSignedPreKey.publicKey.serialize(),
|
||||||
|
aciSignedPreKey.signature
|
||||||
|
)
|
||||||
|
aciVerifySignatureResult assertIs true
|
||||||
|
|
||||||
|
val pniVerifySignatureResult = Curve.verifySignature(
|
||||||
|
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.publicKey,
|
||||||
|
pniSignedPreKey.publicKey.serialize(),
|
||||||
|
pniSignedPreKey.signature
|
||||||
|
)
|
||||||
|
pniVerifySignatureResult assertIs true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With 100 prekeys registered for each identity, do nothing.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun runWithRegisteredKeysForBothIdentities() {
|
||||||
|
// GIVEN
|
||||||
|
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
|
||||||
|
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
|
||||||
|
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) },
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result: Job.Result = job.run()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
result.isSuccess assertIs true
|
||||||
|
|
||||||
|
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
|
||||||
|
pniPreKeyMeta.activeSignedPreKeyId assertIs currentPniKeyId
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With 100 prekeys registered for ACI, but no PNI prekeys registered according to local state,
|
||||||
|
* do nothing for ACI but create PNI prekeys and update local state.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun runWithRegisteredKeysForAciIdentityOnly() {
|
||||||
|
// GIVEN
|
||||||
|
pniPreKeyMeta.isSignedPreKeyRegistered = false
|
||||||
|
|
||||||
|
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
|
||||||
|
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
|
||||||
|
Put("/v2/keys/signed?identity=pni") { MockResponse().success() },
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result: Job.Result = job.run()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
result.isSuccess assertIs true
|
||||||
|
|
||||||
|
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
|
||||||
|
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
|
||||||
|
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With <10 prekeys registered for each identity, upload new.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun runWithLowNumberOfRegisteredKeysForBothIdentities() {
|
||||||
|
// GIVEN
|
||||||
|
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
|
||||||
|
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
|
||||||
|
|
||||||
|
val currentNextAciPreKeyId = aciPreKeyMeta.nextOneTimePreKeyId
|
||||||
|
val currentNextPniPreKeyId = pniPreKeyMeta.nextOneTimePreKeyId
|
||||||
|
|
||||||
|
lateinit var aciPreKeyStateRequest: PreKeyState
|
||||||
|
lateinit var pniPreKeyStateRequest: PreKeyState
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(5)) },
|
||||||
|
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(5)) },
|
||||||
|
Put("/v2/keys/?identity=aci") { r ->
|
||||||
|
aciPreKeyStateRequest = r.parsedRequestBody()
|
||||||
|
MockResponse().success()
|
||||||
|
},
|
||||||
|
Put("/v2/keys/?identity=pni") { r ->
|
||||||
|
pniPreKeyStateRequest = r.parsedRequestBody()
|
||||||
|
MockResponse().success()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result: Job.Result = job.run()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
result.isSuccess assertIs true
|
||||||
|
aciPreKeyMeta.activeSignedPreKeyId assertIsNot currentAciKeyId
|
||||||
|
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
|
||||||
|
|
||||||
|
aciPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextAciPreKeyId
|
||||||
|
pniPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextPniPreKeyId
|
||||||
|
|
||||||
|
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.let { aciIdentityKey ->
|
||||||
|
aciPreKeyStateRequest.identityKey assertIs aciIdentityKey
|
||||||
|
|
||||||
|
val verifySignatureResult = Curve.verifySignature(
|
||||||
|
aciIdentityKey.publicKey,
|
||||||
|
aciPreKeyStateRequest.signedPreKey.publicKey.serialize(),
|
||||||
|
aciPreKeyStateRequest.signedPreKey.signature
|
||||||
|
)
|
||||||
|
verifySignatureResult assertIs true
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.let { pniIdentityKey ->
|
||||||
|
pniPreKeyStateRequest.identityKey assertIs pniIdentityKey
|
||||||
|
|
||||||
|
val verifySignatureResult = Curve.verifySignature(
|
||||||
|
pniIdentityKey.publicKey,
|
||||||
|
pniPreKeyStateRequest.signedPreKey.publicKey.serialize(),
|
||||||
|
pniPreKeyStateRequest.signedPreKey.signature
|
||||||
|
)
|
||||||
|
verifySignatureResult assertIs true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package org.thoughtcrime.securesms.profiles.manage
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.fragment.app.testing.FragmentScenario
|
||||||
|
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
|
||||||
|
import androidx.test.espresso.action.ViewActions.typeText
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isNotEnabled
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.reactivex.rxjava3.schedulers.TestScheduler
|
||||||
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
|
||||||
|
import org.thoughtcrime.securesms.testing.Put
|
||||||
|
import org.thoughtcrime.securesms.testing.RxTestSchedulerRule
|
||||||
|
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||||
|
import org.thoughtcrime.securesms.testing.assertIsNotNull
|
||||||
|
import org.thoughtcrime.securesms.testing.assertIsNull
|
||||||
|
import org.thoughtcrime.securesms.testing.success
|
||||||
|
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class UsernameEditFragmentTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val harness = SignalActivityRule(othersCount = 10)
|
||||||
|
|
||||||
|
private val ioScheduler = TestScheduler()
|
||||||
|
private val computationScheduler = TestScheduler()
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val testSchedulerRule = RxTestSchedulerRule(
|
||||||
|
ioTestScheduler = ioScheduler,
|
||||||
|
computationTestScheduler = computationScheduler
|
||||||
|
)
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
InstrumentationApplicationDependencyProvider.clearHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUsernameCreationInRegistration() {
|
||||||
|
val scenario = createScenario(true)
|
||||||
|
|
||||||
|
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
|
onView(withId(R.id.toolbar)).check { view, noViewFoundException ->
|
||||||
|
noViewFoundException.assertIsNull()
|
||||||
|
val toolbar = view as Toolbar
|
||||||
|
|
||||||
|
toolbar.navigationIcon.assertIsNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
onView(withText(R.string.UsernameEditFragment__add_a_username)).check(matches(isDisplayed()))
|
||||||
|
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUsernameCreationOutsideOfRegistration() {
|
||||||
|
val scenario = createScenario()
|
||||||
|
|
||||||
|
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
|
onView(withId(R.id.toolbar)).check { view, noViewFoundException ->
|
||||||
|
noViewFoundException.assertIsNull()
|
||||||
|
val toolbar = view as Toolbar
|
||||||
|
|
||||||
|
toolbar.navigationIcon.assertIsNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
onView(withText(R.string.UsernameEditFragment_username)).check(matches(isDisplayed()))
|
||||||
|
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNicknameUpdateHappyPath() {
|
||||||
|
val nickname = "Spiderman"
|
||||||
|
val discriminator = "4578"
|
||||||
|
val username = "$nickname${UsernameState.DELIMITER}$discriminator"
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Put("/v1/accounts/username/reserved") {
|
||||||
|
MockResponse().success(ReserveUsernameResponse(username, "reservationToken"))
|
||||||
|
},
|
||||||
|
Put("/v1/accounts/username/confirm") {
|
||||||
|
MockResponse().success()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val scenario = createScenario(isInRegistration = true)
|
||||||
|
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
|
onView(withId(R.id.username_text)).perform(typeText(nickname))
|
||||||
|
|
||||||
|
computationScheduler.advanceTimeBy(501, TimeUnit.MILLISECONDS)
|
||||||
|
computationScheduler.triggerActions()
|
||||||
|
|
||||||
|
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
|
||||||
|
ioScheduler.triggerActions()
|
||||||
|
computationScheduler.triggerActions()
|
||||||
|
|
||||||
|
onView(withId(R.id.username_text)).perform(closeSoftKeyboard())
|
||||||
|
onView(withId(R.id.username_done_button)).check(matches(isDisplayed()))
|
||||||
|
onView(withId(R.id.username_done_button)).check(matches(isEnabled()))
|
||||||
|
onView(withText(username)).check(matches(isDisplayed()))
|
||||||
|
|
||||||
|
onView(withId(R.id.username_done_button)).perform(click())
|
||||||
|
|
||||||
|
computationScheduler.triggerActions()
|
||||||
|
onView(withId(R.id.username_done_button)).check(matches(isNotEnabled()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createScenario(isInRegistration: Boolean = false): FragmentScenario<UsernameEditFragment> {
|
||||||
|
val fragmentArgs = UsernameEditFragmentArgs.Builder().setIsInRegistration(isInRegistration).build().toBundle()
|
||||||
|
return launchFragmentInContainer(
|
||||||
|
fragmentArgs = fragmentArgs,
|
||||||
|
themeResId = R.style.Signal_DayNight_NoActionBar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,14 @@ import org.mockito.kotlin.doReturn
|
|||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
import org.mockito.kotlin.stub
|
import org.mockito.kotlin.stub
|
||||||
import org.signal.core.util.Hex
|
import org.signal.core.util.Hex
|
||||||
|
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||||
|
import org.signal.libsignal.protocol.ecc.Curve
|
||||||
|
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||||
|
import org.signal.libsignal.protocol.util.KeyHelper
|
||||||
|
import org.signal.libsignal.protocol.util.Medium
|
||||||
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||||
import org.thoughtcrime.securesms.pin.TokenData
|
import org.thoughtcrime.securesms.pin.TokenData
|
||||||
import org.thoughtcrime.securesms.test.BuildConfig
|
import org.thoughtcrime.securesms.test.BuildConfig
|
||||||
@@ -16,10 +23,14 @@ import org.whispersystems.signalservice.api.kbs.HashedPin
|
|||||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
|
||||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
|
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
|
||||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||||
import org.whispersystems.signalservice.internal.push.DeviceInfoList
|
import org.whispersystems.signalservice.internal.push.DeviceInfoList
|
||||||
|
import org.whispersystems.signalservice.internal.push.PreKeyEntity
|
||||||
|
import org.whispersystems.signalservice.internal.push.PreKeyResponse
|
||||||
|
import org.whispersystems.signalservice.internal.push.PreKeyResponseItem
|
||||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
||||||
import org.whispersystems.signalservice.internal.push.SenderCertificate
|
import org.whispersystems.signalservice.internal.push.SenderCertificate
|
||||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
||||||
@@ -83,4 +94,21 @@ object MockProvider {
|
|||||||
on { newRegistrationSession(any(), any()) } doReturn session
|
on { newRegistrationSession(any(), any()) } doReturn session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account().aciIdentityKey, deviceId: Int): PreKeyResponse {
|
||||||
|
val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey)
|
||||||
|
val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), Curve.generateKeyPair())
|
||||||
|
|
||||||
|
val device = PreKeyResponseItem().apply {
|
||||||
|
this.deviceId = deviceId
|
||||||
|
registrationId = KeyHelper.generateRegistrationId(false)
|
||||||
|
signedPreKey = SignedPreKeyEntity(signedPreKeyRecord.id, signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature)
|
||||||
|
preKey = PreKeyEntity(oneTimePreKey.id, oneTimePreKey.keyPair.publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PreKeyResponse().apply {
|
||||||
|
identityKey = identity.publicKey
|
||||||
|
devices = listOf(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.thoughtcrime.securesms.testing
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.plugins.RxJavaPlugins
|
||||||
|
import io.reactivex.rxjava3.schedulers.TestScheduler
|
||||||
|
import org.junit.rules.ExternalResource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit Rule which initialises Rx thread schedulers. If a specific
|
||||||
|
* scheduler is not specified, it defaults to the `defaultTestScheduler`
|
||||||
|
*/
|
||||||
|
class RxTestSchedulerRule(
|
||||||
|
val defaultTestScheduler: TestScheduler = TestScheduler(),
|
||||||
|
val ioTestScheduler: TestScheduler = defaultTestScheduler,
|
||||||
|
val computationTestScheduler: TestScheduler = defaultTestScheduler,
|
||||||
|
val singleTestScheduler: TestScheduler = defaultTestScheduler,
|
||||||
|
val newThreadTestScheduler: TestScheduler = defaultTestScheduler,
|
||||||
|
) : ExternalResource() {
|
||||||
|
|
||||||
|
override fun before() {
|
||||||
|
RxJavaPlugins.setInitIoSchedulerHandler { ioTestScheduler }
|
||||||
|
RxJavaPlugins.setIoSchedulerHandler { ioTestScheduler }
|
||||||
|
|
||||||
|
RxJavaPlugins.setInitComputationSchedulerHandler { computationTestScheduler }
|
||||||
|
RxJavaPlugins.setComputationSchedulerHandler { computationTestScheduler }
|
||||||
|
|
||||||
|
RxJavaPlugins.setInitSingleSchedulerHandler { singleTestScheduler }
|
||||||
|
RxJavaPlugins.setSingleSchedulerHandler { singleTestScheduler }
|
||||||
|
|
||||||
|
RxJavaPlugins.setInitNewThreadSchedulerHandler { newThreadTestScheduler }
|
||||||
|
RxJavaPlugins.setNewThreadSchedulerHandler { newThreadTestScheduler }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after() {
|
||||||
|
RxJavaPlugins.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,7 +108,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
|
|||||||
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
||||||
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
||||||
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
||||||
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true))
|
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true))
|
||||||
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
||||||
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), IdentityKeyUtil.generateIdentityKeyPair().publicKey)
|
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), IdentityKeyUtil.generateIdentityKeyPair().publicKey)
|
||||||
others += recipientId
|
others += recipientId
|
||||||
|
|||||||
@@ -155,7 +155,8 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".DeviceProvisioningActivity"
|
<activity android:name=".DeviceProvisioningActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
@@ -181,10 +182,10 @@
|
|||||||
|
|
||||||
<activity android:name=".sharing.v2.ShareActivity"
|
<activity android:name=".sharing.v2.ShareActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:exported="true"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:exported="true"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
@@ -212,13 +213,14 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".stickers.StickerPackPreviewActivity"
|
<activity android:name=".stickers.StickerPackPreviewActivity"
|
||||||
|
android:exported="true"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:noHistory="true"
|
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>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" android:exported="true" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="sgnl"
|
<data android:scheme="sgnl"
|
||||||
@@ -255,6 +257,7 @@
|
|||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<activity android:name=".deeplinks.DeepLinkEntryActivity"
|
<activity android:name=".deeplinks.DeepLinkEntryActivity"
|
||||||
|
android:exported="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/Signal.Transparent">
|
android:theme="@style/Signal.Transparent">
|
||||||
|
|
||||||
@@ -386,10 +389,12 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".verify.VerifyIdentityActivity"
|
<activity android:name=".verify.VerifyIdentityActivity"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
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=".components.settings.app.AppSettingsActivity"
|
<activity android:name=".components.settings.app.AppSettingsActivity"
|
||||||
|
android:exported="true"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
@@ -517,10 +522,11 @@
|
|||||||
android:finishOnTaskLaunch="true" />
|
android:finishOnTaskLaunch="true" />
|
||||||
|
|
||||||
<activity android:name=".PlayServicesProblemActivity"
|
<activity android:name=".PlayServicesProblemActivity"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/TextSecure.DialogActivity"
|
android:theme="@style/TextSecure.DialogActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".SmsSendtoActivity">
|
<activity android:name=".SmsSendtoActivity" android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SENDTO" />
|
<action android:name="android.intent.action.SENDTO" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
@@ -539,6 +545,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
|
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
|
||||||
|
android:exported="true"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:permission="android.permission.CALL_PHONE"
|
android:permission="android.permission.CALL_PHONE"
|
||||||
android:theme="@style/NoAnimation.Theme.BlackScreen"
|
android:theme="@style/NoAnimation.Theme.BlackScreen"
|
||||||
@@ -554,7 +561,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".mediasend.AvatarSelectionActivity"
|
<activity android:name=".mediasend.AvatarSelectionActivity"
|
||||||
android:theme="@style/TextSecure.FullScreenMedia"
|
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".blocked.BlockedUsersActivity"
|
<activity android:name=".blocked.BlockedUsersActivity"
|
||||||
@@ -570,6 +577,10 @@
|
|||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
|
||||||
|
<activity android:name=".profiles.username.AddAUsernameActivity"
|
||||||
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
|
||||||
<activity android:name=".profiles.manage.ManageProfileActivity"
|
<activity android:name=".profiles.manage.ManageProfileActivity"
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
@@ -659,7 +670,7 @@
|
|||||||
|
|
||||||
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
|
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:theme="@style/TextSecure.FullScreenMedia" />
|
android:theme="@style/TextSecure.DarkNoActionBar" />
|
||||||
|
|
||||||
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
|
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
@@ -670,6 +681,12 @@
|
|||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".exporter.flow.SmsExportActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<service android:enabled="true" android:name=".exporter.SignalSmsExportService" android:foregroundServiceType="dataSync" />
|
||||||
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService" android:foregroundServiceType="camera|microphone"/>
|
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService" android:foregroundServiceType="camera|microphone"/>
|
||||||
<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"/>
|
||||||
@@ -682,13 +699,13 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service android:name=".components.voice.VoiceNotePlaybackService">
|
<service android:name=".components.voice.VoiceNotePlaybackService" android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.media.browse.MediaBrowserService" />
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
<receiver android:name="androidx.media.session.MediaButtonReceiver" android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -728,7 +745,7 @@
|
|||||||
|
|
||||||
<service android:name=".gcm.FcmFetchForegroundService" />
|
<service android:name=".gcm.FcmFetchForegroundService" />
|
||||||
|
|
||||||
<service android:name=".gcm.FcmReceiveService">
|
<service android:name=".gcm.FcmReceiveService" android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -819,51 +836,51 @@
|
|||||||
|
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<receiver android:name=".service.BootReceiver">
|
<receiver android:name=".service.BootReceiver" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
<action android:name="org.thoughtcrime.securesms.RESTART"/>
|
<action android:name="org.thoughtcrime.securesms.RESTART"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.DirectoryRefreshListener">
|
<receiver android:name=".service.DirectoryRefreshListener" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.RotateSignedPreKeyListener">
|
<receiver android:name=".service.RotateSignedPreKeyListener" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.RotateSenderCertificateListener">
|
<receiver android:name=".service.RotateSenderCertificateListener" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver">
|
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
|
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.LocalBackupListener">
|
<receiver android:name=".service.LocalBackupListener" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.PersistentConnectionBootListener">
|
<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".notifications.LocaleChangedReceiver">
|
<receiver android:name=".notifications.LocaleChangedReceiver" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.LOCALE_CHANGED"/>
|
<action android:name="android.intent.action.LOCALE_CHANGED"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -871,7 +888,7 @@
|
|||||||
|
|
||||||
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver"/>
|
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver"/>
|
||||||
|
|
||||||
<receiver android:name=".notifications.DeleteNotificationReceiver">
|
<receiver android:name=".notifications.DeleteNotificationReceiver" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="org.thoughtcrime.securesms.DELETE_NOTIFICATION"/>
|
<action android:name="org.thoughtcrime.securesms.DELETE_NOTIFICATION"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -899,16 +916,19 @@
|
|||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".jobmanager.KeepAliveService"
|
android:name=".jobmanager.KeepAliveService"
|
||||||
android:enabled="@bool/enable_alarm_manager" />
|
android:enabled="@bool/enable_alarm_manager"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver"
|
android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver"
|
||||||
android:enabled="@bool/enable_alarm_manager" />
|
android:enabled="@bool/enable_alarm_manager"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<!-- Probably don't need this one -->
|
<!-- Probably don't need this one -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".jobmanager.BootReceiver"
|
android:name=".jobmanager.BootReceiver"
|
||||||
android:enabled="true">
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package androidx.documentfile.provider;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Located in androidx package as {@link TreeDocumentFile} is package protected.
|
||||||
|
*/
|
||||||
|
public class DocumentFileHelper {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(DocumentFileHelper.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System implementation swallows the exception and we are having problems with the rename. This inlines the
|
||||||
|
* same call and logs the exception. Note this implementation does not update the passed in document file like
|
||||||
|
* the system implementation. Do not use the provided document file after calling this method.
|
||||||
|
*
|
||||||
|
* @return true if rename successful
|
||||||
|
*/
|
||||||
|
@RequiresApi(21)
|
||||||
|
public static boolean renameTo(Context context, DocumentFile documentFile, String displayName) {
|
||||||
|
if (documentFile instanceof TreeDocumentFile) {
|
||||||
|
Log.d(TAG, "Renaming document directly");
|
||||||
|
try {
|
||||||
|
final Uri result = DocumentsContract.renameDocument(context.getContentResolver(), documentFile.getUri(), displayName);
|
||||||
|
return result != null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "Unable to rename document file", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Letting OS rename document: " + documentFile.getClass().getSimpleName());
|
||||||
|
return documentFile.renameTo(displayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.stories.Stories;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||||
|
|
||||||
@@ -20,6 +21,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, SENDER_KEY, ANNOUNCEMENT_GROUPS, CHANGE_NUMBER, FeatureFlags.stories(), FeatureFlags.giftBadgeReceiveSupport());
|
return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION, SENDER_KEY, ANNOUNCEMENT_GROUPS, CHANGE_NUMBER, Stories.isFeatureFlagEnabled(), FeatureFlags.giftBadgeReceiveSupport(), FeatureFlags.phoneNumberPrivacy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,20 +50,21 @@ import org.thoughtcrime.securesms.emoji.EmojiSource;
|
|||||||
import org.thoughtcrime.securesms.emoji.JumboEmoji;
|
import org.thoughtcrime.securesms.emoji.JumboEmoji;
|
||||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||||
import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
|
import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
|
||||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
|
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
|
||||||
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
|
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
||||||
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
|
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
|
||||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
|
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
|
||||||
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
|
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.SubscriptionKeepAliveJob;
|
import org.thoughtcrime.securesms.jobs.SubscriptionKeepAliveJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration;
|
||||||
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.PersistentLogger;
|
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||||
@@ -180,13 +181,12 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||||
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
||||||
.addNonBlocking(this::initializeFcmCheck)
|
.addNonBlocking(this::initializeFcmCheck)
|
||||||
.addNonBlocking(CreateSignedPreKeyJob::enqueueIfNeeded)
|
.addNonBlocking(PreKeysSyncJob::enqueueIfNeeded)
|
||||||
.addNonBlocking(this::initializePeriodicTasks)
|
.addNonBlocking(this::initializePeriodicTasks)
|
||||||
.addNonBlocking(this::initializeCircumvention)
|
.addNonBlocking(this::initializeCircumvention)
|
||||||
.addNonBlocking(this::initializePendingMessages)
|
.addNonBlocking(this::initializePendingMessages)
|
||||||
.addNonBlocking(this::initializeCleanup)
|
.addNonBlocking(this::initializeCleanup)
|
||||||
.addNonBlocking(this::initializeGlideCodecs)
|
.addNonBlocking(this::initializeGlideCodecs)
|
||||||
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
|
|
||||||
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||||
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
||||||
.addNonBlocking(EmojiSource::refresh)
|
.addNonBlocking(EmojiSource::refresh)
|
||||||
@@ -196,6 +196,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
||||||
.addPostRender(this::initializeExpiringMessageManager)
|
.addPostRender(this::initializeExpiringMessageManager)
|
||||||
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
||||||
|
.addPostRender(this::initializeTrimThreadsByDateManager)
|
||||||
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
||||||
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
|
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
|
||||||
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
|
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
|
||||||
@@ -206,6 +207,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
.addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary)
|
.addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary)
|
||||||
.addPostRender(GroupV2UpdateSelfProfileKeyJob::enqueueForGroupsIfNecessary)
|
.addPostRender(GroupV2UpdateSelfProfileKeyJob::enqueueForGroupsIfNecessary)
|
||||||
.addPostRender(StoryOnboardingDownloadJob.Companion::enqueueIfNeeded)
|
.addPostRender(StoryOnboardingDownloadJob.Companion::enqueueIfNeeded)
|
||||||
|
.addPostRender(PnpInitializeDevicesJob::enqueueIfNecessary)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
@@ -385,6 +387,13 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary();
|
ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeTrimThreadsByDateManager() {
|
||||||
|
KeepMessagesDuration keepMessagesDuration = SignalStore.settings().getKeepMessagesDuration();
|
||||||
|
if (keepMessagesDuration != KeepMessagesDuration.FOREVER) {
|
||||||
|
ApplicationDependencies.getTrimThreadsByDateManager().scheduleIfNecessary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initializePeriodicTasks() {
|
private void initializePeriodicTasks() {
|
||||||
RotateSignedPreKeyListener.schedule(this);
|
RotateSignedPreKeyListener.schedule(this);
|
||||||
DirectoryRefreshListener.schedule(this);
|
DirectoryRefreshListener.schedule(this);
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package org.thoughtcrime.securesms
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.biometric.BiometricPrompt.PromptInfo
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication using phone biometric (face, fingerprint recognition) or device lock (pattern, pin or passphrase).
|
||||||
|
*/
|
||||||
|
class BiometricDeviceAuthentication(
|
||||||
|
private val biometricManager: BiometricManager,
|
||||||
|
private val biometricPrompt: BiometricPrompt,
|
||||||
|
private val biometricPromptInfo: PromptInfo
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val AUTHENTICATED = 1
|
||||||
|
const val NOT_AUTHENTICATED = -1
|
||||||
|
const val TAG: String = "BiometricDeviceAuth"
|
||||||
|
const val BIOMETRIC_AUTHENTICATORS = BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||||
|
const val ALLOWED_AUTHENTICATORS = BIOMETRIC_AUTHENTICATORS or BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
|
}
|
||||||
|
|
||||||
|
fun authenticate(context: Context, force: Boolean, showConfirmDeviceCredentialIntent: () -> Unit): Boolean {
|
||||||
|
val isKeyGuardSecure = ServiceUtil.getKeyguardManager(context).isKeyguardSecure
|
||||||
|
|
||||||
|
if (!isKeyGuardSecure) {
|
||||||
|
Log.w(TAG, "Keyguard not secure...")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (Build.VERSION.SDK_INT != 29 && biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||||
|
if (force) {
|
||||||
|
Log.i(TAG, "Listening for biometric authentication...")
|
||||||
|
biometricPrompt.authenticate(biometricPromptInfo)
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Skipping show system biometric or device lock dialog unless forced")
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
if (force) {
|
||||||
|
Log.i(TAG, "firing intent...")
|
||||||
|
showConfirmDeviceCredentialIntent()
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Skipping firing intent unless forced")
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Not compatible...")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelAuthentication() {
|
||||||
|
biometricPrompt.cancelAuthentication()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BiometricDeviceLockContract : ActivityResultContract<String, Int>() {
|
||||||
|
|
||||||
|
@RequiresApi(api = 21)
|
||||||
|
override fun createIntent(context: Context, input: String): Intent {
|
||||||
|
val keyguardManager = ServiceUtil.getKeyguardManager(context)
|
||||||
|
return keyguardManager.createConfirmDeviceCredentialIntent(input, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?) =
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
BiometricDeviceAuthentication.NOT_AUTHENTICATED
|
||||||
|
} else {
|
||||||
|
BiometricDeviceAuthentication.AUTHENTICATED
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,20 +144,20 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
private MappingAdapter contactChipAdapter;
|
private MappingAdapter contactChipAdapter;
|
||||||
private ContactChipViewModel contactChipViewModel;
|
private ContactChipViewModel contactChipViewModel;
|
||||||
private LifecycleDisposable lifecycleDisposable;
|
private LifecycleDisposable lifecycleDisposable;
|
||||||
|
|
||||||
private HeaderActionProvider headerActionProvider;
|
private HeaderActionProvider headerActionProvider;
|
||||||
private TextView headerActionView;
|
private TextView headerActionView;
|
||||||
|
|
||||||
@Nullable private FixedViewsAdapter headerAdapter;
|
@Nullable private FixedViewsAdapter headerAdapter;
|
||||||
@Nullable private FixedViewsAdapter footerAdapter;
|
@Nullable private FixedViewsAdapter footerAdapter;
|
||||||
@Nullable private ListCallback listCallback;
|
@Nullable private ListCallback listCallback;
|
||||||
@Nullable private ScrollCallback scrollCallback;
|
@Nullable private ScrollCallback scrollCallback;
|
||||||
private GlideRequests glideRequests;
|
@Nullable private OnItemLongClickListener onItemLongClickListener;
|
||||||
private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS;
|
private GlideRequests glideRequests;
|
||||||
private Set<RecipientId> currentSelection;
|
private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS;
|
||||||
private boolean isMulti;
|
private Set<RecipientId> currentSelection;
|
||||||
private boolean hideCount;
|
private boolean isMulti;
|
||||||
private boolean canSelectSelf;
|
private boolean hideCount;
|
||||||
|
private boolean canSelectSelf;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
@@ -206,6 +206,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
if (getParentFragment() instanceof HeaderActionProvider) {
|
if (getParentFragment() instanceof HeaderActionProvider) {
|
||||||
headerActionProvider = (HeaderActionProvider) getParentFragment();
|
headerActionProvider = (HeaderActionProvider) getParentFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context instanceof OnItemLongClickListener) {
|
||||||
|
onItemLongClickListener = (OnItemLongClickListener) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getParentFragment() instanceof OnItemLongClickListener) {
|
||||||
|
onItemLongClickListener = (OnItemLongClickListener) getParentFragment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -720,6 +728,15 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onItemLongClick(ContactSelectionListItem item) {
|
||||||
|
if (onItemLongClickListener != null) {
|
||||||
|
return onItemLongClickListener.onLongClick(item);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean selectionHardLimitReached() {
|
private boolean selectionHardLimitReached() {
|
||||||
@@ -850,6 +867,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||||||
@NonNull HeaderAction getHeaderAction();
|
@NonNull HeaderAction getHeaderAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface OnItemLongClickListener {
|
||||||
|
boolean onLongClick(ContactSelectionListItem contactSelectionListItem);
|
||||||
|
}
|
||||||
|
|
||||||
public interface AbstractContactsCursorLoaderFactoryProvider {
|
public interface AbstractContactsCursorLoaderFactoryProvider {
|
||||||
@NonNull AbstractContactsCursorLoader.Factory get();
|
@NonNull AbstractContactsCursorLoader.Factory get();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.core.view.ViewCompat;
|
|||||||
|
|
||||||
import org.signal.qr.QrScannerView;
|
import org.signal.qr.QrScannerView;
|
||||||
import org.signal.qr.kitkat.ScanListener;
|
import org.signal.qr.kitkat.ScanListener;
|
||||||
|
import org.thoughtcrime.securesms.mediasend.camerax.CameraXModelBlocklist;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
@@ -57,7 +58,7 @@ public class DeviceAddFragment extends LoggingFragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
scannerView.start(getViewLifecycleOwner());
|
scannerView.start(getViewLifecycleOwner(), CameraXModelBlocklist.isBlocklisted());
|
||||||
|
|
||||||
lifecycleDisposable.bindTo(getViewLifecycleOwner());
|
lifecycleDisposable.bindTo(getViewLifecycleOwner());
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import androidx.core.view.ViewCompat;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
@@ -168,7 +168,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
|
||||||
voiceNoteMediaController = new VoiceNoteMediaController(this);
|
voiceNoteMediaController = new VoiceNoteMediaController(this);
|
||||||
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
|
viewModel = new ViewModelProvider(this).get(MediaPreviewViewModel.class);
|
||||||
|
|
||||||
fullscreenHelper = new FullscreenHelper(this);
|
fullscreenHelper = new FullscreenHelper(this);
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,27 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
|
import org.signal.core.util.DimensionUnit;
|
||||||
|
import org.signal.core.util.concurrent.SimpleTask;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.components.menu.ActionItem;
|
||||||
|
import org.thoughtcrime.securesms.components.menu.SignalContextMenu;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||||
|
import org.thoughtcrime.securesms.contacts.management.ContactsManagementRepository;
|
||||||
|
import org.thoughtcrime.securesms.contacts.management.ContactsManagementViewModel;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
@@ -33,32 +49,57 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
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.signal.core.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
||||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity container for starting a new conversation.
|
* Activity container for starting a new conversation.
|
||||||
*
|
*
|
||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class NewConversationActivity extends ContactSelectionActivity
|
public class NewConversationActivity extends ContactSelectionActivity
|
||||||
implements ContactSelectionListFragment.ListCallback
|
implements ContactSelectionListFragment.ListCallback, ContactSelectionListFragment.OnItemLongClickListener
|
||||||
{
|
{
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = Log.tag(NewConversationActivity.class);
|
private static final String TAG = Log.tag(NewConversationActivity.class);
|
||||||
|
|
||||||
|
private ContactsManagementViewModel viewModel;
|
||||||
|
private ActivityResultLauncher<Intent> contactLauncher;
|
||||||
|
|
||||||
|
private final LifecycleDisposable disposables = new LifecycleDisposable();
|
||||||
|
|
||||||
@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);
|
getSupportActionBar().setTitle(R.string.NewConversationActivity__new_message);
|
||||||
|
|
||||||
|
disposables.bindTo(this);
|
||||||
|
|
||||||
|
ContactsManagementRepository repository = new ContactsManagementRepository(this);
|
||||||
|
ContactsManagementViewModel.Factory factory = new ContactsManagementViewModel.Factory(repository);
|
||||||
|
|
||||||
|
contactLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
|
||||||
|
if (activityResult.getResultCode() == RESULT_OK) {
|
||||||
|
handleManualRefresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel = new ViewModelProvider(this, factory).get(ContactsManagementViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -120,10 +161,18 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
super.onOptionsItemSelected(item);
|
super.onOptionsItemSelected(item);
|
||||||
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home: super.onBackPressed(); return true;
|
case android.R.id.home:
|
||||||
case R.id.menu_refresh: handleManualRefresh(); return true;
|
super.onBackPressed();
|
||||||
case R.id.menu_new_group: handleCreateGroup(); return true;
|
return true;
|
||||||
case R.id.menu_invite: handleInvite(); return true;
|
case R.id.menu_refresh:
|
||||||
|
handleManualRefresh();
|
||||||
|
return true;
|
||||||
|
case R.id.menu_new_group:
|
||||||
|
handleCreateGroup();
|
||||||
|
return true;
|
||||||
|
case R.id.menu_invite:
|
||||||
|
handleInvite();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -162,4 +211,143 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||||||
handleCreateGroup();
|
handleCreateGroup();
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(ContactSelectionListItem contactSelectionListItem) {
|
||||||
|
RecipientId recipientId = contactSelectionListItem.getRecipientId().orElse(null);
|
||||||
|
if (recipientId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ActionItem> actions = generateContextualActionsForRecipient(recipientId);
|
||||||
|
if (actions.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
new SignalContextMenu.Builder(contactSelectionListItem, (ViewGroup) contactSelectionListItem.getRootView())
|
||||||
|
.preferredVerticalPosition(SignalContextMenu.VerticalPosition.BELOW)
|
||||||
|
.preferredHorizontalPosition(SignalContextMenu.HorizontalPosition.START)
|
||||||
|
.offsetX((int) DimensionUnit.DP.toPixels(12))
|
||||||
|
.offsetY((int) DimensionUnit.DP.toPixels(12))
|
||||||
|
.show(actions);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull List<ActionItem> generateContextualActionsForRecipient(@NonNull RecipientId recipientId) {
|
||||||
|
Recipient recipient = Recipient.resolved(recipientId);
|
||||||
|
|
||||||
|
return Stream.of(
|
||||||
|
createMessageActionItem(recipient),
|
||||||
|
createAudioCallActionItem(recipient),
|
||||||
|
createVideoCallActionItem(recipient),
|
||||||
|
createRemoveActionItem(recipient),
|
||||||
|
createBlockActionItem(recipient)
|
||||||
|
).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull ActionItem createMessageActionItem(@NonNull Recipient recipient) {
|
||||||
|
return new ActionItem(
|
||||||
|
R.drawable.ic_message_24,
|
||||||
|
getString(R.string.NewConversationActivity__message),
|
||||||
|
R.color.signal_colorOnSurface,
|
||||||
|
() -> startActivity(ConversationIntents.createBuilder(this, recipient.getId(), -1L).build())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable ActionItem createAudioCallActionItem(@NonNull Recipient recipient) {
|
||||||
|
if (recipient.isSelf() || recipient.isGroup()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActionItem(
|
||||||
|
R.drawable.ic_phone_right_24,
|
||||||
|
getString(R.string.NewConversationActivity__audio_call),
|
||||||
|
R.color.signal_colorOnSurface,
|
||||||
|
() -> CommunicationActions.startVoiceCall(this, recipient)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable ActionItem createVideoCallActionItem(@NonNull Recipient recipient) {
|
||||||
|
if (recipient.isSelf() || recipient.isMmsGroup()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActionItem(
|
||||||
|
R.drawable.ic_video_call_24,
|
||||||
|
getString(R.string.NewConversationActivity__video_call),
|
||||||
|
R.color.signal_colorOnSurface,
|
||||||
|
() -> CommunicationActions.startVideoCall(this, recipient)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable ActionItem createRemoveActionItem(@NonNull Recipient recipient) {
|
||||||
|
if (!FeatureFlags.hideContacts() || recipient.isSelf() || recipient.isGroup()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActionItem(
|
||||||
|
R.drawable.ic_minus_circle_20, // TODO [alex] -- correct asset
|
||||||
|
getString(R.string.NewConversationActivity__remove),
|
||||||
|
R.color.signal_colorOnSurface,
|
||||||
|
() -> {
|
||||||
|
if (recipient.isSystemContact()) {
|
||||||
|
displayIsInSystemContactsDialog(recipient);
|
||||||
|
} else {
|
||||||
|
displayRemovalDialog(recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("CodeBlock2Expr")
|
||||||
|
private @Nullable ActionItem createBlockActionItem(@NonNull Recipient recipient) {
|
||||||
|
if (recipient.isSelf()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActionItem(
|
||||||
|
R.drawable.ic_block_tinted_24,
|
||||||
|
getString(R.string.NewConversationActivity__block),
|
||||||
|
R.color.signal_colorError,
|
||||||
|
() -> BlockUnblockDialog.showBlockFor(this,
|
||||||
|
this.getLifecycle(),
|
||||||
|
recipient,
|
||||||
|
() -> {
|
||||||
|
disposables.add(viewModel.blockContact(recipient).subscribe(() -> {
|
||||||
|
displaySnackbar(R.string.NewConversationActivity__s_has_been_removed);
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayIsInSystemContactsDialog(@NonNull Recipient recipient) {
|
||||||
|
new MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(getString(R.string.NewConversationActivity__unable_to_remove_s, recipient.getShortDisplayName(this)))
|
||||||
|
.setMessage(R.string.NewConversationActivity__this_person_is_saved_to_your)
|
||||||
|
.setPositiveButton(R.string.NewConversationActivity__view_contact,
|
||||||
|
(dialog, which) -> contactLauncher.launch(new Intent(Intent.ACTION_VIEW, recipient.getContactUri()))
|
||||||
|
)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayRemovalDialog(@NonNull Recipient recipient) {
|
||||||
|
new MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(getString(R.string.NewConversationActivity__remove_s, recipient.getShortDisplayName(this)))
|
||||||
|
.setMessage(R.string.NewConversationActivity__you_wont_see_this_person)
|
||||||
|
.setPositiveButton(R.string.NewConversationActivity__remove,
|
||||||
|
(dialog, which) -> {
|
||||||
|
disposables.add(viewModel.hideContact(recipient).subscribe(() -> {
|
||||||
|
displaySnackbar(R.string.NewConversationActivity__s_has_been_removed);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displaySnackbar(@StringRes int message) {
|
||||||
|
Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.biometric.BiometricManager;
|
import androidx.biometric.BiometricManager;
|
||||||
import androidx.biometric.BiometricManager.Authenticators;
|
|
||||||
import androidx.biometric.BiometricPrompt;
|
import androidx.biometric.BiometricPrompt;
|
||||||
|
|
||||||
import org.signal.core.util.ThreadUtil;
|
import org.signal.core.util.ThreadUtil;
|
||||||
@@ -64,6 +63,8 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|||||||
import org.thoughtcrime.securesms.util.SupportEmailUtil;
|
import org.thoughtcrime.securesms.util.SupportEmailUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
|
import kotlin.Unit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity that prompts for a user's passphrase.
|
* Activity that prompts for a user's passphrase.
|
||||||
*
|
*
|
||||||
@@ -72,8 +73,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||||||
public class PassphrasePromptActivity extends PassphraseActivity {
|
public class PassphrasePromptActivity extends PassphraseActivity {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(PassphrasePromptActivity.class);
|
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 short AUTHENTICATE_REQUEST_CODE = 1007;
|
||||||
private static final String BUNDLE_ALREADY_SHOWN = "bundle_already_shown";
|
private static final String BUNDLE_ALREADY_SHOWN = "bundle_already_shown";
|
||||||
public static final String FROM_FOREGROUND = "from_foreground";
|
public static final String FROM_FOREGROUND = "from_foreground";
|
||||||
@@ -90,9 +89,9 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
private ImageButton hideButton;
|
private ImageButton hideButton;
|
||||||
private AnimatingToggle visibilityToggle;
|
private AnimatingToggle visibilityToggle;
|
||||||
|
|
||||||
private BiometricManager biometricManager;
|
private BiometricManager biometricManager;
|
||||||
private BiometricPrompt biometricPrompt;
|
private BiometricPrompt biometricPrompt;
|
||||||
private BiometricPrompt.PromptInfo biometricPromptInfo;
|
private BiometricDeviceAuthentication biometricAuth;
|
||||||
|
|
||||||
private boolean authenticated;
|
private boolean authenticated;
|
||||||
private boolean hadFailure;
|
private boolean hadFailure;
|
||||||
@@ -249,12 +248,12 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
||||||
biometricManager = BiometricManager.from(this);
|
biometricManager = BiometricManager.from(this);
|
||||||
biometricPrompt = new BiometricPrompt(this, new BiometricAuthenticationListener());
|
biometricPrompt = new BiometricPrompt(this, new BiometricAuthenticationListener());
|
||||||
biometricPromptInfo = new BiometricPrompt.PromptInfo
|
BiometricPrompt.PromptInfo biometricPromptInfo = new BiometricPrompt.PromptInfo
|
||||||
.Builder()
|
.Builder()
|
||||||
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
|
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
|
||||||
.setTitle(getString(R.string.PassphrasePromptActivity_unlock_signal))
|
.setTitle(getString(R.string.PassphrasePromptActivity_unlock_signal))
|
||||||
.build();
|
.build();
|
||||||
|
biometricAuth = new BiometricDeviceAuthentication(biometricManager, biometricPrompt, biometricPromptInfo);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setTitle("");
|
getSupportActionBar().setTitle("");
|
||||||
|
|
||||||
@@ -279,7 +278,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
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
|
fingerprintPrompt.setVisibility(biometricManager.canAuthenticate(BiometricDeviceAuthentication.BIOMETRIC_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS ? View.VISIBLE
|
||||||
: View.GONE);
|
: View.GONE);
|
||||||
lockScreenButton.setVisibility(View.VISIBLE);
|
lockScreenButton.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
@@ -290,33 +289,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void resumeScreenLock(boolean force) {
|
private void resumeScreenLock(boolean force) {
|
||||||
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
if (!biometricAuth.authenticate(getApplicationContext(), force, this::showConfirmDeviceCredentialIntent)) {
|
||||||
|
|
||||||
assert keyguardManager != null;
|
|
||||||
|
|
||||||
if (!keyguardManager.isKeyguardSecure()) {
|
|
||||||
Log.w(TAG ,"Keyguard not secure...");
|
|
||||||
handleAuthenticated();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT != 29 && biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS) {
|
|
||||||
if (force) {
|
|
||||||
Log.i(TAG, "Listening for biometric authentication...");
|
|
||||||
biometricPrompt.authenticate(biometricPromptInfo);
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Skipping show system biometric dialog unless forced");
|
|
||||||
}
|
|
||||||
} 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 {
|
|
||||||
Log.w(TAG, "Not compatible...");
|
|
||||||
handleAuthenticated();
|
handleAuthenticated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,6 +305,16 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
body);
|
body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Unit showConfirmDeviceCredentialIntent() {
|
||||||
|
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
||||||
|
Intent intent = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.PassphrasePromptActivity_unlock_signal), "");
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, AUTHENTICATE_REQUEST_CODE);
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
private class PassphraseActionListener implements TextView.OnEditorActionListener {
|
private class PassphraseActionListener implements TextView.OnEditorActionListener {
|
||||||
@Override
|
@Override
|
||||||
public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent keyEvent) {
|
public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent keyEvent) {
|
||||||
|
|||||||
@@ -20,17 +20,20 @@ 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.devicetransfer.olddevice.OldDeviceTransferActivity;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
|
||||||
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;
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.pin.PinRestoreActivity;
|
import org.thoughtcrime.securesms.pin.PinRestoreActivity;
|
||||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||||
|
import org.thoughtcrime.securesms.profiles.username.AddAUsernameActivity;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.AppStartup;
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -52,6 +55,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
private static final int STATE_TRANSFER_ONGOING = 8;
|
private static final int STATE_TRANSFER_ONGOING = 8;
|
||||||
private static final int STATE_TRANSFER_LOCKED = 9;
|
private static final int STATE_TRANSFER_LOCKED = 9;
|
||||||
private static final int STATE_CHANGE_NUMBER_LOCK = 10;
|
private static final int STATE_CHANGE_NUMBER_LOCK = 10;
|
||||||
|
private static final int STATE_CREATE_USERNAME = 11;
|
||||||
|
|
||||||
private SignalServiceNetworkAccess networkAccess;
|
private SignalServiceNetworkAccess networkAccess;
|
||||||
private BroadcastReceiver clearKeyReceiver;
|
private BroadcastReceiver clearKeyReceiver;
|
||||||
@@ -156,6 +160,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
case STATE_TRANSFER_ONGOING: return getOldDeviceTransferIntent();
|
case STATE_TRANSFER_ONGOING: return getOldDeviceTransferIntent();
|
||||||
case STATE_TRANSFER_LOCKED: return getOldDeviceTransferLockedIntent();
|
case STATE_TRANSFER_LOCKED: return getOldDeviceTransferLockedIntent();
|
||||||
case STATE_CHANGE_NUMBER_LOCK: return getChangeNumberLockIntent();
|
case STATE_CHANGE_NUMBER_LOCK: return getChangeNumberLockIntent();
|
||||||
|
case STATE_CREATE_USERNAME: return getCreateUsernameIntent();
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,6 +180,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
return STATE_CREATE_SIGNAL_PIN;
|
return STATE_CREATE_SIGNAL_PIN;
|
||||||
} else if (userMustSetProfileName()) {
|
} else if (userMustSetProfileName()) {
|
||||||
return STATE_CREATE_PROFILE_NAME;
|
return STATE_CREATE_PROFILE_NAME;
|
||||||
|
} else if (shouldAskUserToCreateUsername()) {
|
||||||
|
return STATE_CREATE_USERNAME;
|
||||||
} 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) {
|
} else if (EventBus.getDefault().getStickyEvent(TransferStatus.class) != null && getClass() != OldDeviceTransferActivity.class) {
|
||||||
@@ -200,6 +207,13 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
return !SignalStore.registrationValues().isRegistrationComplete() && Recipient.self().getProfileName().isEmpty();
|
return !SignalStore.registrationValues().isRegistrationComplete() && Recipient.self().getProfileName().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldAskUserToCreateUsername() {
|
||||||
|
return FeatureFlags.usernames() &&
|
||||||
|
FeatureFlags.phoneNumberPrivacy() &&
|
||||||
|
!SignalStore.uiHints().hasSetOrSkippedUsernameCreation() &&
|
||||||
|
SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode() == PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED;
|
||||||
|
}
|
||||||
|
|
||||||
private Intent getCreatePassphraseIntent() {
|
private Intent getCreatePassphraseIntent() {
|
||||||
return getRoutedIntent(PassphraseCreateActivity.class, getIntent());
|
return getRoutedIntent(PassphraseCreateActivity.class, getIntent());
|
||||||
}
|
}
|
||||||
@@ -238,7 +252,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Intent getCreateProfileNameIntent() {
|
private Intent getCreateProfileNameIntent() {
|
||||||
return getRoutedIntent(EditProfileActivity.class, getIntent());
|
Intent intent = EditProfileActivity.getIntentForUserProfile(this);
|
||||||
|
return getRoutedIntent(intent, getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getOldDeviceTransferIntent() {
|
private Intent getOldDeviceTransferIntent() {
|
||||||
@@ -258,6 +273,15 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||||||
return ChangeNumberLockActivity.createIntent(this);
|
return ChangeNumberLockActivity.createIntent(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Intent getCreateUsernameIntent() {
|
||||||
|
return getRoutedIntent(AddAUsernameActivity.class, getIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getRoutedIntent(Intent destination, @Nullable Intent nextIntent) {
|
||||||
|
if (nextIntent != null) destination.putExtra("next_intent", nextIntent);
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -40,15 +39,18 @@ import androidx.appcompat.app.AppCompatDelegate;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.util.Consumer;
|
import androidx.core.util.Consumer;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.window.DisplayFeature;
|
import androidx.window.java.layout.WindowInfoTrackerCallbackAdapter;
|
||||||
import androidx.window.FoldingFeature;
|
import androidx.window.layout.DisplayFeature;
|
||||||
import androidx.window.WindowLayoutInfo;
|
import androidx.window.layout.FoldingFeature;
|
||||||
|
import androidx.window.layout.WindowInfoTracker;
|
||||||
|
import androidx.window.layout.WindowLayoutInfo;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
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.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.signal.libsignal.protocol.IdentityKey;
|
import org.signal.libsignal.protocol.IdentityKey;
|
||||||
@@ -107,20 +109,21 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
|
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
|
||||||
|
|
||||||
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
||||||
|
public static final String EXTRA_STARTED_FROM_FULLSCREEN = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_FULLSCREEN";
|
||||||
|
|
||||||
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
||||||
private WifiToCellularPopupWindow wifiToCellularPopupWindow;
|
private WifiToCellularPopupWindow wifiToCellularPopupWindow;
|
||||||
private DeviceOrientationMonitor deviceOrientationMonitor;
|
private DeviceOrientationMonitor deviceOrientationMonitor;
|
||||||
|
|
||||||
private FullscreenHelper fullscreenHelper;
|
private FullscreenHelper fullscreenHelper;
|
||||||
private WebRtcCallView callScreen;
|
private WebRtcCallView callScreen;
|
||||||
private TooltipPopup videoTooltip;
|
private TooltipPopup videoTooltip;
|
||||||
private WebRtcCallViewModel viewModel;
|
private WebRtcCallViewModel viewModel;
|
||||||
private boolean enableVideoIfAvailable;
|
private boolean enableVideoIfAvailable;
|
||||||
private boolean hasWarnedAboutBluetooth;
|
private boolean hasWarnedAboutBluetooth;
|
||||||
private androidx.window.WindowManager windowManager;
|
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
|
||||||
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
|
private WindowInfoTrackerCallbackAdapter windowInfoTrackerCallbackAdapter;
|
||||||
private ThrottledDebouncer requestNewSizesThrottle;
|
private ThrottledDebouncer requestNewSizesThrottle;
|
||||||
|
|
||||||
private Disposable ephemeralStateDisposable = Disposable.empty();
|
private Disposable ephemeralStateDisposable = Disposable.empty();
|
||||||
|
|
||||||
@@ -133,7 +136,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
@SuppressLint("SourceLockedOrientationActivity")
|
@SuppressLint("SourceLockedOrientationActivity")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
Log.i(TAG, "onCreate()");
|
Log.i(TAG, "onCreate(" + getIntent().getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false) + ")");
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -158,10 +161,10 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
|
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
|
||||||
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
|
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
|
||||||
|
|
||||||
windowManager = new androidx.window.WindowManager(this);
|
|
||||||
windowLayoutInfoConsumer = new WindowLayoutInfoConsumer();
|
windowLayoutInfoConsumer = new WindowLayoutInfoConsumer();
|
||||||
|
|
||||||
windowManager.registerLayoutChangeCallback(SignalExecutors.BOUNDED, windowLayoutInfoConsumer);
|
windowInfoTrackerCallbackAdapter = new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
|
||||||
|
windowInfoTrackerCallbackAdapter.addWindowLayoutInfoListener(this, SignalExecutors.BOUNDED, windowLayoutInfoConsumer);
|
||||||
|
|
||||||
requestNewSizesThrottle = new ThrottledDebouncer(TimeUnit.SECONDS.toMillis(1));
|
requestNewSizesThrottle = new ThrottledDebouncer(TimeUnit.SECONDS.toMillis(1));
|
||||||
}
|
}
|
||||||
@@ -185,11 +188,25 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
if (!EventBus.getDefault().isRegistered(this)) {
|
if (!EventBus.getDefault().isRegistered(this)) {
|
||||||
EventBus.getDefault().register(this);
|
EventBus.getDefault().register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebRtcViewModel rtcViewModel = EventBus.getDefault().getStickyEvent(WebRtcViewModel.class);
|
||||||
|
if (rtcViewModel == null) {
|
||||||
|
Log.w(TAG, "Activity resumed without service event, perform delay destroy");
|
||||||
|
ThreadUtil.runOnMainDelayed(() -> {
|
||||||
|
WebRtcViewModel delayRtcViewModel = EventBus.getDefault().getStickyEvent(WebRtcViewModel.class);
|
||||||
|
if (delayRtcViewModel == null) {
|
||||||
|
Log.w(TAG, "Activity still without service event, finishing activity");
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Event found after delay");
|
||||||
|
}
|
||||||
|
}, TimeUnit.SECONDS.toMillis(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(Intent intent) {
|
public void onNewIntent(Intent intent) {
|
||||||
Log.i(TAG, "onNewIntent");
|
Log.i(TAG, "onNewIntent(" + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false) + ")");
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
processIntent(intent);
|
processIntent(intent);
|
||||||
}
|
}
|
||||||
@@ -234,7 +251,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
windowManager.unregisterLayoutChangeCallback(windowLayoutInfoConsumer);
|
windowInfoTrackerCallbackAdapter.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
|
||||||
EventBus.getDefault().unregister(this);
|
EventBus.getDefault().unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,12 +273,6 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
|
|
||||||
viewModel.setIsInPipMode(isInPictureInPictureMode);
|
|
||||||
participantUpdateWindow.setEnabled(!isInPictureInPictureMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean enterPipModeIfPossible() {
|
private boolean enterPipModeIfPossible() {
|
||||||
if (viewModel.canEnterPipMode() && isSystemPipEnabledAndAvailable()) {
|
if (viewModel.canEnterPipMode() && isSystemPipEnabledAndAvailable()) {
|
||||||
PictureInPictureParams params = new PictureInPictureParams.Builder()
|
PictureInPictureParams params = new PictureInPictureParams.Builder()
|
||||||
@@ -341,6 +352,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||||||
|
|
||||||
viewModel.getOrientationAndLandscapeEnabled().observe(this, pair -> ApplicationDependencies.getSignalCallManager().orientationChanged(pair.second, pair.first.getDegrees()));
|
viewModel.getOrientationAndLandscapeEnabled().observe(this, pair -> ApplicationDependencies.getSignalCallManager().orientationChanged(pair.second, pair.first.getDegrees()));
|
||||||
viewModel.getControlsRotation().observe(this, callScreen::rotateControls);
|
viewModel.getControlsRotation().observe(this, callScreen::rotateControls);
|
||||||
|
|
||||||
|
addOnPictureInPictureModeChangedListener(info -> {
|
||||||
|
viewModel.setIsInPipMode(info.isInPictureInPictureMode());
|
||||||
|
participantUpdateWindow.setEnabled(!info.isInPictureInPictureMode());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
|
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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.signal.core.util.PendingIntentFlags;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationCancellationHelper;
|
import org.thoughtcrime.securesms.notifications.NotificationCancellationHelper;
|
||||||
@@ -40,7 +41,7 @@ public enum BackupFileIOError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void postNotification(@NonNull Context context) {
|
public void postNotification(@NonNull Context context) {
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, AppSettingsActivity.backups(context), 0);
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, AppSettingsActivity.backups(context), PendingIntentFlags.mutable());
|
||||||
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))
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
return count;
|
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) {
|
||||||
long result = 0;
|
long result = 0;
|
||||||
|
|
||||||
try (InputStream inputStream = openAttachmentStream(attachmentSecret, random, data)) {
|
try (InputStream inputStream = openAttachmentStream(attachmentSecret, random, data)) {
|
||||||
@@ -425,6 +425,10 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
}
|
}
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Log.w(TAG, "Missing attachment: " + e.getMessage());
|
Log.w(TAG, "Missing attachment: " + e.getMessage());
|
||||||
|
return 0;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to determine stream length", e);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -708,6 +712,7 @@ public class FullBackupExporter extends FullBackupBase {
|
|||||||
|
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
|
outputStream.flush();
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.components.InputAwareLayout
|
|||||||
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
|
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
|
||||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
|
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationEvent
|
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationEvent
|
||||||
@@ -36,6 +35,7 @@ import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
|
|||||||
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
|
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
|
||||||
import org.thoughtcrime.securesms.util.Debouncer
|
import org.thoughtcrime.securesms.util.Debouncer
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,7 +75,7 @@ class GiftFlowConfirmationFragment :
|
|||||||
private val eventPublisher = PublishSubject.create<TextInput.TextInputEvent>()
|
private val eventPublisher = PublishSubject.create<TextInput.TextInputEvent>()
|
||||||
private val debouncer = Debouncer(100L)
|
private val debouncer = Debouncer(100L)
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
RecipientPreference.register(adapter)
|
RecipientPreference.register(adapter)
|
||||||
GiftRowItem.register(adapter)
|
GiftRowItem.register(adapter)
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,20 @@ import android.view.View
|
|||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import org.signal.core.util.DimensionUnit
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection
|
import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection
|
||||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
|
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle
|
import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle
|
||||||
|
import org.thoughtcrime.securesms.components.settings.models.SplashImage
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
@@ -32,11 +35,12 @@ class GiftFlowStartFragment : DSLSettingsFragment(
|
|||||||
|
|
||||||
private val lifecycleDisposable = LifecycleDisposable()
|
private val lifecycleDisposable = LifecycleDisposable()
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
CurrencySelection.register(adapter)
|
CurrencySelection.register(adapter)
|
||||||
GiftRowItem.register(adapter)
|
GiftRowItem.register(adapter)
|
||||||
NetworkFailure.register(adapter)
|
NetworkFailure.register(adapter)
|
||||||
IndeterminateLoadingCircle.register(adapter)
|
IndeterminateLoadingCircle.register(adapter)
|
||||||
|
SplashImage.register(adapter)
|
||||||
|
|
||||||
val next = requireView().findViewById<View>(R.id.next)
|
val next = requireView().findViewById<View>(R.id.next)
|
||||||
next.setOnClickListener {
|
next.setOnClickListener {
|
||||||
@@ -58,6 +62,28 @@ class GiftFlowStartFragment : DSLSettingsFragment(
|
|||||||
|
|
||||||
private fun getConfiguration(state: GiftFlowState): DSLConfiguration {
|
private fun getConfiguration(state: GiftFlowState): DSLConfiguration {
|
||||||
return configure {
|
return configure {
|
||||||
|
customPref(
|
||||||
|
SplashImage.Model(
|
||||||
|
R.drawable.ic_gift_chat
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
noPadTextPref(
|
||||||
|
title = DSLSettingsText.from(
|
||||||
|
R.string.GiftFlowStartFragment__gift_a_badge,
|
||||||
|
DSLSettingsText.CenterModifier,
|
||||||
|
DSLSettingsText.TextAppearanceModifier(R.style.Signal_Text_Headline)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
space(DimensionUnit.DP.toPixels(16f).toInt())
|
||||||
|
|
||||||
|
noPadTextPref(
|
||||||
|
title = DSLSettingsText.from(R.string.GiftFlowStartFragment__gift_someone_a_badge, DSLSettingsText.CenterModifier)
|
||||||
|
)
|
||||||
|
|
||||||
|
space(DimensionUnit.DP.toPixels(16f).toInt())
|
||||||
|
|
||||||
customPref(
|
customPref(
|
||||||
CurrencySelection.Model(
|
CurrencySelection.Model(
|
||||||
selectedCurrency = state.currency,
|
selectedCurrency = state.currency,
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ object GiftRowItem {
|
|||||||
private val taglineView = itemView.findViewById<TextView>(R.id.tagline)
|
private val taglineView = itemView.findViewById<TextView>(R.id.tagline)
|
||||||
private val priceView = itemView.findViewById<TextView>(R.id.price)
|
private val priceView = itemView.findViewById<TextView>(R.id.price)
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.isSelected = true
|
||||||
|
}
|
||||||
|
|
||||||
override fun bind(model: Model) {
|
override fun bind(model: Model) {
|
||||||
checkView.visible = false
|
checkView.visible = false
|
||||||
badgeView.setBadge(model.giftBadge)
|
badgeView.setBadge(model.giftBadge)
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import org.thoughtcrime.securesms.badges.Badges.displayBadges
|
|||||||
import org.thoughtcrime.securesms.badges.models.Badge
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.badges.models.BadgePreview
|
import org.thoughtcrime.securesms.badges.models.BadgePreview
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment which allows user to select one of their badges to be their "Featured" badge.
|
* Fragment which allows user to select one of their badges to be their "Featured" badge.
|
||||||
@@ -50,7 +50,7 @@ class SelectFeaturedBadgeFragment : DSLSettingsFragment(
|
|||||||
return Material3OnScrollHelper(requireActivity(), scrollShadow)
|
return Material3OnScrollHelper(requireActivity(), scrollShadow)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
Badge.register(adapter) { badge, isSelected, _ ->
|
Badge.register(adapter) { badge, isSelected, _ ->
|
||||||
if (!isSelected) {
|
if (!isSelected) {
|
||||||
viewModel.setSelectedBadge(badge)
|
viewModel.setSelectedBadge(badge)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.badges.Badges.displayBadges
|
|||||||
import org.thoughtcrime.securesms.badges.models.Badge
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment
|
import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.app.subscription.SubscriptionsRepository
|
import org.thoughtcrime.securesms.components.settings.app.subscription.SubscriptionsRepository
|
||||||
@@ -18,6 +17,7 @@ import org.thoughtcrime.securesms.components.settings.configure
|
|||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +35,7 @@ class BadgesOverviewFragment : DSLSettingsFragment(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
Badge.register(adapter) { badge, _, isFaded ->
|
Badge.register(adapter) { badge, _, isFaded ->
|
||||||
if (badge.isExpired() || isFaded) {
|
if (badge.isExpired() || isFaded) {
|
||||||
findNavController().safeNavigate(BadgesOverviewFragmentDirections.actionBadgeManageFragmentToExpiredBadgeDialog(badge, null, null))
|
findNavController().safeNavigate(BadgesOverviewFragmentDirections.actionBadgeManageFragmentToExpiredBadgeDialog(badge, null, null))
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
|
||||||
import org.thoughtcrime.securesms.util.PlayServicesUtil
|
import org.thoughtcrime.securesms.util.PlayServicesUtil
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
@@ -68,10 +67,7 @@ class ViewBadgeBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
|
|||||||
action.setOnClickListener {
|
action.setOnClickListener {
|
||||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.donate_url))
|
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.donate_url))
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (Recipient.self().badges.none { it.category == Badge.Category.Donor && !it.isBoost() && !it.isExpired() }) {
|
||||||
FeatureFlags.donorBadges() &&
|
|
||||||
Recipient.self().badges.none { it.category == Badge.Category.Donor && !it.isBoost() && !it.isExpired() }
|
|
||||||
) {
|
|
||||||
action.setOnClickListener {
|
action.setOnClickListener {
|
||||||
startActivity(AppSettingsActivity.subscriptions(requireContext()))
|
startActivity(AppSettingsActivity.subscriptions(requireContext()))
|
||||||
}
|
}
|
||||||
@@ -143,10 +139,6 @@ class ViewBadgeBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
|
|||||||
recipientId: RecipientId,
|
recipientId: RecipientId,
|
||||||
startBadge: Badge? = null
|
startBadge: Badge? = null
|
||||||
) {
|
) {
|
||||||
if (!FeatureFlags.displayDonorBadges() && recipientId != Recipient.self().id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewBadgeBottomSheetDialogFragment().apply {
|
ViewBadgeBottomSheetDialogFragment().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
putParcelable(ARG_START_BADGE, startBadge)
|
putParcelable(ARG_START_BADGE, startBadge)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import androidx.annotation.StringRes;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
@@ -48,7 +48,7 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
|||||||
BlockedUsersRepository repository = new BlockedUsersRepository(this);
|
BlockedUsersRepository repository = new BlockedUsersRepository(this);
|
||||||
BlockedUsersViewModel.Factory factory = new BlockedUsersViewModel.Factory(repository);
|
BlockedUsersViewModel.Factory factory = new BlockedUsersViewModel.Factory(repository);
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, factory).get(BlockedUsersViewModel.class);
|
viewModel = new ViewModelProvider(this, factory).get(BlockedUsersViewModel.class);
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
ContactFilterView contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
ContactFilterView contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import android.view.ViewGroup;
|
|||||||
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;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.BlockUnblockDialog;
|
import org.thoughtcrime.securesms.BlockUnblockDialog;
|
||||||
@@ -59,7 +59,7 @@ public class BlockedUsersFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(requireActivity()).get(BlockedUsersViewModel.class);
|
viewModel = new ViewModelProvider(requireActivity()).get(BlockedUsersViewModel.class);
|
||||||
viewModel.getRecipients().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getRecipients().observe(getViewLifecycleOwner(), list -> {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
empty.setVisibility(View.VISIBLE);
|
empty.setVisibility(View.VISIBLE);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import com.airbnb.lottie.LottieAnimationView;
|
import com.airbnb.lottie.LottieAnimationView;
|
||||||
@@ -126,6 +127,11 @@ public final class AudioView extends FrameLayout {
|
|||||||
|
|
||||||
setTint(typedArray.getColor(R.styleable.AudioView_foregroundTintColor, Color.WHITE));
|
setTint(typedArray.getColor(R.styleable.AudioView_foregroundTintColor, Color.WHITE));
|
||||||
|
|
||||||
|
int backgroundTintColor = typedArray.getColor(R.styleable.AudioView_backgroundTintColor, Color.TRANSPARENT);
|
||||||
|
if (getBackground() != null && backgroundTintColor != Color.TRANSPARENT) {
|
||||||
|
DrawableCompat.setTint(getBackground(), backgroundTintColor);
|
||||||
|
}
|
||||||
|
|
||||||
this.waveFormPlayedBarsColor = typedArray.getColor(R.styleable.AudioView_waveformPlayedBarsColor, Color.WHITE);
|
this.waveFormPlayedBarsColor = typedArray.getColor(R.styleable.AudioView_waveformPlayedBarsColor, Color.WHITE);
|
||||||
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);
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
|
|
||||||
private @Nullable RecipientContactPhoto recipientContactPhoto;
|
private @Nullable RecipientContactPhoto recipientContactPhoto;
|
||||||
private @NonNull Drawable unknownRecipientDrawable;
|
private @NonNull Drawable unknownRecipientDrawable;
|
||||||
|
private @Nullable AvatarColor fallbackPhotoColor;
|
||||||
|
|
||||||
public AvatarImageView(Context context) {
|
public AvatarImageView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -105,6 +106,10 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
this.fallbackPhotoProvider = fallbackPhotoProvider;
|
this.fallbackPhotoProvider = fallbackPhotoProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFallbackPhotoColor(@Nullable AvatarColor fallbackPhotoColor) {
|
||||||
|
this.fallbackPhotoColor = fallbackPhotoColor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows self as the actual profile picture.
|
* Shows self as the actual profile picture.
|
||||||
*/
|
*/
|
||||||
@@ -213,7 +218,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
|||||||
requestManager.clear(this);
|
requestManager.clear(this);
|
||||||
if (fallbackPhotoProvider != null) {
|
if (fallbackPhotoProvider != null) {
|
||||||
setImageDrawable(fallbackPhotoProvider.getPhotoForRecipientWithoutName()
|
setImageDrawable(fallbackPhotoProvider.getPhotoForRecipientWithoutName()
|
||||||
.asDrawable(getContext(), AvatarColor.UNKNOWN, inverted));
|
.asDrawable(getContext(), Util.firstNonNull(fallbackPhotoColor, AvatarColor.UNKNOWN), inverted));
|
||||||
} else {
|
} else {
|
||||||
setImageDrawable(unknownRecipientDrawable);
|
setImageDrawable(unknownRecipientDrawable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ public class ComposeText extends EmojiEditText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int delimiterSearchIndex = inputCursorPosition - 1;
|
int delimiterSearchIndex = inputCursorPosition - 1;
|
||||||
while (delimiterSearchIndex >= 0 && (text.charAt(delimiterSearchIndex) != starter && text.charAt(delimiterSearchIndex) != ' ')) {
|
while (delimiterSearchIndex >= 0 && (text.charAt(delimiterSearchIndex) != starter && !Character.isWhitespace(text.charAt(delimiterSearchIndex)))) {
|
||||||
delimiterSearchIndex--;
|
delimiterSearchIndex--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ class Material3SearchToolbar @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearText() {
|
||||||
|
input.setText("")
|
||||||
|
}
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
fun onSearchTextChange(text: String)
|
fun onSearchTextChange(text: String)
|
||||||
fun onSearchClosed()
|
fun onSearchClosed()
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.thoughtcrime.securesms.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A small card with a circular progress indicator in it. Usable in place
|
||||||
|
* of a ProgressDialog, which is deprecated.
|
||||||
|
*
|
||||||
|
* Remember to add this as the last UI element in your XML hierarchy so it'll
|
||||||
|
* draw over top of other elements.
|
||||||
|
*/
|
||||||
|
class ProgressCard @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null
|
||||||
|
) : MaterialCardView(context, attrs) {
|
||||||
|
init {
|
||||||
|
inflate(context, R.layout.progress_card, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.thoughtcrime.securesms.components
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewBinderDelegate which enforces the "best practices" for maintaining a reference to a view binding given by
|
||||||
|
* Android official documentation.
|
||||||
|
*/
|
||||||
|
open class ViewBinderDelegate<T : ViewBinding>(
|
||||||
|
private val bindingFactory: (View) -> T,
|
||||||
|
private val onBindingWillBeDestroyed: (T) -> Unit = {}
|
||||||
|
) : DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
private var binding: T? = null
|
||||||
|
private var isBindingDestroyed = false
|
||||||
|
|
||||||
|
operator fun getValue(thisRef: Fragment, property: KProperty<*>): T {
|
||||||
|
if (isBindingDestroyed) {
|
||||||
|
error("Binding has been destroyed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding == null) {
|
||||||
|
thisRef.viewLifecycleOwner.lifecycle.addObserver(this@ViewBinderDelegate)
|
||||||
|
binding = bindingFactory(thisRef.requireView())
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
if (binding != null) {
|
||||||
|
onBindingWillBeDestroyed(binding!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding = null
|
||||||
|
isBindingDestroyed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,9 @@ final class ReminderActionsAdapter extends RecyclerView.Adapter<ReminderActionsA
|
|||||||
TextView button = ((TextView) LayoutInflater.from(context).inflate(R.layout.reminder_action_button, parent, false));
|
TextView button = ((TextView) LayoutInflater.from(context).inflate(R.layout.reminder_action_button, parent, false));
|
||||||
|
|
||||||
if (importance == Reminder.Importance.NORMAL) {
|
if (importance == Reminder.Importance.NORMAL) {
|
||||||
button.setTextColor(ContextCompat.getColor(context, R.color.signal_accent_primary));
|
button.setTextColor(ContextCompat.getColor(context, R.color.signal_colorPrimary));
|
||||||
|
} else if (importance == Reminder.Importance.ERROR || importance == Reminder.Importance.TERMINAL) {
|
||||||
|
button.setTextColor(ContextCompat.getColor(context, R.color.signal_light_colorOnSurface));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ActionViewHolder(button);
|
return new ActionViewHolder(button);
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.card.MaterialCardView;
|
||||||
|
|
||||||
import org.signal.core.util.DimensionUnit;
|
import org.signal.core.util.DimensionUnit;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
@@ -28,8 +30,7 @@ import java.util.List;
|
|||||||
public final class ReminderView extends FrameLayout {
|
public final class ReminderView extends FrameLayout {
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private TextView progressText;
|
private TextView progressText;
|
||||||
private ViewGroup container;
|
private MaterialCardView container;
|
||||||
private View background;
|
|
||||||
private ImageButton closeButton;
|
private ImageButton closeButton;
|
||||||
private TextView title;
|
private TextView title;
|
||||||
private TextView text;
|
private TextView text;
|
||||||
@@ -58,7 +59,6 @@ public final class ReminderView extends FrameLayout {
|
|||||||
progressBar = findViewById(R.id.reminder_progress);
|
progressBar = findViewById(R.id.reminder_progress);
|
||||||
progressText = findViewById(R.id.reminder_progress_text);
|
progressText = findViewById(R.id.reminder_progress_text);
|
||||||
container = findViewById(R.id.container);
|
container = findViewById(R.id.container);
|
||||||
background = findViewById(R.id.background);
|
|
||||||
closeButton = findViewById(R.id.cancel);
|
closeButton = findViewById(R.id.cancel);
|
||||||
title = findViewById(R.id.reminder_title);
|
title = findViewById(R.id.reminder_title);
|
||||||
text = findViewById(R.id.reminder_text);
|
text = findViewById(R.id.reminder_text);
|
||||||
@@ -75,6 +75,7 @@ public final class ReminderView extends FrameLayout {
|
|||||||
title.setText("");
|
title.setText("");
|
||||||
title.setVisibility(GONE);
|
title.setVisibility(GONE);
|
||||||
space.setVisibility(VISIBLE);
|
space.setVisibility(VISIBLE);
|
||||||
|
text.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_colorOnSurface));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reminder.isDismissable()) {
|
if (!reminder.isDismissable()) {
|
||||||
@@ -82,22 +83,17 @@ public final class ReminderView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
text.setText(reminder.getText());
|
text.setText(reminder.getText());
|
||||||
|
|
||||||
switch (reminder.getImportance()) {
|
switch (reminder.getImportance()) {
|
||||||
case NORMAL:
|
case NORMAL:
|
||||||
background.setBackgroundResource(R.drawable.reminder_background_normal);
|
title.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_colorOnSurface));
|
||||||
title.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_text_primary));
|
text.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_colorOnSurfaceVariant));
|
||||||
text.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_text_primary));
|
|
||||||
break;
|
break;
|
||||||
case ERROR:
|
case ERROR:
|
||||||
background.setBackgroundResource(R.drawable.reminder_background_error);
|
|
||||||
title.setTextColor(ContextCompat.getColor(getContext(), R.color.core_black));
|
|
||||||
text.setTextColor(ContextCompat.getColor(getContext(), R.color.core_black));
|
|
||||||
break;
|
|
||||||
case TERMINAL:
|
case TERMINAL:
|
||||||
background.setBackgroundResource(R.drawable.reminder_background_terminal);
|
container.setStrokeWidth(0);
|
||||||
title.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_button_primary_text));
|
container.setCardBackgroundColor(ContextCompat.getColor(getContext(), R.color.reminder_background));
|
||||||
text.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_button_primary_text));
|
title.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_light_colorOnSurface));
|
||||||
|
text.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_light_colorOnSurface));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
@@ -118,7 +114,7 @@ public final class ReminderView extends FrameLayout {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (reminder.getImportance() == Reminder.Importance.NORMAL) {
|
if (reminder.getImportance() == Reminder.Importance.NORMAL) {
|
||||||
closeButton.setColorFilter(ContextCompat.getColor(getContext(), R.color.signal_text_primary));
|
closeButton.setColorFilter(ContextCompat.getColor(getContext(), R.color.signal_colorOnSurfaceVariant));
|
||||||
}
|
}
|
||||||
|
|
||||||
int progress = reminder.getProgress();
|
int progress = reminder.getProgress();
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
import java.lang.UnsupportedOperationException
|
||||||
|
|
||||||
abstract class DSLSettingsFragment(
|
abstract class DSLSettingsFragment(
|
||||||
@StringRes private val titleId: Int = -1,
|
@StringRes private val titleId: Int = -1,
|
||||||
@@ -27,9 +30,11 @@ abstract class DSLSettingsFragment(
|
|||||||
protected var recyclerView: RecyclerView? = null
|
protected var recyclerView: RecyclerView? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private var toolbar: Toolbar? = null
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
val toolbar: Toolbar? = view.findViewById(R.id.toolbar)
|
toolbar = view.findViewById(R.id.toolbar)
|
||||||
|
|
||||||
if (titleId != -1) {
|
if (titleId != -1) {
|
||||||
toolbar?.setTitle(titleId)
|
toolbar?.setTitle(titleId)
|
||||||
@@ -44,7 +49,13 @@ abstract class DSLSettingsFragment(
|
|||||||
toolbar?.setOnMenuItemClickListener { onOptionsItemSelected(it) }
|
toolbar?.setOnMenuItemClickListener { onOptionsItemSelected(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val settingsAdapter = DSLSettingsAdapter()
|
val config = ConcatAdapter.Config.Builder().setIsolateViewTypes(false).build()
|
||||||
|
val settingsAdapters = createAdapters()
|
||||||
|
val settingsAdapter: RecyclerView.Adapter<out RecyclerView.ViewHolder> = when {
|
||||||
|
settingsAdapters.size > 1 -> ConcatAdapter(config, *settingsAdapters)
|
||||||
|
settingsAdapters.size == 1 -> settingsAdapters.first()
|
||||||
|
else -> error("Require one or more settings adapters.")
|
||||||
|
}
|
||||||
|
|
||||||
recyclerView = view.findViewById<RecyclerView>(R.id.recycler).apply {
|
recyclerView = view.findViewById<RecyclerView>(R.id.recycler).apply {
|
||||||
edgeEffectFactory = EdgeEffectFactory()
|
edgeEffectFactory = EdgeEffectFactory()
|
||||||
@@ -56,7 +67,11 @@ abstract class DSLSettingsFragment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bindAdapter(settingsAdapter)
|
when (settingsAdapter) {
|
||||||
|
is ConcatAdapter -> bindAdapters(settingsAdapter)
|
||||||
|
is MappingAdapter -> bindAdapter(settingsAdapter)
|
||||||
|
else -> error("Illegal adapter subtype: ${settingsAdapter.javaClass.simpleName}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getMaterial3OnScrollHelper(toolbar: Toolbar?): Material3OnScrollHelper? {
|
open fun getMaterial3OnScrollHelper(toolbar: Toolbar?): Material3OnScrollHelper? {
|
||||||
@@ -76,7 +91,25 @@ abstract class DSLSettingsFragment(
|
|||||||
recyclerView = null
|
recyclerView = null
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun bindAdapter(adapter: DSLSettingsAdapter)
|
fun setTitle(@StringRes resId: Int) {
|
||||||
|
toolbar?.setTitle(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTitle(title: CharSequence) {
|
||||||
|
toolbar?.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun createAdapters(): Array<MappingAdapter> {
|
||||||
|
return arrayOf(DSLSettingsAdapter())
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun bindAdapter(adapter: MappingAdapter) {
|
||||||
|
throw UnsupportedOperationException("This method is not implemented.")
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun bindAdapters(adapter: ConcatAdapter) {
|
||||||
|
throw UnsupportedOperationException("This method is not implemented.")
|
||||||
|
}
|
||||||
|
|
||||||
private class EdgeEffectFactory : RecyclerView.EdgeEffectFactory() {
|
private class EdgeEffectFactory : RecyclerView.EdgeEffectFactory() {
|
||||||
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
|
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.R
|
|||||||
import org.thoughtcrime.securesms.badges.BadgeImageView
|
import org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
@@ -23,6 +22,7 @@ import org.thoughtcrime.securesms.util.FeatureFlags
|
|||||||
import org.thoughtcrime.securesms.util.PlayServicesUtil
|
import org.thoughtcrime.securesms.util.PlayServicesUtil
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
|
|||||||
|
|
||||||
private val viewModel: AppSettingsViewModel by viewModels()
|
private val viewModel: AppSettingsViewModel by viewModels()
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
adapter.registerFactory(BioPreference::class.java, LayoutFactory(::BioPreferenceViewHolder, R.layout.bio_preference_item))
|
adapter.registerFactory(BioPreference::class.java, LayoutFactory(::BioPreferenceViewHolder, R.layout.bio_preference_item))
|
||||||
adapter.registerFactory(PaymentsPreference::class.java, LayoutFactory(::PaymentsPreferenceViewHolder, R.layout.dsl_payments_preference))
|
adapter.registerFactory(PaymentsPreference::class.java, LayoutFactory(::PaymentsPreferenceViewHolder, R.layout.dsl_payments_preference))
|
||||||
adapter.registerFactory(SubscriptionPreference::class.java, LayoutFactory(::SubscriptionPreferenceViewHolder, R.layout.dsl_preference_item))
|
adapter.registerFactory(SubscriptionPreference::class.java, LayoutFactory(::SubscriptionPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||||
@@ -70,8 +70,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (FeatureFlags.donorBadges() && PlayServicesUtil.getPlayServicesStatus(requireContext()) == PlayServicesUtil.PlayServicesStatus.SUCCESS) {
|
if (PlayServicesUtil.getPlayServicesStatus(requireContext()) == PlayServicesUtil.PlayServicesStatus.SUCCESS) {
|
||||||
|
|
||||||
clickPref(
|
clickPref(
|
||||||
title = DSLSettingsText.from(R.string.preferences__donate_to_signal),
|
title = DSLSettingsText.from(R.string.preferences__donate_to_signal),
|
||||||
icon = DSLSettingsIcon.from(R.drawable.ic_heart_24),
|
icon = DSLSettingsIcon.from(R.drawable.ic_heart_24),
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
@@ -33,6 +32,7 @@ import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
|||||||
import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog
|
import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFragment__account) {
|
class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFragment__account) {
|
||||||
@@ -50,7 +50,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
|
|||||||
viewModel.refreshState()
|
viewModel.refreshState()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
viewModel = ViewModelProvider(this)[AccountSettingsViewModel::class.java]
|
viewModel = ViewModelProvider(this)[AccountSettingsViewModel::class.java]
|
||||||
|
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.keyvalue.SettingsValues
|
import org.thoughtcrime.securesms.keyvalue.SettingsValues
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
class AppearanceSettingsFragment : DSLSettingsFragment(R.string.preferences__appearance) {
|
class AppearanceSettingsFragment : DSLSettingsFragment(R.string.preferences__appearance) {
|
||||||
@@ -24,7 +24,7 @@ class AppearanceSettingsFragment : DSLSettingsFragment(R.string.preferences__app
|
|||||||
private val languageLabels by lazy { resources.getStringArray(R.array.language_entries) }
|
private val languageLabels by lazy { resources.getStringArray(R.array.language_entries) }
|
||||||
private val languageValues by lazy { resources.getStringArray(R.array.language_values) }
|
private val languageValues by lazy { resources.getStringArray(R.array.language_values) }
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
viewModel = ViewModelProvider(this)[AppearanceSettingsViewModel::class.java]
|
viewModel = ViewModelProvider(this)[AppearanceSettingsViewModel::class.java]
|
||||||
|
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
|
|||||||
@@ -5,8 +5,12 @@ import android.view.View
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.google.android.gms.auth.api.phone.SmsRetriever
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.LoggingFragment
|
import org.thoughtcrime.securesms.LoggingFragment
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.util.PlayServicesUtil
|
||||||
|
import org.thoughtcrime.securesms.util.PlayServicesUtil.PlayServicesStatus
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
class ChangeNumberConfirmFragment : LoggingFragment(R.layout.fragment_change_number_confirm) {
|
class ChangeNumberConfirmFragment : LoggingFragment(R.layout.fragment_change_number_confirm) {
|
||||||
@@ -29,6 +33,35 @@ class ChangeNumberConfirmFragment : LoggingFragment(R.layout.fragment_change_num
|
|||||||
editNumber.setOnClickListener { findNavController().navigateUp() }
|
editNumber.setOnClickListener { findNavController().navigateUp() }
|
||||||
|
|
||||||
val changeNumber: View = view.findViewById(R.id.change_number_confirm_change_number)
|
val changeNumber: View = view.findViewById(R.id.change_number_confirm_change_number)
|
||||||
changeNumber.setOnClickListener { findNavController().safeNavigate(R.id.action_changePhoneNumberConfirmFragment_to_changePhoneNumberVerifyFragment) }
|
changeNumber.setOnClickListener { onConfirm() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onConfirm() {
|
||||||
|
val playServicesAvailable = PlayServicesUtil.getPlayServicesStatus(context) == PlayServicesStatus.SUCCESS
|
||||||
|
|
||||||
|
if (playServicesAvailable) {
|
||||||
|
val client = SmsRetriever.getClient(requireContext())
|
||||||
|
val task = client.startSmsRetriever()
|
||||||
|
|
||||||
|
task.addOnSuccessListener {
|
||||||
|
Log.i(TAG, "Successfully registered SMS listener.")
|
||||||
|
navigateToVerify()
|
||||||
|
}
|
||||||
|
|
||||||
|
task.addOnFailureListener { e ->
|
||||||
|
Log.w(TAG, "Failed to register SMS listener.", e)
|
||||||
|
navigateToVerify()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigateToVerify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToVerify() {
|
||||||
|
findNavController().safeNavigate(R.id.action_changePhoneNumberConfirmFragment_to_changePhoneNumberVerifyFragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = Log.tag(ChangeNumberConfirmFragment::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ class ChangeNumberLockActivity : PassphraseRequiredActivity() {
|
|||||||
Single.just(false)
|
Single.just(false)
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Local (${SignalStore.account().e164}) and remote (${whoAmI.number}) numbers do not match, updating local.")
|
Log.i(TAG, "Local (${SignalStore.account().e164}) and remote (${whoAmI.number}) numbers do not match, updating local.")
|
||||||
changeNumberRepository.changeLocalNumber(whoAmI.number, PNI.parseOrThrow(whoAmI.pni))
|
Single
|
||||||
|
.just(true)
|
||||||
|
.flatMap { changeNumberRepository.changeLocalNumber(whoAmI.number, PNI.parseOrThrow(whoAmI.pni)) }
|
||||||
|
.compose(ChangeNumberRepository::acquireReleaseChangeNumberLock)
|
||||||
.map { true }
|
.map { true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import io.reactivex.rxjava3.core.Single
|
|||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||||
|
import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||||
import org.signal.libsignal.protocol.state.SignalProtocolStore
|
import org.signal.libsignal.protocol.state.SignalProtocolStore
|
||||||
import org.signal.libsignal.protocol.util.KeyHelper
|
import org.signal.libsignal.protocol.util.KeyHelper
|
||||||
import org.signal.libsignal.protocol.util.Medium
|
import org.signal.libsignal.protocol.util.Medium
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
|
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata
|
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata
|
||||||
@@ -30,7 +30,6 @@ import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException
|
|||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender
|
||||||
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
|
||||||
import org.whispersystems.signalservice.api.push.PNI
|
import org.whispersystems.signalservice.api.push.PNI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
import org.whispersystems.signalservice.api.push.ServiceIdType
|
import org.whispersystems.signalservice.api.push.ServiceIdType
|
||||||
@@ -41,13 +40,47 @@ import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
|
|||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
|
||||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
||||||
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
|
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
|
||||||
|
import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevicesException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
private val TAG: String = Log.tag(ChangeNumberRepository::class.java)
|
private val TAG: String = Log.tag(ChangeNumberRepository::class.java)
|
||||||
|
|
||||||
class ChangeNumberRepository(private val accountManager: SignalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager()) {
|
/**
|
||||||
|
* Provides various change number operations. All operations must run on [Schedulers.single] to support
|
||||||
|
* the global "I am changing the number" lock exclusivity.
|
||||||
|
*/
|
||||||
|
class ChangeNumberRepository(
|
||||||
|
private val accountManager: SignalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager(),
|
||||||
|
private val messageSender: SignalServiceMessageSender = ApplicationDependencies.getSignalServiceMessageSender()
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* This lock should be held by anyone who is performing a change number operation, so that two different parties cannot change the user's number
|
||||||
|
* at the same time.
|
||||||
|
*/
|
||||||
|
val CHANGE_NUMBER_LOCK = ReentrantLock()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds Rx operators to chain to acquire and release the [CHANGE_NUMBER_LOCK] on subscribe and on finish.
|
||||||
|
*/
|
||||||
|
fun <T : Any> acquireReleaseChangeNumberLock(upstream: Single<T>): Single<T> {
|
||||||
|
return upstream.doOnSubscribe {
|
||||||
|
CHANGE_NUMBER_LOCK.lock()
|
||||||
|
SignalStore.misc().lockChangeNumber()
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.single())
|
||||||
|
.observeOn(Schedulers.single())
|
||||||
|
.doFinally {
|
||||||
|
if (CHANGE_NUMBER_LOCK.isHeldByCurrentThread) {
|
||||||
|
CHANGE_NUMBER_LOCK.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun ensureDecryptionsDrained(): Completable {
|
fun ensureDecryptionsDrained(): Completable {
|
||||||
return Completable.create { emitter ->
|
return Completable.create { emitter ->
|
||||||
@@ -56,15 +89,38 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
|||||||
.addDecryptionDrainedListener {
|
.addDecryptionDrainedListener {
|
||||||
emitter.onComplete()
|
emitter.onComplete()
|
||||||
}
|
}
|
||||||
}.subscribeOn(Schedulers.io())
|
}.subscribeOn(Schedulers.single())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeNumber(code: String, newE164: String): Single<ServiceResponse<VerifyAccountResponse>> {
|
fun changeNumber(code: String, newE164: String, pniUpdateMode: Boolean = false): Single<ServiceResponse<VerifyAccountResponse>> {
|
||||||
return Single.fromCallable {
|
return Single.fromCallable {
|
||||||
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(code, newE164, null)
|
var completed = false
|
||||||
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
var attempts = 0
|
||||||
accountManager.changeNumber(request)
|
lateinit var changeNumberResponse: ServiceResponse<VerifyAccountResponse>
|
||||||
}.subscribeOn(Schedulers.io())
|
|
||||||
|
while (!completed && attempts < 5) {
|
||||||
|
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(
|
||||||
|
code = code,
|
||||||
|
newE164 = newE164,
|
||||||
|
registrationLock = null,
|
||||||
|
pniUpdateMode = pniUpdateMode
|
||||||
|
)
|
||||||
|
|
||||||
|
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
||||||
|
|
||||||
|
changeNumberResponse = accountManager.changeNumber(request)
|
||||||
|
|
||||||
|
val possibleError: Throwable? = changeNumberResponse.applicationError.orElse(null)
|
||||||
|
if (possibleError is MismatchedDevicesException) {
|
||||||
|
messageSender.handleChangeNumberMismatchDevices(possibleError.mismatchedDevices)
|
||||||
|
attempts++
|
||||||
|
} else {
|
||||||
|
completed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeNumberResponse
|
||||||
|
}.subscribeOn(Schedulers.single())
|
||||||
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,42 +131,65 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
|||||||
tokenData: TokenData
|
tokenData: TokenData
|
||||||
): Single<ServiceResponse<VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse>> {
|
): Single<ServiceResponse<VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse>> {
|
||||||
return Single.fromCallable {
|
return Single.fromCallable {
|
||||||
|
val kbsData: KbsPinData
|
||||||
|
val registrationLock: String
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val kbsData: KbsPinData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!!
|
kbsData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!!
|
||||||
val registrationLock: String = kbsData.masterKey.deriveRegistrationLock()
|
registrationLock = kbsData.masterKey.deriveRegistrationLock()
|
||||||
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(code, newE164, registrationLock)
|
} catch (e: KeyBackupSystemWrongPinException) {
|
||||||
|
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||||
|
} catch (e: KeyBackupSystemNoDataException) {
|
||||||
|
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var completed = false
|
||||||
|
var attempts = 0
|
||||||
|
lateinit var changeNumberResponse: ServiceResponse<VerifyAccountResponse>
|
||||||
|
|
||||||
|
while (!completed && attempts < 5) {
|
||||||
|
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(
|
||||||
|
code = code,
|
||||||
|
newE164 = newE164,
|
||||||
|
registrationLock = registrationLock,
|
||||||
|
pniUpdateMode = false
|
||||||
|
)
|
||||||
|
|
||||||
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
||||||
|
|
||||||
val response: ServiceResponse<VerifyAccountResponse> = accountManager.changeNumber(request)
|
changeNumberResponse = accountManager.changeNumber(request)
|
||||||
VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse.from(response, kbsData)
|
|
||||||
} catch (e: KeyBackupSystemWrongPinException) {
|
val possibleError: Throwable? = changeNumberResponse.applicationError.orElse(null)
|
||||||
ServiceResponse.forExecutionError(e)
|
if (possibleError is MismatchedDevicesException) {
|
||||||
} catch (e: KeyBackupSystemNoDataException) {
|
messageSender.handleChangeNumberMismatchDevices(possibleError.mismatchedDevices)
|
||||||
ServiceResponse.forExecutionError(e)
|
attempts++
|
||||||
} catch (e: IOException) {
|
} else {
|
||||||
ServiceResponse.forExecutionError(e)
|
completed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.subscribeOn(Schedulers.io())
|
|
||||||
|
VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse.from(changeNumberResponse, kbsData)
|
||||||
|
}.subscribeOn(Schedulers.single())
|
||||||
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UsePropertyAccessSyntax")
|
@Suppress("UsePropertyAccessSyntax")
|
||||||
fun whoAmI(): Single<WhoAmIResponse> {
|
fun whoAmI(): Single<WhoAmIResponse> {
|
||||||
return Single.fromCallable { ApplicationDependencies.getSignalServiceAccountManager().getWhoAmI() }
|
return Single.fromCallable { ApplicationDependencies.getSignalServiceAccountManager().getWhoAmI() }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.single())
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun changeLocalNumber(e164: String, pni: PNI): Single<Unit> {
|
fun changeLocalNumber(e164: String, pni: PNI): Single<Unit> {
|
||||||
val oldStorageId: ByteArray? = Recipient.self().storageServiceId
|
val oldStorageId: ByteArray? = Recipient.self().storageServiceId
|
||||||
SignalDatabase.recipients.updateSelfPhone(e164)
|
SignalDatabase.recipients.updateSelfPhone(e164, pni)
|
||||||
val newStorageId: ByteArray? = Recipient.self().storageServiceId
|
val newStorageId: ByteArray? = Recipient.self().storageServiceId
|
||||||
|
|
||||||
if (MessageDigest.isEqual(oldStorageId, newStorageId)) {
|
if (e164 != SignalStore.account().requireE164() && MessageDigest.isEqual(oldStorageId, newStorageId)) {
|
||||||
Log.w(TAG, "Self storage id was not rotated, attempting to rotate again")
|
Log.w(TAG, "Self storage id was not rotated, attempting to rotate again")
|
||||||
SignalDatabase.recipients.rotateStorageId(Recipient.self().id)
|
SignalDatabase.recipients.rotateStorageId(Recipient.self().id)
|
||||||
Recipient.self().live().refresh()
|
|
||||||
StorageSyncHelper.scheduleSyncForDataChange()
|
StorageSyncHelper.scheduleSyncForDataChange()
|
||||||
val secondAttemptStorageId: ByteArray? = Recipient.self().storageServiceId
|
val secondAttemptStorageId: ByteArray? = Recipient.self().storageServiceId
|
||||||
if (MessageDigest.isEqual(oldStorageId, secondAttemptStorageId)) {
|
if (MessageDigest.isEqual(oldStorageId, secondAttemptStorageId)) {
|
||||||
@@ -118,7 +197,7 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalDatabase.recipients.setPni(Recipient.self().id, pni)
|
ApplicationDependencies.getRecipientCache().clear()
|
||||||
|
|
||||||
SignalStore.account().setE164(e164)
|
SignalStore.account().setE164(e164)
|
||||||
SignalStore.account().setPni(pni)
|
SignalStore.account().setPni(pni)
|
||||||
@@ -161,6 +240,9 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
|||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SignalStore.misc().setPniInitializedDevices(true)
|
||||||
|
ApplicationDependencies.getGroupsV2Authorization().clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
Recipient.self().live().refresh()
|
Recipient.self().live().refresh()
|
||||||
@@ -190,7 +272,7 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
|||||||
|
|
||||||
SignalStore.certificateValues().setUnidentifiedAccessCertificate(certificateType, certificate)
|
SignalStore.certificateValues().setUnidentifiedAccessCertificate(certificateType, certificate)
|
||||||
}
|
}
|
||||||
}.subscribeOn(Schedulers.io())
|
}.subscribeOn(Schedulers.single())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UsePropertyAccessSyntax")
|
@Suppress("UsePropertyAccessSyntax")
|
||||||
@@ -198,51 +280,69 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
|||||||
private fun createChangeNumberRequest(
|
private fun createChangeNumberRequest(
|
||||||
code: String,
|
code: String,
|
||||||
newE164: String,
|
newE164: String,
|
||||||
registrationLock: String?
|
registrationLock: String?,
|
||||||
|
pniUpdateMode: Boolean
|
||||||
): ChangeNumberRequestData {
|
): ChangeNumberRequestData {
|
||||||
val messageSender: SignalServiceMessageSender = ApplicationDependencies.getSignalServiceMessageSender()
|
val selfIdentifier: String = SignalStore.account().requireAci().toString()
|
||||||
val pniProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().pni()
|
val aciProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().aci()
|
||||||
val pniMetadataStore: PreKeyMetadataStore = SignalStore.account().pniPreKeys
|
|
||||||
|
|
||||||
val devices: List<DeviceInfo> = accountManager.getDevices()
|
val pniIdentity: IdentityKeyPair = if (pniUpdateMode) SignalStore.account().pniIdentityKey else IdentityKeyUtil.generateIdentityKeyPair()
|
||||||
|
|
||||||
val pniIdentity: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
|
||||||
val deviceMessages = mutableListOf<OutgoingPushMessage>()
|
val deviceMessages = mutableListOf<OutgoingPushMessage>()
|
||||||
val devicePniSignedPreKeys = mutableMapOf<String, SignedPreKeyEntity>()
|
val devicePniSignedPreKeys = mutableMapOf<Int, SignedPreKeyEntity>()
|
||||||
val pniRegistrationIds = mutableMapOf<String, Int>()
|
val pniRegistrationIds = mutableMapOf<Int, Int>()
|
||||||
val primaryDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID.toString()
|
val primaryDeviceId: Int = SignalServiceAddress.DEFAULT_DEVICE_ID
|
||||||
|
|
||||||
for (device in devices) {
|
val devices: List<Int> = listOf(primaryDeviceId) + aciProtocolStore.getSubDeviceSessions(selfIdentifier)
|
||||||
val deviceId = device.id.toString()
|
|
||||||
|
|
||||||
// Signed Prekeys
|
devices
|
||||||
val signedPreKeyRecord = if (deviceId == primaryDeviceId) {
|
.filter { it == primaryDeviceId || aciProtocolStore.containsSession(SignalProtocolAddress(selfIdentifier, it)) }
|
||||||
PreKeyUtil.generateAndStoreSignedPreKey(pniProtocolStore, pniMetadataStore, pniIdentity.privateKey, false)
|
.forEach { deviceId ->
|
||||||
} else {
|
// Signed Prekeys
|
||||||
PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
|
val signedPreKeyRecord = if (deviceId == primaryDeviceId) {
|
||||||
|
if (pniUpdateMode) {
|
||||||
|
ApplicationDependencies.getProtocolStore().pni().loadSignedPreKey(SignalStore.account().pniPreKeys.activeSignedPreKeyId)
|
||||||
|
} else {
|
||||||
|
PreKeyUtil.generateAndStoreSignedPreKey(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, pniIdentity.privateKey)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
|
||||||
|
}
|
||||||
|
devicePniSignedPreKeys[deviceId] = SignedPreKeyEntity(signedPreKeyRecord.id, signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature)
|
||||||
|
|
||||||
|
// Registration Ids
|
||||||
|
var pniRegistrationId = if (deviceId == primaryDeviceId && pniUpdateMode) {
|
||||||
|
SignalStore.account().pniRegistrationId
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pniRegistrationId < 0 || pniRegistrationIds.values.contains(pniRegistrationId)) {
|
||||||
|
pniRegistrationId = KeyHelper.generateRegistrationId(false)
|
||||||
|
}
|
||||||
|
pniRegistrationIds[deviceId] = pniRegistrationId
|
||||||
|
|
||||||
|
// Device Messages
|
||||||
|
if (deviceId != primaryDeviceId) {
|
||||||
|
val pniChangeNumber = SyncMessage.PniChangeNumber.newBuilder()
|
||||||
|
.setIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
|
||||||
|
.setSignedPreKey(signedPreKeyRecord.serialize().toProtoByteString())
|
||||||
|
.setRegistrationId(pniRegistrationId)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
deviceMessages += messageSender.getEncryptedSyncPniChangeNumberMessage(deviceId, pniChangeNumber)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
devicePniSignedPreKeys[deviceId] = SignedPreKeyEntity(signedPreKeyRecord.id, signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature)
|
|
||||||
|
|
||||||
// Registration Ids
|
val request = ChangePhoneNumberRequest(
|
||||||
var pniRegistrationId = -1
|
newE164,
|
||||||
while (pniRegistrationId < 0 || pniRegistrationIds.values.contains(pniRegistrationId)) {
|
code,
|
||||||
pniRegistrationId = KeyHelper.generateRegistrationId(false)
|
registrationLock,
|
||||||
}
|
pniIdentity.publicKey,
|
||||||
pniRegistrationIds[deviceId] = pniRegistrationId
|
deviceMessages,
|
||||||
|
devicePniSignedPreKeys.mapKeys { it.key.toString() },
|
||||||
|
pniRegistrationIds.mapKeys { it.key.toString() }
|
||||||
|
)
|
||||||
|
|
||||||
// Device Messages
|
|
||||||
if (deviceId != primaryDeviceId) {
|
|
||||||
val pniChangeNumber = SyncMessage.PniChangeNumber.newBuilder()
|
|
||||||
.setIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
|
|
||||||
.setSignedPreKey(signedPreKeyRecord.serialize().toProtoByteString())
|
|
||||||
.setRegistrationId(pniRegistrationId)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
deviceMessages += messageSender.getEncryptedSyncPniChangeNumberMessage(device.id, pniChangeNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val request = ChangePhoneNumberRequest(newE164, code, registrationLock, pniIdentity.publicKey, deviceMessages, devicePniSignedPreKeys, pniRegistrationIds)
|
|
||||||
val metadata = PendingChangeNumberMetadata.newBuilder()
|
val metadata = PendingChangeNumberMetadata.newBuilder()
|
||||||
.setPreviousPni(SignalStore.account().pni!!.toByteString())
|
.setPreviousPni(SignalStore.account().pni!!.toByteString())
|
||||||
.setPniIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
|
.setPniIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||||
import org.thoughtcrime.securesms.pin.TokenData
|
import org.thoughtcrime.securesms.pin.TokenData
|
||||||
|
import org.thoughtcrime.securesms.registration.SmsRetrieverReceiver
|
||||||
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
|
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
|
||||||
import org.thoughtcrime.securesms.registration.VerifyAccountResponseProcessor
|
import org.thoughtcrime.securesms.registration.VerifyAccountResponseProcessor
|
||||||
import org.thoughtcrime.securesms.registration.VerifyAccountResponseWithoutKbs
|
import org.thoughtcrime.securesms.registration.VerifyAccountResponseWithoutKbs
|
||||||
@@ -38,6 +39,7 @@ class ChangeNumberViewModel(
|
|||||||
password: String,
|
password: String,
|
||||||
verifyAccountRepository: VerifyAccountRepository,
|
verifyAccountRepository: VerifyAccountRepository,
|
||||||
kbsRepository: KbsRepository,
|
kbsRepository: KbsRepository,
|
||||||
|
private val smsRetrieverReceiver: SmsRetrieverReceiver = SmsRetrieverReceiver(ApplicationDependencies.getApplication())
|
||||||
) : BaseRegistrationViewModel(savedState, verifyAccountRepository, kbsRepository, password) {
|
) : BaseRegistrationViewModel(savedState, verifyAccountRepository, kbsRepository, password) {
|
||||||
|
|
||||||
var oldNumberState: NumberViewState = NumberViewState.Builder().build()
|
var oldNumberState: NumberViewState = NumberViewState.Builder().build()
|
||||||
@@ -57,6 +59,13 @@ class ChangeNumberViewModel(
|
|||||||
} catch (e: NumberParseException) {
|
} catch (e: NumberParseException) {
|
||||||
Log.i(TAG, "Unable to parse number for default country code")
|
Log.i(TAG, "Unable to parse number for default country code")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smsRetrieverReceiver.registerReceiver()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
smsRetrieverReceiver.unregisterReceiver()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLiveOldNumber(): LiveData<NumberViewState> {
|
fun getLiveOldNumber(): LiveData<NumberViewState> {
|
||||||
@@ -114,13 +123,13 @@ class ChangeNumberViewModel(
|
|||||||
|
|
||||||
override fun verifyCodeWithoutRegistrationLock(code: String): Single<VerifyAccountResponseProcessor> {
|
override fun verifyCodeWithoutRegistrationLock(code: String): Single<VerifyAccountResponseProcessor> {
|
||||||
return super.verifyCodeWithoutRegistrationLock(code)
|
return super.verifyCodeWithoutRegistrationLock(code)
|
||||||
.doOnSubscribe { SignalStore.misc().lockChangeNumber() }
|
.compose(ChangeNumberRepository::acquireReleaseChangeNumberLock)
|
||||||
.flatMap(this::attemptToUnlockChangeNumber)
|
.flatMap(this::attemptToUnlockChangeNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verifyCodeAndRegisterAccountWithRegistrationLock(pin: String): Single<VerifyCodeWithRegistrationLockResponseProcessor> {
|
override fun verifyCodeAndRegisterAccountWithRegistrationLock(pin: String): Single<VerifyCodeWithRegistrationLockResponseProcessor> {
|
||||||
return super.verifyCodeAndRegisterAccountWithRegistrationLock(pin)
|
return super.verifyCodeAndRegisterAccountWithRegistrationLock(pin)
|
||||||
.doOnSubscribe { SignalStore.misc().lockChangeNumber() }
|
.compose(ChangeNumberRepository::acquireReleaseChangeNumberLock)
|
||||||
.flatMap(this::attemptToUnlockChangeNumber)
|
.flatMap(this::attemptToUnlockChangeNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__chats) {
|
class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__chats) {
|
||||||
@@ -19,7 +19,7 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
|
|||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
val repository = ChatsSettingsRepository()
|
val repository = ChatsSettingsRepository()
|
||||||
val factory = ChatsSettingsViewModel.Factory(repository)
|
val factory = ChatsSettingsViewModel.Factory(repository)
|
||||||
viewModel = ViewModelProvider(this, factory)[ChatsSettingsViewModel::class.java]
|
viewModel = ViewModelProvider(this, factory)[ChatsSettingsViewModel::class.java]
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.signal.core.util.concurrent.SignalExecutors
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.util.SmsUtil
|
import org.thoughtcrime.securesms.util.SmsUtil
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
private const val SMS_REQUEST_CODE: Short = 1234
|
private const val SMS_REQUEST_CODE: Short = 1234
|
||||||
@@ -22,13 +30,20 @@ private const val SMS_REQUEST_CODE: Short = 1234
|
|||||||
class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
|
class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
|
||||||
|
|
||||||
private lateinit var viewModel: SmsSettingsViewModel
|
private lateinit var viewModel: SmsSettingsViewModel
|
||||||
|
private lateinit var smsExportLauncher: ActivityResultLauncher<Intent>
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
viewModel.checkSmsEnabled()
|
viewModel.checkSmsEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
|
smsExportLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
|
showSmsRemovalDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModel = ViewModelProvider(this)[SmsSettingsViewModel::class.java]
|
viewModel = ViewModelProvider(this)[SmsSettingsViewModel::class.java]
|
||||||
|
|
||||||
viewModel.state.observe(viewLifecycleOwner) {
|
viewModel.state.observe(viewLifecycleOwner) {
|
||||||
@@ -42,6 +57,32 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
|
|||||||
|
|
||||||
private fun getConfiguration(state: SmsSettingsState): DSLConfiguration {
|
private fun getConfiguration(state: SmsSettingsState): DSLConfiguration {
|
||||||
return configure {
|
return configure {
|
||||||
|
when (state.smsExportState) {
|
||||||
|
SmsSettingsState.SmsExportState.FETCHING -> Unit
|
||||||
|
SmsSettingsState.SmsExportState.HAS_UNEXPORTED_MESSAGES -> {
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.SmsSettingsFragment__export_sms_messages),
|
||||||
|
onClick = {
|
||||||
|
smsExportLauncher.launch(SmsExportActivity.createIntent(requireContext()))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dividerPref()
|
||||||
|
}
|
||||||
|
SmsSettingsState.SmsExportState.ALL_MESSAGES_EXPORTED -> {
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.SmsSettingsFragment__remove_sms_messages),
|
||||||
|
onClick = {
|
||||||
|
showSmsRemovalDialog()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dividerPref()
|
||||||
|
}
|
||||||
|
SmsSettingsState.SmsExportState.NO_SMS_MESSAGES_IN_DATABASE -> Unit
|
||||||
|
SmsSettingsState.SmsExportState.NOT_AVAILABLE -> Unit
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
clickPref(
|
clickPref(
|
||||||
title = DSLSettingsText.from(R.string.SmsSettingsFragment__use_as_default_sms_app),
|
title = DSLSettingsText.from(R.string.SmsSettingsFragment__use_as_default_sms_app),
|
||||||
@@ -96,4 +137,21 @@ class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
|
|||||||
|
|
||||||
startActivityForResult(intent, SMS_REQUEST_CODE.toInt())
|
startActivityForResult(intent, SMS_REQUEST_CODE.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showSmsRemovalDialog() {
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.RemoveSmsMessagesDialogFragment__remove_sms_messages)
|
||||||
|
.setMessage(R.string.RemoveSmsMessagesDialogFragment__you_can_now_remove_sms_messages_from_signal)
|
||||||
|
.setPositiveButton(R.string.RemoveSmsMessagesDialogFragment__keep_messages) { _, _ ->
|
||||||
|
Snackbar.make(requireView(), R.string.SmsSettingsFragment__you_can_remove_sms_messages_from_signal_in_settings, Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.RemoveSmsMessagesDialogFragment__remove_messages) { _, _ ->
|
||||||
|
SignalExecutors.BOUNDED.execute {
|
||||||
|
SignalDatabase.sms.deleteExportedMessages()
|
||||||
|
SignalDatabase.mms.deleteExportedMessages()
|
||||||
|
}
|
||||||
|
Snackbar.make(requireView(), R.string.SmsSettingsFragment__removing_sms_messages_from_signal, Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||||
|
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import org.thoughtcrime.securesms.database.MessageDatabase
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||||
|
|
||||||
|
class SmsSettingsRepository(
|
||||||
|
private val smsDatabase: MessageDatabase = SignalDatabase.sms,
|
||||||
|
private val mmsDatabase: MessageDatabase = SignalDatabase.mms
|
||||||
|
) {
|
||||||
|
fun getSmsExportState(): Single<SmsSettingsState.SmsExportState> {
|
||||||
|
if (!FeatureFlags.smsExporter()) {
|
||||||
|
return Single.just(SmsSettingsState.SmsExportState.NOT_AVAILABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Single.fromCallable {
|
||||||
|
checkInsecureMessageCount() ?: checkUnexportedInsecureMessageCount()
|
||||||
|
}.subscribeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun checkInsecureMessageCount(): SmsSettingsState.SmsExportState? {
|
||||||
|
val totalSmsMmsCount = smsDatabase.insecureMessageCount + mmsDatabase.insecureMessageCount
|
||||||
|
|
||||||
|
return if (totalSmsMmsCount == 0) {
|
||||||
|
SmsSettingsState.SmsExportState.NO_SMS_MESSAGES_IN_DATABASE
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun checkUnexportedInsecureMessageCount(): SmsSettingsState.SmsExportState {
|
||||||
|
val totalUnexportedCount = smsDatabase.unexportedInsecureMessagesCount + mmsDatabase.unexportedInsecureMessagesCount
|
||||||
|
|
||||||
|
return if (totalUnexportedCount > 0) {
|
||||||
|
SmsSettingsState.SmsExportState.HAS_UNEXPORTED_MESSAGES
|
||||||
|
} else {
|
||||||
|
SmsSettingsState.SmsExportState.ALL_MESSAGES_EXPORTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,5 +3,14 @@ package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
|||||||
data class SmsSettingsState(
|
data class SmsSettingsState(
|
||||||
val useAsDefaultSmsApp: Boolean,
|
val useAsDefaultSmsApp: Boolean,
|
||||||
val smsDeliveryReportsEnabled: Boolean,
|
val smsDeliveryReportsEnabled: Boolean,
|
||||||
val wifiCallingCompatibilityEnabled: Boolean
|
val wifiCallingCompatibilityEnabled: Boolean,
|
||||||
)
|
val smsExportState: SmsExportState = SmsExportState.FETCHING
|
||||||
|
) {
|
||||||
|
enum class SmsExportState {
|
||||||
|
FETCHING,
|
||||||
|
HAS_UNEXPORTED_MESSAGES,
|
||||||
|
ALL_MESSAGES_EXPORTED,
|
||||||
|
NO_SMS_MESSAGES_IN_DATABASE,
|
||||||
|
NOT_AVAILABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
@@ -9,6 +11,9 @@ import org.thoughtcrime.securesms.util.livedata.Store
|
|||||||
|
|
||||||
class SmsSettingsViewModel : ViewModel() {
|
class SmsSettingsViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val repository = SmsSettingsRepository()
|
||||||
|
|
||||||
|
private val disposables = CompositeDisposable()
|
||||||
private val store = Store(
|
private val store = Store(
|
||||||
SmsSettingsState(
|
SmsSettingsState(
|
||||||
useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication()),
|
useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication()),
|
||||||
@@ -19,6 +24,16 @@ class SmsSettingsViewModel : ViewModel() {
|
|||||||
|
|
||||||
val state: LiveData<SmsSettingsState> = store.stateLiveData
|
val state: LiveData<SmsSettingsState> = store.stateLiveData
|
||||||
|
|
||||||
|
init {
|
||||||
|
disposables += repository.getSmsExportState().subscribe { state ->
|
||||||
|
store.update { it.copy(smsExportState = state) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
disposables.clear()
|
||||||
|
}
|
||||||
|
|
||||||
fun setSmsDeliveryReportsEnabled(enabled: Boolean) {
|
fun setSmsDeliveryReportsEnabled(enabled: Boolean) {
|
||||||
store.update { it.copy(smsDeliveryReportsEnabled = enabled) }
|
store.update { it.copy(smsDeliveryReportsEnabled = enabled) }
|
||||||
SignalStore.settings().isSmsDeliveryReportsEnabled = enabled
|
SignalStore.settings().isSmsDeliveryReportsEnabled = enabled
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import androidx.navigation.Navigation
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.mms.SentMediaQuality
|
import org.thoughtcrime.securesms.mms.SentMediaQuality
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode
|
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -31,7 +31,7 @@ class DataAndStorageSettingsFragment : DSLSettingsFragment(R.string.preferences_
|
|||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
val repository = DataAndStorageSettingsRepository()
|
val repository = DataAndStorageSettingsRepository()
|
||||||
val factory = DataAndStorageSettingsViewModel.Factory(preferences, repository)
|
val factory = DataAndStorageSettingsViewModel.Factory(preferences, repository)
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import androidx.navigation.Navigation
|
|||||||
import org.thoughtcrime.securesms.BuildConfig
|
import org.thoughtcrime.securesms.BuildConfig
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
class HelpSettingsFragment : DSLSettingsFragment(R.string.preferences__help) {
|
class HelpSettingsFragment : DSLSettingsFragment(R.string.preferences__help) {
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
adapter.submitList(getConfiguration().toMappingModelList())
|
adapter.submitList(getConfiguration().toMappingModelList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import org.signal.ringrtc.CallManager
|
|||||||
import org.thoughtcrime.securesms.BuildConfig
|
import org.thoughtcrime.securesms.BuildConfig
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.database.LocalMetricsDatabase
|
import org.thoughtcrime.securesms.database.LocalMetricsDatabase
|
||||||
import org.thoughtcrime.securesms.database.LogDatabase
|
import org.thoughtcrime.securesms.database.LogDatabase
|
||||||
|
import org.thoughtcrime.securesms.database.MegaphoneDatabase
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||||
@@ -35,10 +35,12 @@ import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
|||||||
import org.thoughtcrime.securesms.jobs.SubscriptionKeepAliveJob
|
import org.thoughtcrime.securesms.jobs.SubscriptionKeepAliveJob
|
||||||
import org.thoughtcrime.securesms.jobs.SubscriptionReceiptRequestResponseJob
|
import org.thoughtcrime.securesms.jobs.SubscriptionReceiptRequestResponseJob
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository
|
||||||
|
import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||||
import org.thoughtcrime.securesms.payments.DataExportUtil
|
import org.thoughtcrime.securesms.payments.DataExportUtil
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -48,7 +50,7 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||||||
|
|
||||||
private lateinit var viewModel: InternalSettingsViewModel
|
private lateinit var viewModel: InternalSettingsViewModel
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
val repository = InternalSettingsRepository(requireContext())
|
val repository = InternalSettingsRepository(requireContext())
|
||||||
val factory = InternalSettingsViewModel.Factory(repository)
|
val factory = InternalSettingsViewModel.Factory(repository)
|
||||||
viewModel = ViewModelProvider(this, factory)[InternalSettingsViewModel::class.java]
|
viewModel = ViewModelProvider(this, factory)[InternalSettingsViewModel::class.java]
|
||||||
@@ -168,15 +170,6 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||||||
|
|
||||||
sectionHeaderPref(R.string.preferences__internal_preferences_groups_v2)
|
sectionHeaderPref(R.string.preferences__internal_preferences_groups_v2)
|
||||||
|
|
||||||
switchPref(
|
|
||||||
title = DSLSettingsText.from(R.string.preferences__internal_do_not_create_gv2),
|
|
||||||
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_create_gv2_description),
|
|
||||||
isChecked = state.gv2doNotCreateGv2Groups,
|
|
||||||
onClick = {
|
|
||||||
viewModel.setGv2DoNotCreateGv2Groups(!state.gv2doNotCreateGv2Groups)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
switchPref(
|
switchPref(
|
||||||
title = DSLSettingsText.from(R.string.preferences__internal_force_gv2_invites),
|
title = DSLSettingsText.from(R.string.preferences__internal_force_gv2_invites),
|
||||||
summary = DSLSettingsText.from(R.string.preferences__internal_force_gv2_invites_description),
|
summary = DSLSettingsText.from(R.string.preferences__internal_force_gv2_invites_description),
|
||||||
@@ -206,28 +199,6 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||||||
|
|
||||||
dividerPref()
|
dividerPref()
|
||||||
|
|
||||||
sectionHeaderPref(R.string.preferences__internal_preferences_groups_v1_migration)
|
|
||||||
|
|
||||||
switchPref(
|
|
||||||
title = DSLSettingsText.from(R.string.preferences__internal_do_not_initiate_automigrate),
|
|
||||||
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_initiate_automigrate_description),
|
|
||||||
isChecked = state.disableAutoMigrationInitiation,
|
|
||||||
onClick = {
|
|
||||||
viewModel.setDisableAutoMigrationInitiation(!state.disableAutoMigrationInitiation)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
switchPref(
|
|
||||||
title = DSLSettingsText.from(R.string.preferences__internal_do_not_notify_automigrate),
|
|
||||||
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_notify_automigrate_description),
|
|
||||||
isChecked = state.disableAutoMigrationNotification,
|
|
||||||
onClick = {
|
|
||||||
viewModel.setDisableAutoMigrationNotification(!state.disableAutoMigrationNotification)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
dividerPref()
|
|
||||||
|
|
||||||
sectionHeaderPref(R.string.preferences__internal_network)
|
sectionHeaderPref(R.string.preferences__internal_network)
|
||||||
|
|
||||||
switchPref(
|
switchPref(
|
||||||
@@ -393,7 +364,7 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (FeatureFlags.donorBadges() && SignalStore.donationsValues().getSubscriber() != null) {
|
if (SignalStore.donationsValues().getSubscriber() != null) {
|
||||||
dividerPref()
|
dividerPref()
|
||||||
|
|
||||||
sectionHeaderPref(R.string.preferences__internal_badges)
|
sectionHeaderPref(R.string.preferences__internal_badges)
|
||||||
@@ -431,6 +402,19 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.preferences__internal_reset_donation_megaphone),
|
||||||
|
onClick = {
|
||||||
|
SignalDatabase.remoteMegaphones.debugRemoveAll()
|
||||||
|
MegaphoneDatabase.getInstance(ApplicationDependencies.getApplication()).let {
|
||||||
|
it.delete(Megaphones.Event.REMOTE_MEGAPHONE)
|
||||||
|
it.markFirstVisible(Megaphones.Event.DONATE_Q2_2022, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31))
|
||||||
|
}
|
||||||
|
// Force repository database cache refresh
|
||||||
|
MegaphoneRepository(ApplicationDependencies.getApplication()).onFirstEverAppLaunch()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
clickPref(
|
clickPref(
|
||||||
title = DSLSettingsText.from(R.string.preferences__internal_fetch_release_channel),
|
title = DSLSettingsText.from(R.string.preferences__internal_fetch_release_channel),
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -478,14 +462,6 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||||||
|
|
||||||
sectionHeaderPref(R.string.ConversationListTabs__stories)
|
sectionHeaderPref(R.string.ConversationListTabs__stories)
|
||||||
|
|
||||||
switchPref(
|
|
||||||
title = DSLSettingsText.from(R.string.preferences__internal_disable_stories),
|
|
||||||
isChecked = state.disableStories,
|
|
||||||
onClick = {
|
|
||||||
viewModel.toggleStories()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
clickPref(
|
clickPref(
|
||||||
title = DSLSettingsText.from(R.string.preferences__internal_clear_onboarding_state),
|
title = DSLSettingsText.from(R.string.preferences__internal_clear_onboarding_state),
|
||||||
summary = DSLSettingsText.from(R.string.preferences__internal_clears_onboarding_flag_and_triggers_download_of_onboarding_stories),
|
summary = DSLSettingsText.from(R.string.preferences__internal_clears_onboarding_flag_and_triggers_download_of_onboarding_stories),
|
||||||
@@ -494,6 +470,13 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||||||
viewModel.onClearOnboardingState()
|
viewModel.onClearOnboardingState()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.preferences__internal_stories_dialog_launcher),
|
||||||
|
onClick = {
|
||||||
|
findNavController().safeNavigate(InternalSettingsFragmentDirections.actionInternalSettingsFragmentToStoryDialogsLauncherFragment())
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ import org.thoughtcrime.securesms.emoji.EmojiFiles
|
|||||||
data class InternalSettingsState(
|
data class InternalSettingsState(
|
||||||
val seeMoreUserDetails: Boolean,
|
val seeMoreUserDetails: Boolean,
|
||||||
val shakeToReport: Boolean,
|
val shakeToReport: Boolean,
|
||||||
val gv2doNotCreateGv2Groups: Boolean,
|
|
||||||
val gv2forceInvites: Boolean,
|
val gv2forceInvites: Boolean,
|
||||||
val gv2ignoreServerChanges: Boolean,
|
val gv2ignoreServerChanges: Boolean,
|
||||||
val gv2ignoreP2PChanges: Boolean,
|
val gv2ignoreP2PChanges: Boolean,
|
||||||
val disableAutoMigrationInitiation: Boolean,
|
|
||||||
val disableAutoMigrationNotification: Boolean,
|
|
||||||
val allowCensorshipSetting: Boolean,
|
val allowCensorshipSetting: Boolean,
|
||||||
val callingServer: String,
|
val callingServer: String,
|
||||||
val callingAudioProcessingMethod: CallManager.AudioProcessingMethod,
|
val callingAudioProcessingMethod: CallManager.AudioProcessingMethod,
|
||||||
@@ -22,6 +19,5 @@ data class InternalSettingsState(
|
|||||||
val removeSenderKeyMinimium: Boolean,
|
val removeSenderKeyMinimium: Boolean,
|
||||||
val delayResends: Boolean,
|
val delayResends: Boolean,
|
||||||
val disableStorageService: Boolean,
|
val disableStorageService: Boolean,
|
||||||
val disableStories: Boolean,
|
|
||||||
val canClearOnboardingState: Boolean
|
val canClearOnboardingState: Boolean
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.signal.ringrtc.CallManager
|
|||||||
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob
|
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob
|
||||||
import org.thoughtcrime.securesms.keyvalue.InternalValues
|
import org.thoughtcrime.securesms.keyvalue.InternalValues
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.stories.Stories
|
import org.thoughtcrime.securesms.stories.Stories
|
||||||
import org.thoughtcrime.securesms.util.livedata.Store
|
import org.thoughtcrime.securesms.util.livedata.Store
|
||||||
|
|
||||||
@@ -38,11 +39,6 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setGv2DoNotCreateGv2Groups(enabled: Boolean) {
|
|
||||||
preferenceDataStore.putBoolean(InternalValues.GV2_DO_NOT_CREATE_GV2, enabled)
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setGv2ForceInvites(enabled: Boolean) {
|
fun setGv2ForceInvites(enabled: Boolean) {
|
||||||
preferenceDataStore.putBoolean(InternalValues.GV2_FORCE_INVITES, enabled)
|
preferenceDataStore.putBoolean(InternalValues.GV2_FORCE_INVITES, enabled)
|
||||||
refresh()
|
refresh()
|
||||||
@@ -58,16 +54,6 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDisableAutoMigrationInitiation(enabled: Boolean) {
|
|
||||||
preferenceDataStore.putBoolean(InternalValues.GV2_DISABLE_AUTOMIGRATE_INITIATION, enabled)
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDisableAutoMigrationNotification(enabled: Boolean) {
|
|
||||||
preferenceDataStore.putBoolean(InternalValues.GV2_DISABLE_AUTOMIGRATE_NOTIFICATION, enabled)
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAllowCensorshipSetting(enabled: Boolean) {
|
fun setAllowCensorshipSetting(enabled: Boolean) {
|
||||||
preferenceDataStore.putBoolean(InternalValues.ALLOW_CENSORSHIP_SETTING, enabled)
|
preferenceDataStore.putBoolean(InternalValues.ALLOW_CENSORSHIP_SETTING, enabled)
|
||||||
refresh()
|
refresh()
|
||||||
@@ -108,12 +94,6 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleStories() {
|
|
||||||
val newState = !SignalStore.storyValues().isFeatureDisabled
|
|
||||||
SignalStore.storyValues().isFeatureDisabled = newState
|
|
||||||
store.update { getState().copy(disableStories = newState) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addSampleReleaseNote() {
|
fun addSampleReleaseNote() {
|
||||||
repository.addSampleReleaseNote()
|
repository.addSampleReleaseNote()
|
||||||
}
|
}
|
||||||
@@ -125,12 +105,9 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||||||
private fun getState() = InternalSettingsState(
|
private fun getState() = InternalSettingsState(
|
||||||
seeMoreUserDetails = SignalStore.internalValues().recipientDetails(),
|
seeMoreUserDetails = SignalStore.internalValues().recipientDetails(),
|
||||||
shakeToReport = SignalStore.internalValues().shakeToReport(),
|
shakeToReport = SignalStore.internalValues().shakeToReport(),
|
||||||
gv2doNotCreateGv2Groups = SignalStore.internalValues().gv2DoNotCreateGv2Groups(),
|
|
||||||
gv2forceInvites = SignalStore.internalValues().gv2ForceInvites(),
|
gv2forceInvites = SignalStore.internalValues().gv2ForceInvites(),
|
||||||
gv2ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges(),
|
gv2ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges(),
|
||||||
gv2ignoreP2PChanges = SignalStore.internalValues().gv2IgnoreP2PChanges(),
|
gv2ignoreP2PChanges = SignalStore.internalValues().gv2IgnoreP2PChanges(),
|
||||||
disableAutoMigrationInitiation = SignalStore.internalValues().disableGv1AutoMigrateInitiation(),
|
|
||||||
disableAutoMigrationNotification = SignalStore.internalValues().disableGv1AutoMigrateNotification(),
|
|
||||||
allowCensorshipSetting = SignalStore.internalValues().allowChangingCensorshipSetting(),
|
allowCensorshipSetting = SignalStore.internalValues().allowChangingCensorshipSetting(),
|
||||||
callingServer = SignalStore.internalValues().groupCallingServer(),
|
callingServer = SignalStore.internalValues().groupCallingServer(),
|
||||||
callingAudioProcessingMethod = SignalStore.internalValues().callingAudioProcessingMethod(),
|
callingAudioProcessingMethod = SignalStore.internalValues().callingAudioProcessingMethod(),
|
||||||
@@ -141,13 +118,13 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||||||
removeSenderKeyMinimium = SignalStore.internalValues().removeSenderKeyMinimum(),
|
removeSenderKeyMinimium = SignalStore.internalValues().removeSenderKeyMinimum(),
|
||||||
delayResends = SignalStore.internalValues().delayResends(),
|
delayResends = SignalStore.internalValues().delayResends(),
|
||||||
disableStorageService = SignalStore.internalValues().storageServiceDisabled(),
|
disableStorageService = SignalStore.internalValues().storageServiceDisabled(),
|
||||||
disableStories = SignalStore.storyValues().isFeatureDisabled,
|
|
||||||
canClearOnboardingState = SignalStore.storyValues().hasDownloadedOnboardingStory && Stories.isFeatureEnabled()
|
canClearOnboardingState = SignalStore.storyValues().hasDownloadedOnboardingStory && Stories.isFeatureEnabled()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun onClearOnboardingState() {
|
fun onClearOnboardingState() {
|
||||||
SignalStore.storyValues().hasDownloadedOnboardingStory = false
|
SignalStore.storyValues().hasDownloadedOnboardingStory = false
|
||||||
SignalStore.storyValues().userHasSeenOnboardingStory = false
|
SignalStore.storyValues().userHasSeenOnboardingStory = false
|
||||||
|
Stories.onStorySettingsChanged(Recipient.self().id)
|
||||||
refresh()
|
refresh()
|
||||||
StoryOnboardingDownloadJob.enqueueIfNeeded()
|
StoryOnboardingDownloadJob.enqueueIfNeeded()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.thoughtcrime.securesms.components.settings.app.internal
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
|
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
|
||||||
|
class StoryDialogLauncherFragment : DSLSettingsFragment(titleId = R.string.preferences__internal_stories_dialog_launcher) {
|
||||||
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
|
adapter.submitList(getConfiguration().toMappingModelList())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getConfiguration(): DSLConfiguration {
|
||||||
|
return configure {
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.preferences__internal_retry_send),
|
||||||
|
onClick = {
|
||||||
|
StoryDialogs.resendStory(requireContext()) {
|
||||||
|
Toast.makeText(requireContext(), R.string.preferences__internal_retry_send, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.preferences__internal_story_or_profile_selector),
|
||||||
|
onClick = {
|
||||||
|
StoryDialogs.displayStoryOrProfileImage(
|
||||||
|
context = requireContext(),
|
||||||
|
onViewStory = { Toast.makeText(requireContext(), R.string.StoryDialogs__view_story, Toast.LENGTH_SHORT).show() },
|
||||||
|
onViewAvatar = { Toast.makeText(requireContext(), R.string.StoryDialogs__view_profile_photo, Toast.LENGTH_SHORT).show() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.preferences__internal_hide_story),
|
||||||
|
onClick = {
|
||||||
|
StoryDialogs.hideStory(requireContext(), "Spiderman") {
|
||||||
|
Toast.makeText(requireContext(), R.string.preferences__internal_hide_story, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,19 +5,19 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|||||||
import org.signal.donations.StripeDeclineCode
|
import org.signal.donations.StripeDeclineCode
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.UnexpectedSubscriptionCancellation
|
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.UnexpectedSubscriptionCancellation
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
|
||||||
class DonorErrorConfigurationFragment : DSLSettingsFragment() {
|
class DonorErrorConfigurationFragment : DSLSettingsFragment() {
|
||||||
|
|
||||||
private val viewModel: DonorErrorConfigurationViewModel by viewModels()
|
private val viewModel: DonorErrorConfigurationViewModel by viewModels()
|
||||||
private val lifecycleDisposable = LifecycleDisposable()
|
private val lifecycleDisposable = LifecycleDisposable()
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
lifecycleDisposable += viewModel.state.observeOn(AndroidSchedulers.mainThread()).subscribe { state ->
|
lifecycleDisposable += viewModel.state.observeOn(AndroidSchedulers.mainThread()).subscribe { state ->
|
||||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import androidx.preference.PreferenceManager
|
|||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
@@ -35,6 +34,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels
|
|||||||
import org.thoughtcrime.securesms.util.RingtoneUtil
|
import org.thoughtcrime.securesms.util.RingtoneUtil
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
private const val MESSAGE_SOUND_SELECT: Int = 1
|
private const val MESSAGE_SOUND_SELECT: Int = 1
|
||||||
@@ -70,7 +70,7 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
adapter.registerFactory(
|
adapter.registerFactory(
|
||||||
LedColorPreference::class.java,
|
LedColorPreference::class.java,
|
||||||
LayoutFactory(::LedColorPreferenceViewHolder, R.layout.dsl_preference_item)
|
LayoutFactory(::LedColorPreferenceViewHolder, R.layout.dsl_preference_item)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import com.google.android.material.snackbar.Snackbar
|
|||||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfileAddMembers
|
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfileAddMembers
|
||||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfileRecipient
|
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfileRecipient
|
||||||
@@ -18,6 +17,7 @@ import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
|||||||
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.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton
|
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class AddAllowedMembersFragment : DSLSettingsFragment(layoutId = R.layout.fragme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
NotificationProfileAddMembers.register(adapter)
|
NotificationProfileAddMembers.register(adapter)
|
||||||
NotificationProfileRecipient.register(adapter)
|
NotificationProfileRecipient.register(adapter)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import org.signal.core.util.BreakIteratorCompat
|
|||||||
import org.signal.core.util.EditTextUtil
|
import org.signal.core.util.EditTextUtil
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.EditNotificationProfileViewModel.SaveNotificationProfileResult
|
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.EditNotificationProfileViewModel.SaveNotificationProfileResult
|
||||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfileNamePreset
|
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfileNamePreset
|
||||||
@@ -25,6 +24,7 @@ import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDial
|
|||||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged
|
import org.thoughtcrime.securesms.util.text.AfterTextChanged
|
||||||
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton
|
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton
|
||||||
@@ -131,7 +131,7 @@ class EditNotificationProfileFragment : DSLSettingsFragment(layoutId = R.layout.
|
|||||||
this.emojiView = emojiView
|
this.emojiView = emojiView
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
NotificationProfileNamePreset.register(adapter)
|
NotificationProfileNamePreset.register(adapter)
|
||||||
|
|
||||||
val onClick = { preset: NotificationProfileNamePreset.Model ->
|
val onClick = { preset: NotificationProfileNamePreset.Model ->
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
|
|||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
@@ -31,6 +30,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.SpanUtil
|
import org.thoughtcrime.securesms.util.SpanUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.formatHours
|
import org.thoughtcrime.securesms.util.formatHours
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.thoughtcrime.securesms.util.orderOfDaysInWeek
|
import org.thoughtcrime.securesms.util.orderOfDaysInWeek
|
||||||
@@ -65,7 +65,7 @@ class NotificationProfileDetailsFragment : DSLSettingsFragment() {
|
|||||||
toolbar = null
|
toolbar = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
NotificationProfilePreference.register(adapter)
|
NotificationProfilePreference.register(adapter)
|
||||||
NotificationProfileAddMembers.register(adapter)
|
NotificationProfileAddMembers.register(adapter)
|
||||||
NotificationProfileRecipient.register(adapter)
|
NotificationProfileRecipient.register(adapter)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import androidx.navigation.fragment.findNavController
|
|||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
@@ -20,6 +19,7 @@ import org.thoughtcrime.securesms.components.settings.conversation.preferences.L
|
|||||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +48,7 @@ class NotificationProfilesFragment : DSLSettingsFragment() {
|
|||||||
toolbar = null
|
toolbar = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
NoNotificationProfiles.register(adapter)
|
NoNotificationProfiles.register(adapter)
|
||||||
LargeIconClickPreference.register(adapter)
|
LargeIconClickPreference.register(adapter)
|
||||||
NotificationProfilePreference.register(adapter)
|
NotificationProfilePreference.register(adapter)
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.app.privacy
|
package org.thoughtcrime.securesms.components.settings.app.privacy
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.style.TextAppearanceSpan
|
import android.text.style.TextAppearanceSpan
|
||||||
@@ -16,18 +19,17 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import mobi.upod.timedurationpicker.TimeDurationPicker
|
import mobi.upod.timedurationpicker.TimeDurationPicker
|
||||||
import mobi.upod.timedurationpicker.TimeDurationPickerDialog
|
import mobi.upod.timedurationpicker.TimeDurationPickerDialog
|
||||||
import org.signal.core.util.DimensionUnit
|
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.PassphraseChangeActivity
|
import org.thoughtcrime.securesms.PassphraseChangeActivity
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.ClickPreference
|
import org.thoughtcrime.securesms.components.settings.ClickPreference
|
||||||
import org.thoughtcrime.securesms.components.settings.ClickPreferenceViewHolder
|
import org.thoughtcrime.securesms.components.settings.ClickPreferenceViewHolder
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
@@ -36,12 +38,8 @@ import org.thoughtcrime.securesms.components.settings.configure
|
|||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
|
||||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
||||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||||
import org.thoughtcrime.securesms.stories.Stories
|
import org.thoughtcrime.securesms.stories.Stories
|
||||||
import org.thoughtcrime.securesms.stories.settings.custom.PrivateStorySettingsFragmentArgs
|
|
||||||
import org.thoughtcrime.securesms.stories.settings.story.PrivateStoryItem
|
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil
|
import org.thoughtcrime.securesms.util.ExpirationUtil
|
||||||
@@ -50,6 +48,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil
|
|||||||
import org.thoughtcrime.securesms.util.SpanUtil
|
import org.thoughtcrime.securesms.util.SpanUtil
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import java.lang.Integer.max
|
import java.lang.Integer.max
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -76,17 +75,22 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
|||||||
viewModel.refreshBlockedCount()
|
viewModel.refreshBlockedCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
adapter.registerFactory(ValueClickPreference::class.java, LayoutFactory(::ValueClickPreferenceViewHolder, R.layout.value_click_preference_item))
|
adapter.registerFactory(ValueClickPreference::class.java, LayoutFactory(::ValueClickPreferenceViewHolder, R.layout.value_click_preference_item))
|
||||||
PrivateStoryItem.register(adapter)
|
|
||||||
|
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
val repository = PrivacySettingsRepository()
|
val repository = PrivacySettingsRepository()
|
||||||
val factory = PrivacySettingsViewModel.Factory(sharedPreferences, repository)
|
val factory = PrivacySettingsViewModel.Factory(sharedPreferences, repository)
|
||||||
viewModel = ViewModelProvider(this, factory)[PrivacySettingsViewModel::class.java]
|
viewModel = ViewModelProvider(this, factory)[PrivacySettingsViewModel::class.java]
|
||||||
|
val args: PrivacySettingsFragmentArgs by navArgs()
|
||||||
|
var showPaymentLock = true
|
||||||
|
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||||
|
if (args.showPaymentLock && showPaymentLock) {
|
||||||
|
showPaymentLock = false
|
||||||
|
recyclerView?.scrollToPosition(adapter.itemCount - 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +219,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
|||||||
summary = DSLSettingsText.from(R.string.preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity),
|
summary = DSLSettingsText.from(R.string.preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity),
|
||||||
isChecked = state.isObsoletePasswordTimeoutEnabled,
|
isChecked = state.isObsoletePasswordTimeoutEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setObsoletePasswordTimeoutEnabled(!state.isObsoletePasswordEnabled)
|
viewModel.setObsoletePasswordTimeoutEnabled(!state.isObsoletePasswordTimeoutEnabled)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -297,56 +301,36 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (Stories.isFeatureAvailable()) {
|
if (Stories.isFeatureAvailable()) {
|
||||||
|
|
||||||
dividerPref()
|
dividerPref()
|
||||||
|
|
||||||
sectionHeaderPref(R.string.ConversationListTabs__stories)
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.preferences__stories),
|
||||||
if (!SignalStore.storyValues().isFeatureDisabled) {
|
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__manage_your_stories),
|
||||||
customPref(
|
|
||||||
PrivateStoryItem.RecipientModel(
|
|
||||||
recipient = Recipient.self(),
|
|
||||||
onClick = { findNavController().safeNavigate(R.id.action_privacySettings_to_myStorySettings) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
space(DimensionUnit.DP.toPixels(24f).toInt())
|
|
||||||
|
|
||||||
customPref(
|
|
||||||
PrivateStoryItem.NewModel(
|
|
||||||
onClick = {
|
|
||||||
findNavController().safeNavigate(R.id.action_privacySettings_to_newPrivateStory)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
state.privateStories.forEach {
|
|
||||||
customPref(
|
|
||||||
PrivateStoryItem.PartialModel(
|
|
||||||
privateStoryItemData = it,
|
|
||||||
onClick = { model ->
|
|
||||||
findNavController().safeNavigate(
|
|
||||||
R.id.action_privacySettings_to_privateStorySettings,
|
|
||||||
PrivateStorySettingsFragmentArgs.Builder(model.privateStoryItemData.id).build().toBundle()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switchPref(
|
|
||||||
title = DSLSettingsText.from(R.string.PrivacySettingsFragment__share_and_view_stories),
|
|
||||||
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__you_will_no_longer_be_able),
|
|
||||||
isChecked = state.isStoriesEnabled,
|
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setStoriesEnabled(!state.isStoriesEnabled)
|
findNavController().safeNavigate(PrivacySettingsFragmentDirections.actionPrivacySettingsFragmentToStoryPrivacySettings(R.string.preferences__stories))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dividerPref()
|
dividerPref()
|
||||||
|
|
||||||
|
sectionHeaderPref(R.string.preferences_app_protection__payments)
|
||||||
|
|
||||||
|
switchPref(
|
||||||
|
title = DSLSettingsText.from(R.string.preferences__payment_lock),
|
||||||
|
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__payment_lock_require_lock),
|
||||||
|
isChecked = state.paymentLock && ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure,
|
||||||
|
onClick = {
|
||||||
|
if (!ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure) {
|
||||||
|
showGoToPhoneSettings()
|
||||||
|
} else {
|
||||||
|
viewModel.togglePaymentLock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dividerPref()
|
||||||
|
|
||||||
clickPref(
|
clickPref(
|
||||||
title = DSLSettingsText.from(R.string.preferences__advanced),
|
title = DSLSettingsText.from(R.string.preferences__advanced),
|
||||||
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__signal_message_and_calls),
|
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__signal_message_and_calls),
|
||||||
@@ -357,6 +341,29 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showGoToPhoneSettings() {
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setTitle(getString(R.string.PrivacySettingsFragment__cant_enable_title))
|
||||||
|
setMessage(getString(R.string.PrivacySettingsFragment__cant_enable_description))
|
||||||
|
setPositiveButton(R.string.PaymentsHomeFragment__enable) { _, _ ->
|
||||||
|
val intent = when {
|
||||||
|
Build.VERSION.SDK_INT >= 30 -> Intent(Settings.ACTION_BIOMETRIC_ENROLL)
|
||||||
|
Build.VERSION.SDK_INT >= 28 -> Intent(Settings.ACTION_FINGERPRINT_ENROLL)
|
||||||
|
else -> Intent(Settings.ACTION_SECURITY_SETTINGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(intent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Log.w(TAG, "Failed to navigate to system settings.", e)
|
||||||
|
Toast.makeText(requireContext(), R.string.PrivacySettingsFragment__failed_to_navigate_to_system_settings, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNegativeButton(R.string.PaymentsHomeFragment__not_now) { _, _ -> }
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getScreenLockInactivityTimeoutSummary(timeoutSeconds: Long): String {
|
private fun getScreenLockInactivityTimeoutSummary(timeoutSeconds: Long): String {
|
||||||
val hours = TimeUnit.SECONDS.toHours(timeoutSeconds)
|
val hours = TimeUnit.SECONDS.toHours(timeoutSeconds)
|
||||||
val minutes = TimeUnit.SECONDS.toMinutes(timeoutSeconds) - hours * 60
|
val minutes = TimeUnit.SECONDS.toMinutes(timeoutSeconds) - hours * 60
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.components.settings.app.privacy
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.signal.core.util.concurrent.SignalExecutors
|
import org.signal.core.util.concurrent.SignalExecutors
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
import org.thoughtcrime.securesms.database.model.DistributionListPartialRecord
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob
|
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
@@ -23,12 +22,6 @@ class PrivacySettingsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPrivateStories(consumer: (List<DistributionListPartialRecord>) -> Unit) {
|
|
||||||
SignalExecutors.BOUNDED.execute {
|
|
||||||
consumer(SignalDatabase.distributionLists.getCustomListsForUi())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun syncReadReceiptState() {
|
fun syncReadReceiptState() {
|
||||||
SignalExecutors.BOUNDED.execute {
|
SignalExecutors.BOUNDED.execute {
|
||||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.app.privacy
|
package org.thoughtcrime.securesms.components.settings.app.privacy
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.model.DistributionListPartialRecord
|
|
||||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
||||||
|
|
||||||
data class PrivacySettingsState(
|
data class PrivacySettingsState(
|
||||||
@@ -13,10 +12,9 @@ data class PrivacySettingsState(
|
|||||||
val screenLockActivityTimeout: Long,
|
val screenLockActivityTimeout: Long,
|
||||||
val screenSecurity: Boolean,
|
val screenSecurity: Boolean,
|
||||||
val incognitoKeyboard: Boolean,
|
val incognitoKeyboard: Boolean,
|
||||||
|
val paymentLock: Boolean,
|
||||||
val isObsoletePasswordEnabled: Boolean,
|
val isObsoletePasswordEnabled: Boolean,
|
||||||
val isObsoletePasswordTimeoutEnabled: Boolean,
|
val isObsoletePasswordTimeoutEnabled: Boolean,
|
||||||
val obsoletePasswordTimeout: Int,
|
val obsoletePasswordTimeout: Int,
|
||||||
val universalExpireTimer: Int,
|
val universalExpireTimer: Int
|
||||||
val privateStories: List<DistributionListPartialRecord>,
|
|
||||||
val isStoriesEnabled: Boolean
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ class PrivacySettingsViewModel(
|
|||||||
store.update { it.copy(blockedCount = count) }
|
store.update { it.copy(blockedCount = count) }
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
repository.getPrivateStories { privateStories ->
|
|
||||||
store.update { it.copy(privateStories = privateStories) }
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setReadReceiptsEnabled(enabled: Boolean) {
|
fun setReadReceiptsEnabled(enabled: Boolean) {
|
||||||
@@ -79,6 +74,11 @@ class PrivacySettingsViewModel(
|
|||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun togglePaymentLock() {
|
||||||
|
SignalStore.paymentsValues().paymentLock = state.value?.let { !it.paymentLock } ?: false
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
fun setObsoletePasswordTimeoutEnabled(enabled: Boolean) {
|
fun setObsoletePasswordTimeoutEnabled(enabled: Boolean) {
|
||||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF, enabled).apply()
|
sharedPreferences.edit().putBoolean(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF, enabled).apply()
|
||||||
refresh()
|
refresh()
|
||||||
@@ -89,11 +89,6 @@ class PrivacySettingsViewModel(
|
|||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setStoriesEnabled(isStoriesEnabled: Boolean) {
|
|
||||||
SignalStore.storyValues().isFeatureDisabled = !isStoriesEnabled
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
store.update(this::updateState)
|
store.update(this::updateState)
|
||||||
}
|
}
|
||||||
@@ -107,19 +102,18 @@ class PrivacySettingsViewModel(
|
|||||||
screenLockActivityTimeout = TextSecurePreferences.getScreenLockTimeout(ApplicationDependencies.getApplication()),
|
screenLockActivityTimeout = TextSecurePreferences.getScreenLockTimeout(ApplicationDependencies.getApplication()),
|
||||||
screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(ApplicationDependencies.getApplication()),
|
screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(ApplicationDependencies.getApplication()),
|
||||||
incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(ApplicationDependencies.getApplication()),
|
incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(ApplicationDependencies.getApplication()),
|
||||||
|
paymentLock = SignalStore.paymentsValues().paymentLock,
|
||||||
seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode,
|
seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode,
|
||||||
findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode,
|
findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode,
|
||||||
isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(ApplicationDependencies.getApplication()),
|
isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(ApplicationDependencies.getApplication()),
|
||||||
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(ApplicationDependencies.getApplication()),
|
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(ApplicationDependencies.getApplication()),
|
||||||
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(ApplicationDependencies.getApplication()),
|
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(ApplicationDependencies.getApplication()),
|
||||||
universalExpireTimer = SignalStore.settings().universalExpireTimer,
|
universalExpireTimer = SignalStore.settings().universalExpireTimer
|
||||||
privateStories = emptyList(),
|
|
||||||
isStoriesEnabled = !SignalStore.storyValues().isFeatureDisabled
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateState(state: PrivacySettingsState): PrivacySettingsState {
|
private fun updateState(state: PrivacySettingsState): PrivacySettingsState {
|
||||||
return getState().copy(blockedCount = state.blockedCount, privateStories = state.privateStories)
|
return getState().copy(blockedCount = state.blockedCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import androidx.preference.PreferenceManager
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
@@ -29,6 +28,7 @@ import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity
|
|||||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||||
import org.thoughtcrime.securesms.util.SpanUtil
|
import org.thoughtcrime.securesms.util.SpanUtil
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
|
||||||
class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__advanced) {
|
class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__advanced) {
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences
|
|||||||
unregisterNetworkReceiver()
|
unregisterNetworkReceiver()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
val repository = AdvancedPrivacySettingsRepository(requireContext())
|
val repository = AdvancedPrivacySettingsRepository(requireContext())
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
val factory = AdvancedPrivacySettingsViewModel.Factory(preferences, repository)
|
val factory = AdvancedPrivacySettingsViewModel.Factory(preferences, repository)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.navigation.fragment.NavHostFragment
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
@@ -18,6 +17,7 @@ import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason
|
|||||||
import org.thoughtcrime.securesms.groups.ui.GroupErrors
|
import org.thoughtcrime.securesms.groups.ui.GroupErrors
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil
|
import org.thoughtcrime.securesms.util.ExpirationUtil
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.livedata.ProcessState
|
import org.thoughtcrime.securesms.util.livedata.ProcessState
|
||||||
import org.thoughtcrime.securesms.util.livedata.distinctUntilChanged
|
import org.thoughtcrime.securesms.util.livedata.distinctUntilChanged
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
@@ -48,7 +48,7 @@ class ExpireTimerSettingsFragment : DSLSettingsFragment(
|
|||||||
recycler.clipToPadding = false
|
recycler.clipToPadding = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
val provider = ViewModelProvider(
|
val provider = ViewModelProvider(
|
||||||
NavHostFragment.findNavController(this).getViewModelStoreOwner(R.id.app_settings_expire_timer),
|
NavHostFragment.findNavController(this).getViewModelStoreOwner(R.id.app_settings_expire_timer),
|
||||||
ExpireTimerSettingsViewModel.Factory(requireContext(), arguments.toConfig())
|
ExpireTimerSettingsViewModel.Factory(requireContext(), arguments.toConfig())
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import org.signal.core.util.PendingIntentFlags
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||||
import org.thoughtcrime.securesms.help.HelpFragment
|
import org.thoughtcrime.securesms.help.HelpFragment
|
||||||
@@ -90,7 +91,7 @@ object DonationErrorNotifications {
|
|||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
actionIntent,
|
actionIntent,
|
||||||
if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_ONE_SHOT else 0
|
if (Build.VERSION.SDK_INT >= 23) PendingIntentFlags.oneShot() else PendingIntentFlags.mutable()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ class DonationErrorParams<V> private constructor(
|
|||||||
private fun <V> getVerificationErrorParams(context: Context, verificationError: DonationError.GiftRecipientVerificationError, callback: Callback<V>): DonationErrorParams<V> {
|
private fun <V> getVerificationErrorParams(context: Context, verificationError: DonationError.GiftRecipientVerificationError, callback: Callback<V>): DonationErrorParams<V> {
|
||||||
return when (verificationError) {
|
return when (verificationError) {
|
||||||
is DonationError.GiftRecipientVerificationError.FailedToFetchProfile -> DonationErrorParams(
|
is DonationError.GiftRecipientVerificationError.FailedToFetchProfile -> DonationErrorParams(
|
||||||
title = R.string.DonationsErrors__could_not_verify_recipient,
|
title = R.string.DonationsErrors__couldnt_send_gift,
|
||||||
message = R.string.DonationsErrors__please_check_your_network_connection,
|
message = R.string.DonationsErrors__please_check_your_network_connection,
|
||||||
positiveAction = callback.onOk(context),
|
positiveAction = callback.onOk(context),
|
||||||
negativeAction = null
|
negativeAction = null
|
||||||
)
|
)
|
||||||
else -> DonationErrorParams(
|
else -> DonationErrorParams(
|
||||||
title = R.string.DonationsErrors__recipient_verification_failed,
|
title = R.string.DonationsErrors__cant_send_gift,
|
||||||
message = R.string.DonationsErrors__target_does_not_support_gifting,
|
message = R.string.DonationsErrors__target_does_not_support_gifting,
|
||||||
positiveAction = callback.onOk(context),
|
positiveAction = callback.onOk(context),
|
||||||
negativeAction = null
|
negativeAction = null
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.badges.gifts.ExpiredGiftSheet
|
|||||||
import org.thoughtcrime.securesms.badges.gifts.flow.GiftFlowActivity
|
import org.thoughtcrime.securesms.badges.gifts.flow.GiftFlowActivity
|
||||||
import org.thoughtcrime.securesms.badges.models.BadgePreview
|
import org.thoughtcrime.securesms.badges.models.BadgePreview
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
@@ -28,6 +27,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||||||
import org.thoughtcrime.securesms.subscription.Subscription
|
import org.thoughtcrime.securesms.subscription.Subscription
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||||
import org.thoughtcrime.securesms.util.SpanUtil
|
import org.thoughtcrime.securesms.util.SpanUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||||
import java.util.Currency
|
import java.util.Currency
|
||||||
@@ -60,7 +60,7 @@ class ManageDonationsFragment : DSLSettingsFragment(), ExpiredGiftSheet.Callback
|
|||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
ActiveSubscriptionPreference.register(adapter)
|
ActiveSubscriptionPreference.register(adapter)
|
||||||
IndeterminateLoadingCircle.register(adapter)
|
IndeterminateLoadingCircle.register(adapter)
|
||||||
BadgePreview.register(adapter)
|
BadgePreview.register(adapter)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import org.signal.core.util.concurrent.SimpleTask
|
|||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
@@ -26,6 +25,7 @@ import org.thoughtcrime.securesms.database.model.DonationReceiptRecord
|
|||||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class DonationReceiptDetailFragment : DSLSettingsFragment(layoutId = R.layout.do
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
SplashImage.register(adapter)
|
SplashImage.register(adapter)
|
||||||
|
|
||||||
val sharePngButton: MaterialButton = requireView().findViewById(R.id.share_png)
|
val sharePngButton: MaterialButton = requireView().findViewById(R.id.share_png)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import org.thoughtcrime.securesms.R
|
|||||||
import org.thoughtcrime.securesms.badges.models.Badge
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.badges.models.BadgePreview
|
import org.thoughtcrime.securesms.badges.models.BadgePreview
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||||
@@ -37,6 +36,7 @@ import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
|||||||
import org.thoughtcrime.securesms.subscription.Subscription
|
import org.thoughtcrime.securesms.subscription.Subscription
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.SpanUtil
|
import org.thoughtcrime.securesms.util.SpanUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
@@ -82,7 +82,7 @@ class SubscribeFragment : DSLSettingsFragment(
|
|||||||
viewModel.refreshActiveSubscription()
|
viewModel.refreshActiveSubscription()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
donationPaymentComponent = requireListener()
|
donationPaymentComponent = requireListener()
|
||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.conversation
|
package org.thoughtcrime.securesms.components.settings.conversation
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
@@ -39,7 +40,6 @@ import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment
|
|||||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
import org.thoughtcrime.securesms.components.recyclerview.OnScrollAnimationHelper
|
import org.thoughtcrime.securesms.components.recyclerview.OnScrollAnimationHelper
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
@@ -81,9 +81,9 @@ import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
|
|||||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||||
import org.thoughtcrime.securesms.util.ContextUtil
|
import org.thoughtcrime.securesms.util.ContextUtil
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil
|
import org.thoughtcrime.securesms.util.ExpirationUtil
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
|
||||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog
|
||||||
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity
|
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity
|
||||||
@@ -201,7 +201,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
val args = ConversationSettingsFragmentArgs.fromBundle(requireArguments())
|
val args = ConversationSettingsFragmentArgs.fromBundle(requireArguments())
|
||||||
|
|
||||||
BioTextPreference.register(adapter)
|
BioTextPreference.register(adapter)
|
||||||
@@ -230,7 +230,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||||||
.withFixedSize(ViewUtil.dpToPx(80))
|
.withFixedSize(ViewUtil.dpToPx(80))
|
||||||
.load(state.recipient)
|
.load(state.recipient)
|
||||||
|
|
||||||
if (FeatureFlags.displayDonorBadges() && !state.recipient.isSelf) {
|
if (!state.recipient.isSelf) {
|
||||||
toolbarBadge.setBadgeFromRecipient(state.recipient)
|
toolbarBadge.setBadgeFromRecipient(state.recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +286,8 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||||||
requireContext(),
|
requireContext(),
|
||||||
StoryViewerArgs(
|
StoryViewerArgs(
|
||||||
recipientId = state.recipient.id,
|
recipientId = state.recipient.id,
|
||||||
isInHiddenStoryMode = state.recipient.shouldHideStory()
|
isInHiddenStoryMode = state.recipient.shouldHideStory(),
|
||||||
|
isFromQuote = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
StoryDialogs.displayStoryOrProfileImage(
|
StoryDialogs.displayStoryOrProfileImage(
|
||||||
@@ -477,7 +478,11 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||||||
title = DSLSettingsText.from(R.string.ConversationSettingsFragment__add_as_a_contact),
|
title = DSLSettingsText.from(R.string.ConversationSettingsFragment__add_as_a_contact),
|
||||||
icon = DSLSettingsIcon.from(R.drawable.ic_plus_24),
|
icon = DSLSettingsIcon.from(R.drawable.ic_plus_24),
|
||||||
onClick = {
|
onClick = {
|
||||||
startActivityForResult(RecipientExporter.export(state.recipient).asAddContactIntent(), REQUEST_CODE_ADD_CONTACT)
|
try {
|
||||||
|
startActivityForResult(RecipientExporter.export(state.recipient).asAddContactIntent(), REQUEST_CODE_ADD_CONTACT)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(context, R.string.ConversationSettingsFragment__contacts_app_not_found, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.signal.core.util.concurrent.SignalExecutors
|
|||||||
import org.thoughtcrime.securesms.MainActivity
|
import org.thoughtcrime.securesms.MainActivity
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
@@ -23,12 +22,14 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver
|
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage
|
||||||
import org.thoughtcrime.securesms.subscription.Subscriber
|
import org.thoughtcrime.securesms.subscription.Subscriber
|
||||||
import org.thoughtcrime.securesms.util.Base64
|
import org.thoughtcrime.securesms.util.Base64
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||||
import org.thoughtcrime.securesms.util.SpanUtil
|
import org.thoughtcrime.securesms.util.SpanUtil
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.livedata.Store
|
import org.thoughtcrime.securesms.util.livedata.Store
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId
|
|
||||||
import java.util.Objects
|
import java.util.Objects
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +46,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||||
}
|
}
|
||||||
@@ -62,27 +63,26 @@ class InternalConversationSettingsFragment : DSLSettingsFragment(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!recipient.isGroup) {
|
if (!recipient.isGroup) {
|
||||||
if (recipient.isSelf) {
|
val e164: String = recipient.e164.orElse("null")
|
||||||
val aci: String = SignalStore.account().aci?.toString() ?: "null"
|
longClickPref(
|
||||||
longClickPref(
|
title = DSLSettingsText.from("E164"),
|
||||||
title = DSLSettingsText.from("ACI"),
|
summary = DSLSettingsText.from(e164),
|
||||||
summary = DSLSettingsText.from(aci),
|
onLongClick = { copyToClipboard(e164) }
|
||||||
onLongClick = { copyToClipboard(aci) }
|
)
|
||||||
)
|
|
||||||
val pni: String = SignalStore.account().pni?.toString() ?: "null"
|
val serviceId: String = recipient.serviceId.map { it.toString() }.orElse("null")
|
||||||
longClickPref(
|
longClickPref(
|
||||||
title = DSLSettingsText.from("PNI"),
|
title = DSLSettingsText.from("ServiceId"),
|
||||||
summary = DSLSettingsText.from(pni),
|
summary = DSLSettingsText.from(serviceId),
|
||||||
onLongClick = { copyToClipboard(pni) }
|
onLongClick = { copyToClipboard(serviceId) }
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
val serviceId: String = recipient.serviceId.map(ServiceId::toString).orElse("null")
|
val pni: String = recipient.pni.map { it.toString() }.orElse("null")
|
||||||
longClickPref(
|
longClickPref(
|
||||||
title = DSLSettingsText.from("ServiceId"),
|
title = DSLSettingsText.from("PNI"),
|
||||||
summary = DSLSettingsText.from(serviceId),
|
summary = DSLSettingsText.from(pni),
|
||||||
onLongClick = { copyToClipboard(serviceId) }
|
onLongClick = { copyToClipboard(pni) }
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.groupId != null) {
|
if (state.groupId != null) {
|
||||||
@@ -215,6 +215,48 @@ class InternalConversationSettingsFragment : DSLSettingsFragment(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sectionHeaderPref(DSLSettingsText.from("PNP"))
|
||||||
|
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from("Split contact"),
|
||||||
|
summary = DSLSettingsText.from("Splits this contact into two recipients and two threads so that you can test merging them together. This will remain the 'primary' recipient."),
|
||||||
|
onClick = {
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle("Are you sure?")
|
||||||
|
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
if (!recipient.hasE164()) {
|
||||||
|
Toast.makeText(context, "Recipient doesn't have an E164! Can't split.", Toast.LENGTH_SHORT).show()
|
||||||
|
return@setPositiveButton
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalDatabase.recipients.debugClearE164AndPni(recipient.id)
|
||||||
|
|
||||||
|
val splitRecipientId: RecipientId = if (FeatureFlags.phoneNumberPrivacy()) {
|
||||||
|
SignalDatabase.recipients.getAndPossiblyMergePnpVerified(recipient.pni.orElse(null), recipient.pni.orElse(null), recipient.requireE164())
|
||||||
|
} else {
|
||||||
|
SignalDatabase.recipients.getAndPossiblyMerge(recipient.pni.orElse(null), recipient.requireE164())
|
||||||
|
}
|
||||||
|
val splitRecipient: Recipient = Recipient.resolved(splitRecipientId)
|
||||||
|
val splitThreadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(splitRecipient)
|
||||||
|
|
||||||
|
val messageId: Long = SignalDatabase.sms.insertMessageOutbox(
|
||||||
|
splitThreadId,
|
||||||
|
OutgoingEncryptedMessage(splitRecipient, "Test Message ${System.currentTimeMillis()}", 0),
|
||||||
|
false,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
SignalDatabase.sms.markAsSent(messageId, true)
|
||||||
|
|
||||||
|
SignalDatabase.threads.update(splitThreadId, true)
|
||||||
|
|
||||||
|
Toast.makeText(context, "Done! We split the E164/PNI from this contact into $splitRecipientId", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.groups.ParcelableGroupId
|
import org.thoughtcrime.securesms.groups.ParcelableGroupId
|
||||||
import org.thoughtcrime.securesms.groups.ui.GroupErrors
|
import org.thoughtcrime.securesms.groups.ui.GroupErrors
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
|
||||||
class PermissionsSettingsFragment : DSLSettingsFragment(
|
class PermissionsSettingsFragment : DSLSettingsFragment(
|
||||||
titleId = R.string.ConversationSettingsFragment__permissions
|
titleId = R.string.ConversationSettingsFragment__permissions
|
||||||
@@ -30,7 +30,7 @@ class PermissionsSettingsFragment : DSLSettingsFragment(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import org.thoughtcrime.securesms.MuteDialog
|
import org.thoughtcrime.securesms.MuteDialog
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
@@ -14,6 +13,7 @@ import org.thoughtcrime.securesms.components.settings.configure
|
|||||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.Utils.formatMutedUntil
|
import org.thoughtcrime.securesms.components.settings.conversation.preferences.Utils.formatMutedUntil
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
|
||||||
class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment(
|
class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment(
|
||||||
@@ -38,7 +38,7 @@ class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment(
|
|||||||
viewModel.channelConsistencyCheck()
|
viewModel.channelConsistencyCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
if (state.channelConsistencyCheckComplete && state.recipientId != Recipient.UNKNOWN.id) {
|
if (state.channelConsistencyCheckComplete && state.recipientId != Recipient.UNKNOWN.id) {
|
||||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import androidx.fragment.app.viewModels
|
|||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
@@ -22,6 +21,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase
|
|||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||||
import org.thoughtcrime.securesms.util.RingtoneUtil
|
import org.thoughtcrime.securesms.util.RingtoneUtil
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
|
||||||
private val TAG = Log.tag(CustomNotificationsSettingsFragment::class.java)
|
private val TAG = Log.tag(CustomNotificationsSettingsFragment::class.java)
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class CustomNotificationsSettingsFragment : DSLSettingsFragment(R.string.CustomN
|
|||||||
viewModel.channelConsistencyCheck()
|
viewModel.channelConsistencyCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: MappingAdapter) {
|
||||||
messageSoundResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
messageSoundResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
handleResult(result, viewModel::setMessageSound)
|
handleResult(result, viewModel::setMessageSound)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class AttachmentRegionDecoder implements ImageRegionDecoder {
|
|||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inSampleSize = sampleSize;
|
options.inSampleSize = sampleSize;
|
||||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||||
|
|
||||||
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(rect, options);
|
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(rect, options);
|
||||||
|
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ class VoiceNoteMediaItemFactory {
|
|||||||
sender.getDisplayName(context),
|
sender.getDisplayName(context),
|
||||||
threadRecipient.getDisplayName(context));
|
threadRecipient.getDisplayName(context));
|
||||||
} else if (preference.isDisplayContact()) {
|
} else if (preference.isDisplayContact()) {
|
||||||
return sender.isSelf() ? context.getString(R.string.note_to_self)
|
return sender.isSelf() && threadRecipient.isSelf() ? context.getString(R.string.note_to_self)
|
||||||
: sender.getDisplayName(context);
|
: sender.getDisplayName(context);
|
||||||
} else {
|
} else {
|
||||||
return context.getString(R.string.MessageNotifier_signal_message);
|
return context.getString(R.string.MessageNotifier_signal_message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
|
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
|
||||||
|
|
||||||
|
import org.signal.core.util.PendingIntentFlags;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||||
@@ -107,7 +108,7 @@ class VoiceNoteNotificationManager {
|
|||||||
return PendingIntent.getActivity(context,
|
return PendingIntent.getActivity(context,
|
||||||
0,
|
0,
|
||||||
conversationActivity,
|
conversationActivity,
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
PendingIntentFlags.cancelCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.media.AudioManager
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.ResultReceiver
|
import android.os.ResultReceiver
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.ControlDispatcher
|
|
||||||
import com.google.android.exoplayer2.PlaybackParameters
|
import com.google.android.exoplayer2.PlaybackParameters
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
@@ -22,8 +21,7 @@ class VoiceNotePlaybackController(
|
|||||||
private val TAG = Log.tag(VoiceNoteMediaController::class.java)
|
private val TAG = Log.tag(VoiceNoteMediaController::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("deprecation")
|
override fun onCommand(p: Player, command: String, extras: Bundle?, cb: ResultReceiver?): Boolean {
|
||||||
override fun onCommand(p: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?): Boolean {
|
|
||||||
Log.d(TAG, "[onCommand] Received player command $command (extras? ${extras != null})")
|
Log.d(TAG, "[onCommand] Received player command $command (extras? ${extras != null})")
|
||||||
|
|
||||||
if (command == VoiceNotePlaybackService.ACTION_NEXT_PLAYBACK_SPEED) {
|
if (command == VoiceNotePlaybackService.ACTION_NEXT_PLAYBACK_SPEED) {
|
||||||
@@ -37,13 +35,13 @@ class VoiceNotePlaybackController(
|
|||||||
val currentStreamType = Util.getStreamTypeForAudioUsage(player.audioAttributes.usage)
|
val currentStreamType = Util.getStreamTypeForAudioUsage(player.audioAttributes.usage)
|
||||||
if (newStreamType != currentStreamType) {
|
if (newStreamType != currentStreamType) {
|
||||||
val attributes = when (newStreamType) {
|
val attributes = when (newStreamType) {
|
||||||
AudioManager.STREAM_MUSIC -> AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MUSIC).setUsage(C.USAGE_MEDIA).build()
|
AudioManager.STREAM_MUSIC -> AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MUSIC).setUsage(C.USAGE_MEDIA).build()
|
||||||
AudioManager.STREAM_VOICE_CALL -> AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_SPEECH).setUsage(C.USAGE_VOICE_COMMUNICATION).build()
|
AudioManager.STREAM_VOICE_CALL -> AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_SPEECH).setUsage(C.USAGE_VOICE_COMMUNICATION).build()
|
||||||
else -> throw AssertionError()
|
else -> throw AssertionError()
|
||||||
}
|
}
|
||||||
|
|
||||||
player.playWhenReady = false
|
player.playWhenReady = false
|
||||||
player.setAudioAttributes(attributes, false)
|
player.setAudioAttributes(attributes, newStreamType == AudioManager.STREAM_MUSIC)
|
||||||
|
|
||||||
if (newStreamType == AudioManager.STREAM_VOICE_CALL) {
|
if (newStreamType == AudioManager.STREAM_VOICE_CALL) {
|
||||||
player.playWhenReady = true
|
player.playWhenReady = true
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
import com.google.android.exoplayer2.ControlDispatcher;
|
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.MediaMetadata;
|
import com.google.android.exoplayer2.MediaMetadata;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
@@ -40,7 +39,7 @@ import java.util.stream.Collectors;
|
|||||||
/**
|
/**
|
||||||
* ExoPlayer Preparer for Voice Notes. This only supports ACTION_PLAY_FROM_URI
|
* ExoPlayer Preparer for Voice Notes. This only supports ACTION_PLAY_FROM_URI
|
||||||
*/
|
*/
|
||||||
final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackPreparer {
|
final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackPreparer {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(VoiceNotePlaybackPreparer.class);
|
private static final String TAG = Log.tag(VoiceNotePlaybackPreparer.class);
|
||||||
private static final Executor EXECUTOR = Executors.newSingleThreadExecutor();
|
private static final Executor EXECUTOR = Executors.newSingleThreadExecutor();
|
||||||
@@ -291,7 +290,6 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.PlaybackP
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NonNull Player player,
|
public boolean onCommand(@NonNull Player player,
|
||||||
@NonNull ControlDispatcher controlDispatcher,
|
|
||||||
@NonNull String command,
|
@NonNull String command,
|
||||||
@Nullable Bundle extras,
|
@Nullable Bundle extras,
|
||||||
@Nullable ResultReceiver cb)
|
@Nullable ResultReceiver cb)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.google.android.exoplayer2.C
|
|||||||
import com.google.android.exoplayer2.DefaultLoadControl
|
import com.google.android.exoplayer2.DefaultLoadControl
|
||||||
import com.google.android.exoplayer2.ForwardingPlayer
|
import com.google.android.exoplayer2.ForwardingPlayer
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
|
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||||
import org.thoughtcrime.securesms.video.exo.SignalMediaSourceFactory
|
import org.thoughtcrime.securesms.video.exo.SignalMediaSourceFactory
|
||||||
|
|
||||||
class VoiceNotePlayer @JvmOverloads constructor(
|
class VoiceNotePlayer @JvmOverloads constructor(
|
||||||
@@ -15,7 +16,9 @@ class VoiceNotePlayer @JvmOverloads constructor(
|
|||||||
DefaultLoadControl.Builder()
|
DefaultLoadControl.Builder()
|
||||||
.setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE)
|
.setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE)
|
||||||
.build()
|
.build()
|
||||||
).build()
|
).build().apply {
|
||||||
|
setAudioAttributes(AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MUSIC).setUsage(C.USAGE_MEDIA).build(), true)
|
||||||
|
}
|
||||||
) : ForwardingPlayer(internalPlayer) {
|
) : ForwardingPlayer(internalPlayer) {
|
||||||
|
|
||||||
override fun seekTo(windowIndex: Int, positionMs: Long) {
|
override fun seekTo(windowIndex: Int, positionMs: Long) {
|
||||||
|
|||||||
@@ -149,8 +149,8 @@ public class CallParticipantView extends ConstraintLayout {
|
|||||||
|
|
||||||
boolean hasContentToRender = (participant.isVideoEnabled() || participant.isScreenSharing()) && participant.isForwardingVideo();
|
boolean hasContentToRender = (participant.isVideoEnabled() || participant.isScreenSharing()) && participant.isForwardingVideo();
|
||||||
|
|
||||||
rendererFrame.setVisibility(hasContentToRender ? View.VISIBLE : View.GONE);
|
rendererFrame.setVisibility(hasContentToRender ? View.VISIBLE : View.INVISIBLE);
|
||||||
renderer.setVisibility(hasContentToRender ? View.VISIBLE : View.GONE);
|
renderer.setVisibility(hasContentToRender ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
|
||||||
if (participant.isVideoEnabled()) {
|
if (participant.isVideoEnabled()) {
|
||||||
participant.getVideoSink().getLockableEglBase().performWithValidEglBase(eglBase -> {
|
participant.getVideoSink().getLockableEglBase().performWithValidEglBase(eglBase -> {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
|
||||||
|
import org.signal.core.util.PendingIntentFlags;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
@@ -28,7 +29,7 @@ public final class GroupCallSafetyNumberChangeNotificationUtil {
|
|||||||
Intent contentIntent = new Intent(context, WebRtcCallActivity.class);
|
Intent contentIntent = new Intent(context, WebRtcCallActivity.class);
|
||||||
contentIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
contentIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, 0);
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntentFlags.mutable());
|
||||||
|
|
||||||
Notification safetyNumberChangeNotification = new NotificationCompat.Builder(context, NotificationChannels.CALLS)
|
Notification safetyNumberChangeNotification = new NotificationCompat.Builder(context, NotificationChannels.CALLS)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ public class CallParticipantsListDialog extends BottomSheetDialogFragment {
|
|||||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
final WebRtcCallViewModel viewModel = ViewModelProviders.of(requireActivity()).get(WebRtcCallViewModel.class);
|
final WebRtcCallViewModel viewModel = new ViewModelProvider(requireActivity()).get(WebRtcCallViewModel.class);
|
||||||
|
|
||||||
initializeList();
|
initializeList();
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,14 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
|||||||
itemView.setOnClickListener(v -> {
|
itemView.setOnClickListener(v -> {
|
||||||
if (clickListener != null) clickListener.onItemClick(getView());
|
if (clickListener != null) clickListener.onItemClick(getView());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
itemView.setOnLongClickListener(v -> {
|
||||||
|
if (clickListener != null) {
|
||||||
|
return clickListener.onItemLongClick(getView());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactSelectionListItem getView() {
|
public ContactSelectionListItem getView() {
|
||||||
@@ -300,6 +308,11 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
|||||||
|
|
||||||
private @Nullable String getHeaderLetterForDisplayName(@NonNull Cursor cursor) {
|
private @Nullable String getHeaderLetterForDisplayName(@NonNull Cursor cursor) {
|
||||||
String name = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
|
String name = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
|
||||||
|
|
||||||
|
if (name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Iterator<String> characterIterator = new CharacterIterable(name).iterator();
|
Iterator<String> characterIterator = new CharacterIterable(name).iterator();
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(name) && characterIterator.hasNext()) {
|
if (!TextUtils.isEmpty(name) && characterIterator.hasNext()) {
|
||||||
@@ -430,5 +443,6 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
|||||||
|
|
||||||
public interface ItemClickListener {
|
public interface ItemClickListener {
|
||||||
void onItemClick(ContactSelectionListItem item);
|
void onItemClick(ContactSelectionListItem item);
|
||||||
|
boolean onItemLongClick(ContactSelectionListItem item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.contacts;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
@@ -10,18 +12,24 @@ 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.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||||
import org.thoughtcrime.securesms.components.FromTextView;
|
import org.thoughtcrime.securesms.components.FromTextView;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||||
|
import org.thoughtcrime.securesms.profiles.manage.UsernameState;
|
||||||
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.RecipientForeverObserver;
|
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
@@ -51,6 +59,8 @@ public class ContactSelectionListItem extends ConstraintLayout implements Recipi
|
|||||||
private LiveRecipient recipient;
|
private LiveRecipient recipient;
|
||||||
private GlideRequests glideRequests;
|
private GlideRequests glideRequests;
|
||||||
|
|
||||||
|
private final UsernameFallbackPhotoProvider usernameFallbackPhotoProvider = new UsernameFallbackPhotoProvider();
|
||||||
|
|
||||||
public ContactSelectionListItem(Context context) {
|
public ContactSelectionListItem(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
@@ -104,8 +114,11 @@ public class ContactSelectionListItem extends ConstraintLayout implements Recipi
|
|||||||
this.contactLabel = label;
|
this.contactLabel = label;
|
||||||
this.contactAbout = about;
|
this.contactAbout = about;
|
||||||
|
|
||||||
|
this.contactPhotoImage.setFallbackPhotoProvider(null);
|
||||||
if (type == ContactRepository.NEW_PHONE_TYPE || type == ContactRepository.NEW_USERNAME_TYPE) {
|
if (type == ContactRepository.NEW_PHONE_TYPE || type == ContactRepository.NEW_USERNAME_TYPE) {
|
||||||
this.recipient = null;
|
this.recipient = null;
|
||||||
|
this.contactPhotoImage.setFallbackPhotoProvider(usernameFallbackPhotoProvider);
|
||||||
|
this.contactPhotoImage.setFallbackPhotoColor(AvatarColor.ON_SURFACE_VARIANT);
|
||||||
this.contactPhotoImage.setAvatar(glideRequests, null, false);
|
this.contactPhotoImage.setAvatar(glideRequests, null, false);
|
||||||
} else if (recipientId != null) {
|
} else if (recipientId != null) {
|
||||||
if (this.recipient != null) {
|
if (this.recipient != null) {
|
||||||
@@ -168,6 +181,8 @@ public class ContactSelectionListItem extends ConstraintLayout implements Recipi
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private void setText(@Nullable Recipient recipient, int type, String name, String number, String label, @Nullable String about) {
|
private void setText(@Nullable Recipient recipient, int type, String name, String number, String label, @Nullable String about) {
|
||||||
|
this.numberView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
if (number == null || number.isEmpty()) {
|
if (number == null || number.isEmpty()) {
|
||||||
this.nameView.setEnabled(false);
|
this.nameView.setEnabled(false);
|
||||||
this.numberView.setText("");
|
this.numberView.setText("");
|
||||||
@@ -181,10 +196,9 @@ public class ContactSelectionListItem extends ConstraintLayout implements Recipi
|
|||||||
this.nameView.setEnabled(true);
|
this.nameView.setEnabled(true);
|
||||||
this.labelView.setVisibility(View.GONE);
|
this.labelView.setVisibility(View.GONE);
|
||||||
} else if (type == ContactRepository.NEW_USERNAME_TYPE) {
|
} else if (type == ContactRepository.NEW_USERNAME_TYPE) {
|
||||||
this.numberView.setText("@" + number);
|
this.numberView.setVisibility(View.GONE);
|
||||||
this.nameView.setEnabled(true);
|
this.nameView.setEnabled(true);
|
||||||
this.labelView.setText(label);
|
this.labelView.setVisibility(View.GONE);
|
||||||
this.labelView.setVisibility(View.VISIBLE);
|
|
||||||
} else if (recipient != null && recipient.isDistributionList()) {
|
} else if (recipient != null && recipient.isDistributionList()) {
|
||||||
this.numberView.setText(getViewerCount(number));
|
this.numberView.setText(getViewerCount(number));
|
||||||
this.labelView.setVisibility(View.GONE);
|
this.labelView.setVisibility(View.GONE);
|
||||||
@@ -198,6 +212,8 @@ public class ContactSelectionListItem extends ConstraintLayout implements Recipi
|
|||||||
if (recipient != null) {
|
if (recipient != null) {
|
||||||
this.nameView.setText(recipient);
|
this.nameView.setText(recipient);
|
||||||
chipName = recipient.getShortDisplayName(getContext());
|
chipName = recipient.getShortDisplayName(getContext());
|
||||||
|
} else if (type == ContactRepository.NEW_USERNAME_TYPE && number != null) {
|
||||||
|
this.nameView.setText(presentUsername(number));
|
||||||
} else {
|
} else {
|
||||||
this.nameView.setText(name);
|
this.nameView.setText(name);
|
||||||
chipName = name;
|
chipName = name;
|
||||||
@@ -224,6 +240,14 @@ public class ContactSelectionListItem extends ConstraintLayout implements Recipi
|
|||||||
int viewerCount = Integer.parseInt(number);
|
int viewerCount = Integer.parseInt(number);
|
||||||
return getContext().getResources().getQuantityString(R.plurals.contact_selection_list_item__number_of_viewers, viewerCount, viewerCount);
|
return getContext().getResources().getQuantityString(R.plurals.contact_selection_list_item__number_of_viewers, viewerCount, viewerCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CharSequence presentUsername(@NonNull String username) {
|
||||||
|
if (username.contains(UsernameState.DELIMITER)) {
|
||||||
|
return username;
|
||||||
|
} else {
|
||||||
|
return new SpannableStringBuilder(username).append(SpanUtil.color(ContextCompat.getColor(getContext(), R.color.signal_colorOutline), UsernameState.DELIMITER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable LiveRecipient getRecipient() {
|
public @Nullable LiveRecipient getRecipient() {
|
||||||
return recipient;
|
return recipient;
|
||||||
@@ -264,4 +288,11 @@ public class ContactSelectionListItem extends ConstraintLayout implements Recipi
|
|||||||
Log.w(TAG, "Bad change! Local recipient doesn't match. Ignoring. Local: " + (this.recipient == null ? "null" : this.recipient.getId()) + ", Changed: " + recipient.getId());
|
Log.w(TAG, "Bad change! Local recipient doesn't match. Ignoring. Local: " + (this.recipient == null ? "null" : this.recipient.getId()) + ", Changed: " + recipient.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class UsernameFallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
|
||||||
|
@Override
|
||||||
|
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
|
||||||
|
return new ResourceContactPhoto(R.drawable.ic_search_24, R.drawable.ic_search_24, R.drawable.ic_search_24);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,18 +22,26 @@ import android.database.MatrixCursor;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.core.util.CursorUtil;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||||
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||||
|
import org.whispersystems.signalservice.internal.util.Util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CursorLoader that initializes a ContactsDatabase instance
|
* CursorLoader that initializes a ContactsDatabase instance
|
||||||
@@ -213,13 +221,36 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Cursor getGroupsCursor() {
|
private Cursor getGroupsCursor() {
|
||||||
MatrixCursor groupContacts = ContactsCursorRows.createMatrixCursor();
|
MatrixCursor groupContacts = ContactsCursorRows.createMatrixCursor();
|
||||||
|
Map<RecipientId, GroupDatabase.GroupRecord> groups = new LinkedHashMap<>();
|
||||||
|
|
||||||
try (GroupDatabase.Reader reader = SignalDatabase.groups().queryGroupsByTitle(getFilter(), flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode), !smsEnabled(mode))) {
|
try (GroupDatabase.Reader reader = SignalDatabase.groups().queryGroupsByTitle(getFilter(), flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode), !smsEnabled(mode))) {
|
||||||
GroupDatabase.GroupRecord groupRecord;
|
GroupDatabase.GroupRecord groupRecord;
|
||||||
while ((groupRecord = reader.getNext()) != null) {
|
while ((groupRecord = reader.getNext()) != null) {
|
||||||
groupContacts.addRow(ContactsCursorRows.forGroup(groupRecord));
|
groups.put(groupRecord.getRecipientId(), groupRecord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getFilter() != null && !Util.isEmpty(getFilter())) {
|
||||||
|
Set<RecipientId> filteredContacts = new HashSet<>();
|
||||||
|
try (Cursor cursor = SignalDatabase.recipients().queryAllContacts(getFilter())) {
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
filteredContacts.add(RecipientId.from(CursorUtil.requireString(cursor, RecipientDatabase.ID)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (GroupDatabase.Reader reader = SignalDatabase.groups().queryGroupsByMembership(filteredContacts, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode), !smsEnabled(mode))) {
|
||||||
|
GroupDatabase.GroupRecord groupRecord;
|
||||||
|
while ((groupRecord = reader.getNext()) != null) {
|
||||||
|
groups.put(groupRecord.getRecipientId(), groupRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GroupDatabase.GroupRecord groupRecord : groups.values()) {
|
||||||
|
groupContacts.addRow(ContactsCursorRows.forGroup(groupRecord));
|
||||||
|
}
|
||||||
|
|
||||||
return groupContacts;
|
return groupContacts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +259,7 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Cursor getUsernameSearchCursor() {
|
private Cursor getUsernameSearchCursor() {
|
||||||
return ContactsCursorRows.forUsernameSearch(getUnknownContactTitle(), getFilter());
|
return ContactsCursorRows.forUsernameSearch(getFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUnknownContactTitle() {
|
private String getUnknownContactTitle() {
|
||||||
|
|||||||
@@ -104,11 +104,11 @@ public final class ContactsCursorRows {
|
|||||||
/**
|
/**
|
||||||
* Create a row for a contacts cursor for a username the user is entering or has entered.
|
* Create a row for a contacts cursor for a username the user is entering or has entered.
|
||||||
*/
|
*/
|
||||||
public static @NonNull MatrixCursor forUsernameSearch(@NonNull String unknownContactTitle, @NonNull String filter) {
|
public static @NonNull MatrixCursor forUsernameSearch(@NonNull String filter) {
|
||||||
MatrixCursor matrixCursor = createMatrixCursor(1);
|
MatrixCursor matrixCursor = createMatrixCursor(1);
|
||||||
|
|
||||||
matrixCursor.addRow(new Object[]{null,
|
matrixCursor.addRow(new Object[]{null,
|
||||||
unknownContactTitle,
|
null,
|
||||||
filter,
|
filter,
|
||||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||||
"\u21e2",
|
"\u21e2",
|
||||||
@@ -119,7 +119,7 @@ public final class ContactsCursorRows {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static @NonNull MatrixCursor forUsernameSearchHeader(@NonNull Context context) {
|
public static @NonNull MatrixCursor forUsernameSearchHeader(@NonNull Context context) {
|
||||||
return forHeader(context.getString(R.string.ContactsCursorLoader_username_search));
|
return forHeader(context.getString(R.string.ContactsCursorLoader_find_by_username));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NonNull MatrixCursor forPhoneNumberSearchHeader(@NonNull Context context) {
|
public static @NonNull MatrixCursor forPhoneNumberSearchHeader(@NonNull Context context) {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import com.makeramen.roundedimageview.RoundedDrawable;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.avatar.Avatars;
|
|
||||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
||||||
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColorPair;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -63,14 +63,18 @@ public class ResourceContactPhoto implements FallbackContactPhoto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull Drawable buildDrawable(@NonNull Context context, int resourceId, @NonNull AvatarColor color, boolean inverted) {
|
private @NonNull Drawable buildDrawable(@NonNull Context context, int resourceId, @NonNull AvatarColor color, boolean inverted) {
|
||||||
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(color);
|
AvatarColorPair avatarColorPair = AvatarColorPair.create(context, color);
|
||||||
Drawable background = Objects.requireNonNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable));
|
|
||||||
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(AppCompatResources.getDrawable(context, resourceId));
|
final int backgroundColor = avatarColorPair.getBackgroundColor();
|
||||||
|
final int foregroundColor = avatarColorPair.getForegroundColor();
|
||||||
|
|
||||||
|
Drawable background = Objects.requireNonNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable));
|
||||||
|
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(AppCompatResources.getDrawable(context, resourceId));
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
foreground.setScaleType(scaleType);
|
foreground.setScaleType(scaleType);
|
||||||
background.setColorFilter(inverted ? foregroundColor.getColorInt() : color.colorInt(), PorterDuff.Mode.SRC_IN);
|
background.setColorFilter(inverted ? foregroundColor : backgroundColor, PorterDuff.Mode.SRC_IN);
|
||||||
foreground.setColorFilter(inverted ? color.colorInt() : foregroundColor.getColorInt(), PorterDuff.Mode.SRC_ATOP);
|
foreground.setColorFilter(inverted ? backgroundColor : foregroundColor, PorterDuff.Mode.SRC_ATOP);
|
||||||
|
|
||||||
return new ExpandingLayerDrawable(new Drawable[] {background, foreground});
|
return new ExpandingLayerDrawable(new Drawable[] {background, foreground});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.thoughtcrime.securesms.contacts.management
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.CheckResult
|
||||||
|
import io.reactivex.rxjava3.core.Completable
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientUtil
|
||||||
|
|
||||||
|
class ContactsManagementRepository(context: Context) {
|
||||||
|
private val context = context.applicationContext
|
||||||
|
|
||||||
|
@CheckResult
|
||||||
|
fun blockContact(recipient: Recipient): Completable {
|
||||||
|
return Completable.fromAction {
|
||||||
|
if (recipient.isDistributionList) {
|
||||||
|
error("Blocking a distribution list makes no sense")
|
||||||
|
} else if (recipient.isGroup) {
|
||||||
|
RecipientUtil.block(context, recipient)
|
||||||
|
} else {
|
||||||
|
RecipientUtil.blockNonGroup(context, recipient)
|
||||||
|
}
|
||||||
|
}.subscribeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckResult
|
||||||
|
fun hideContact(recipient: Recipient): Completable {
|
||||||
|
return Completable.fromAction {
|
||||||
|
if (recipient.isGroup || recipient.isDistributionList || recipient.isSelf) {
|
||||||
|
error("Cannot hide groups, self, or distribution lists.")
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalDatabase.recipients.markHidden(recipient.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.thoughtcrime.securesms.contacts.management
|
||||||
|
|
||||||
|
import androidx.annotation.CheckResult
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.core.Completable
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
|
class ContactsManagementViewModel(private val repository: ContactsManagementRepository) : ViewModel() {
|
||||||
|
|
||||||
|
@CheckResult
|
||||||
|
fun hideContact(recipient: Recipient): Completable {
|
||||||
|
return repository.hideContact(recipient).observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckResult
|
||||||
|
fun blockContact(recipient: Recipient): Completable {
|
||||||
|
return repository.blockContact(recipient).observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
class Factory(private val repository: ContactsManagementRepository) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return modelClass.cast(ContactsManagementViewModel(repository)) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user