mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-15 15:37:29 +00:00
Compare commits
387 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5a73a3380 | ||
|
|
173dd180d9 | ||
|
|
f332cbf1bc | ||
|
|
c4d317b33e | ||
|
|
9c59d6a69b | ||
|
|
61bdd4e027 | ||
|
|
ab2efe78b1 | ||
|
|
c3af3e4740 | ||
|
|
6418eac658 | ||
|
|
d74e9f7410 | ||
|
|
569c83d90e | ||
|
|
1dc3cf7824 | ||
|
|
d8dead82b6 | ||
|
|
b053fbc4a7 | ||
|
|
2144dc3b67 | ||
|
|
243b4b9414 | ||
|
|
e068fde8f2 | ||
|
|
3162f04937 | ||
|
|
58a32c11ec | ||
|
|
f06817f00d | ||
|
|
da4be5c1cf | ||
|
|
a59f5d953a | ||
|
|
815a988587 | ||
|
|
b65d9ffaed | ||
|
|
0fc73c3a6f | ||
|
|
57fdc1b223 | ||
|
|
22d5fc6cba | ||
|
|
c36f9646f9 | ||
|
|
45e11f6291 | ||
|
|
2893c3dc0e | ||
|
|
116022b01d | ||
|
|
09cba8774d | ||
|
|
41129f7c50 | ||
|
|
44d014c445 | ||
|
|
51e086b20e | ||
|
|
d71a5c99f4 | ||
|
|
fb0243a029 | ||
|
|
713441d9cb | ||
|
|
451f0fd12b | ||
|
|
5a84fa5a80 | ||
|
|
751ba8d1c2 | ||
|
|
974ed439a4 | ||
|
|
0172c1e385 | ||
|
|
faa19acf81 | ||
|
|
1f9afb6c6e | ||
|
|
5d96bc2d3a | ||
|
|
9366596f5f | ||
|
|
cb6e3ade15 | ||
|
|
45178b3eb3 | ||
|
|
31e3e37c9b | ||
|
|
e1489bb407 | ||
|
|
8b50d8645a | ||
|
|
796fdb1cf6 | ||
|
|
5203d40804 | ||
|
|
21252aad0f | ||
|
|
0c535904fc | ||
|
|
490944a02a | ||
|
|
ace85df9b7 | ||
|
|
9e56441d4a | ||
|
|
d83c3d35eb | ||
|
|
8f26d63d6f | ||
|
|
75c520097a | ||
|
|
38375982dd | ||
|
|
d24a71bbd2 | ||
|
|
7964c9fca7 | ||
|
|
ec07e4b233 | ||
|
|
b4266b8575 | ||
|
|
07201203b2 | ||
|
|
e7c5eb93dd | ||
|
|
e70229c672 | ||
|
|
86c5b28562 | ||
|
|
a9149c5dc0 | ||
|
|
a64430c65f | ||
|
|
75aab4c031 | ||
|
|
6f8be3260c | ||
|
|
e74460bd91 | ||
|
|
c25250cb05 | ||
|
|
42c3cc5296 | ||
|
|
e4b3f90457 | ||
|
|
992b04f8c5 | ||
|
|
d1e0f3646a | ||
|
|
b4ba565923 | ||
|
|
006e7dc736 | ||
|
|
5ed6407ea3 | ||
|
|
faf6b5a4e4 | ||
|
|
f92891895e | ||
|
|
d8cc3c86b4 | ||
|
|
e7f233db5b | ||
|
|
fd9c420dc8 | ||
|
|
dc9fceb8cf | ||
|
|
dc9b8169c0 | ||
|
|
38caf1e2b7 | ||
|
|
4b3e7c8858 | ||
|
|
2be3068675 | ||
|
|
682e47c7b3 | ||
|
|
8c90c3ad81 | ||
|
|
d5afcc4aec | ||
|
|
14ed4201c0 | ||
|
|
1b9efeb049 | ||
|
|
4b862cf4c7 | ||
|
|
4e7683961e | ||
|
|
ce9d44d010 | ||
|
|
32d052259f | ||
|
|
fb98874948 | ||
|
|
55a62ead05 | ||
|
|
f81f50646e | ||
|
|
f707c1d02c | ||
|
|
417c04bf1c | ||
|
|
8f7f836598 | ||
|
|
32aea8d154 | ||
|
|
3f6c8cb622 | ||
|
|
8f6ff215aa | ||
|
|
4f01bacb49 | ||
|
|
e6f4b0976f | ||
|
|
e0d9c3f149 | ||
|
|
9d3cebf430 | ||
|
|
5e106bf510 | ||
|
|
fc617fb7a9 | ||
|
|
b88ecb6370 | ||
|
|
9b21001953 | ||
|
|
cb0e10c7ab | ||
|
|
69884935f3 | ||
|
|
b809008291 | ||
|
|
79e77f871e | ||
|
|
32ac6e3429 | ||
|
|
da56c2790f | ||
|
|
687192f071 | ||
|
|
2e82ee0aaf | ||
|
|
4dacf4e342 | ||
|
|
b91f04316a | ||
|
|
3c4252a933 | ||
|
|
be4b687e48 | ||
|
|
8950100bd7 | ||
|
|
d651716d99 | ||
|
|
270606699b | ||
|
|
cc7617a302 | ||
|
|
c04b5f2085 | ||
|
|
28f3ded4bd | ||
|
|
d56607a686 | ||
|
|
f8dde57133 | ||
|
|
f2ea13a142 | ||
|
|
69a1fa0d3c | ||
|
|
5cb54b9ad7 | ||
|
|
ee610cadd3 | ||
|
|
acf131308b | ||
|
|
c591ec3185 | ||
|
|
264a245d27 | ||
|
|
f6c25d2a8b | ||
|
|
d069d9331c | ||
|
|
ba14031945 | ||
|
|
0b639e0169 | ||
|
|
afee8631e1 | ||
|
|
214cb25d1b | ||
|
|
a1457d22d6 | ||
|
|
82107e2938 | ||
|
|
e2f5fa6962 | ||
|
|
74336041ea | ||
|
|
0dea5eb779 | ||
|
|
267f759452 | ||
|
|
2779d7efc5 | ||
|
|
04b7cb15cc | ||
|
|
eba04eb75b | ||
|
|
d924fc92ab | ||
|
|
69de830a10 | ||
|
|
60b9811e08 | ||
|
|
01418c0e36 | ||
|
|
3f374eebc2 | ||
|
|
502a2ead00 | ||
|
|
0d9490e1fb | ||
|
|
4afb459b30 | ||
|
|
37f4557fef | ||
|
|
424979d91f | ||
|
|
527fbee41e | ||
|
|
1935b0ebdd | ||
|
|
f6aaef1434 | ||
|
|
050b59f09d | ||
|
|
db8dcf6073 | ||
|
|
c45002d5b6 | ||
|
|
3cd9f0ffef | ||
|
|
d065a6f563 | ||
|
|
49d7a032fb | ||
|
|
80f3504098 | ||
|
|
37d971859b | ||
|
|
2a5bed1d21 | ||
|
|
0927914c57 | ||
|
|
dda0e0393e | ||
|
|
cc9be7b61e | ||
|
|
2751076089 | ||
|
|
2a3f85008b | ||
|
|
432a732e7c | ||
|
|
dc6045ca8b | ||
|
|
b58b0fd7a8 | ||
|
|
5e122353e1 | ||
|
|
fc41fb5014 | ||
|
|
99477c8eef | ||
|
|
f50466f779 | ||
|
|
eb79300fe2 | ||
|
|
b35c96b0b6 | ||
|
|
2282cd12d7 | ||
|
|
0cbe992912 | ||
|
|
98cb6b457c | ||
|
|
29d66f2b92 | ||
|
|
763a12dbc6 | ||
|
|
158f3d898f | ||
|
|
b935999548 | ||
|
|
2cca6a5afb | ||
|
|
2954c31b5f | ||
|
|
6c2d21125e | ||
|
|
59d69192c6 | ||
|
|
937a288cee | ||
|
|
236e1ba885 | ||
|
|
3bdf2e7e2c | ||
|
|
a160af2d11 | ||
|
|
341a31da00 | ||
|
|
e0128e7e31 | ||
|
|
8f51bdcb78 | ||
|
|
53dc5bab43 | ||
|
|
8f86de1764 | ||
|
|
133a7d2576 | ||
|
|
8b7506ed2d | ||
|
|
c378e4413e | ||
|
|
f3182ddbc6 | ||
|
|
951d4ad06f | ||
|
|
2678a00781 | ||
|
|
e6e8786d86 | ||
|
|
1ec3782f64 | ||
|
|
a4ec31eebe | ||
|
|
94b631ccfe | ||
|
|
26d8df5ea9 | ||
|
|
0569d0555f | ||
|
|
3e2349c4ff | ||
|
|
af7e736de9 | ||
|
|
51879a9c46 | ||
|
|
524f3d6d08 | ||
|
|
64fe78ff9a | ||
|
|
e798f3f276 | ||
|
|
ddb04c6ea3 | ||
|
|
213ffdab62 | ||
|
|
60354b2f1f | ||
|
|
4bb214cb2a | ||
|
|
cfd4399685 | ||
|
|
30563ed3e5 | ||
|
|
46344776a4 | ||
|
|
0d215d609b | ||
|
|
c15ea8c0b4 | ||
|
|
d6061fb699 | ||
|
|
7f2b6178d5 | ||
|
|
53177bf40e | ||
|
|
857b945410 | ||
|
|
904593c103 | ||
|
|
dcfa7e3b36 | ||
|
|
589f345825 | ||
|
|
0b7c22886d | ||
|
|
e9e2846532 | ||
|
|
e0fc191883 | ||
|
|
b2ecd89a71 | ||
|
|
9ed95a6081 | ||
|
|
3f51f89d86 | ||
|
|
01778f718a | ||
|
|
7d5ddd8eac | ||
|
|
2447601219 | ||
|
|
701e43c13d | ||
|
|
bbbccccf47 | ||
|
|
1e9ca0a9bf | ||
|
|
0b62bb8168 | ||
|
|
4f9f62992f | ||
|
|
1938d6cae0 | ||
|
|
13e8c55781 | ||
|
|
6264f9b585 | ||
|
|
4482bfcabb | ||
|
|
015088a53f | ||
|
|
ef7d707432 | ||
|
|
d1f6a924fb | ||
|
|
f312757daf | ||
|
|
1d83729e6c | ||
|
|
6a45858b4a | ||
|
|
1b448c2bdf | ||
|
|
f6cd190245 | ||
|
|
23303e5407 | ||
|
|
b5237848e9 | ||
|
|
7cac0c9a7c | ||
|
|
9dbbe4675f | ||
|
|
95978f16e9 | ||
|
|
d055bba452 | ||
|
|
8ef809a02b | ||
|
|
458941f952 | ||
|
|
5852a508aa | ||
|
|
e2b4995fbb | ||
|
|
a3556d9f68 | ||
|
|
fe890a1a41 | ||
|
|
c06bb18249 | ||
|
|
9099969b41 | ||
|
|
6358f59f67 | ||
|
|
073034dd3c | ||
|
|
17fb815805 | ||
|
|
409e7c41b4 | ||
|
|
b9a1a5027c | ||
|
|
49535f6378 | ||
|
|
c058452605 | ||
|
|
b3511dba77 | ||
|
|
afbe27c55f | ||
|
|
41d227207d | ||
|
|
92b586c061 | ||
|
|
acbc17c909 | ||
|
|
15f17747ee | ||
|
|
781054fc9d | ||
|
|
b59769a30a | ||
|
|
26e0e09e24 | ||
|
|
3a2990a911 | ||
|
|
d8060b3041 | ||
|
|
f42ec5318f | ||
|
|
bd0d425cbf | ||
|
|
b3d5d7c33e | ||
|
|
1746869dc3 | ||
|
|
633f4cbbe5 | ||
|
|
0944e2f758 | ||
|
|
b49e4004ab | ||
|
|
68381f8b64 | ||
|
|
f180066058 | ||
|
|
6b7de2e85e | ||
|
|
c650a978e9 | ||
|
|
e05cadafe6 | ||
|
|
c6008a4f90 | ||
|
|
5624855eba | ||
|
|
799ff86fc0 | ||
|
|
798fc84e82 | ||
|
|
cc363a3c88 | ||
|
|
6fdaef1f58 | ||
|
|
6db6c93295 | ||
|
|
4fb0f30d32 | ||
|
|
7fa4eb079b | ||
|
|
c5392b8844 | ||
|
|
e460973957 | ||
|
|
e1c6311a18 | ||
|
|
ed11e2f05a | ||
|
|
3182e5af88 | ||
|
|
5cfdf626fe | ||
|
|
e55834d523 | ||
|
|
9d5a52a980 | ||
|
|
5649c906a5 | ||
|
|
ee548d27e5 | ||
|
|
1dc737b5e5 | ||
|
|
427869d4ca | ||
|
|
36395ced89 | ||
|
|
52a9f2c893 | ||
|
|
bd88be2513 | ||
|
|
7107c1d6b2 | ||
|
|
51f4a343c9 | ||
|
|
a12ee1b78b | ||
|
|
4bf59a55da | ||
|
|
dbac9bf9f6 | ||
|
|
b95083fe92 | ||
|
|
e2d297eb8a | ||
|
|
a3176bbb67 | ||
|
|
4203dde151 | ||
|
|
880661710f | ||
|
|
d844fa0fb5 | ||
|
|
18ede2e900 | ||
|
|
06cc96bee7 | ||
|
|
43a12d2a81 | ||
|
|
0a29ffcf4c | ||
|
|
9c88532c21 | ||
|
|
f3450b8f10 | ||
|
|
a4d56e376f | ||
|
|
24b5bac589 | ||
|
|
762f17f1c1 | ||
|
|
105c8c9745 | ||
|
|
93d99287eb | ||
|
|
ce156c3450 | ||
|
|
7db16e6156 | ||
|
|
e80033c287 | ||
|
|
1553f9b75d | ||
|
|
c244a98962 | ||
|
|
a8ad1e718e | ||
|
|
b5712f4bd1 | ||
|
|
6bcb0de43d | ||
|
|
80651d2425 | ||
|
|
46492b8238 | ||
|
|
1be561543c | ||
|
|
e430a46e20 | ||
|
|
8d187c8ba1 | ||
|
|
eacf03768f | ||
|
|
b077c9b4f3 | ||
|
|
893749fcab | ||
|
|
848ead5e78 | ||
|
|
9c47acb004 | ||
|
|
8ca54bcc7b |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
custom: https://signal.org/donate/
|
||||
7
.github/workflows/android.yml
vendored
7
.github/workflows/android.yml
vendored
@@ -26,3 +26,10 @@ jobs:
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew qa
|
||||
|
||||
- name: Archive reports for failed build
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: reports
|
||||
path: '*/build/reports'
|
||||
|
||||
@@ -16,12 +16,6 @@ repositories {
|
||||
includeGroupByRegex "com\\.github\\.chrisbanes.*"
|
||||
}
|
||||
}
|
||||
maven {
|
||||
url "https://raw.github.com/signalapp/maven/master/shortcutbadger/releases/"
|
||||
content {
|
||||
includeGroupByRegex "me\\.leolin.*"
|
||||
}
|
||||
}
|
||||
maven {
|
||||
url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/"
|
||||
content {
|
||||
@@ -61,8 +55,8 @@ protobuf {
|
||||
}
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 772
|
||||
def canonicalVersionName = "5.2.3"
|
||||
def canonicalVersionCode = 807
|
||||
def canonicalVersionName = "5.6.2"
|
||||
|
||||
def postFixSize = 100
|
||||
def abiPostFix = ['universal' : 0,
|
||||
@@ -108,6 +102,7 @@ android {
|
||||
project.ext.set("archivesBaseName", "Signal");
|
||||
|
||||
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
|
||||
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
|
||||
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
|
||||
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
|
||||
@@ -160,10 +155,8 @@ android {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
ignoreAssetsPattern '!contours.tfl:!LMprec_600.emd:!blazeface.tfl'
|
||||
exclude '/org/spongycastle/x509/CertPathReviewerMessages.properties'
|
||||
exclude '/org/spongycastle/x509/CertPathReviewerMessages_de.properties'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -211,6 +204,12 @@ android {
|
||||
debuggable false
|
||||
matchingFallbacks = ['debug']
|
||||
}
|
||||
mock {
|
||||
initWith debug
|
||||
isDefault false
|
||||
minifyEnabled false
|
||||
matchingFallbacks = ['debug']
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
@@ -236,6 +235,15 @@ android {
|
||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||
}
|
||||
|
||||
study {
|
||||
dimension 'distribution'
|
||||
|
||||
applicationIdSuffix ".study"
|
||||
ext.websiteUpdateUrl = "null"
|
||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||
}
|
||||
|
||||
prod {
|
||||
dimension 'environment'
|
||||
|
||||
@@ -255,7 +263,7 @@ android {
|
||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
||||
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
||||
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\", " +
|
||||
"\"038c40bbbacdc873caa81ac793bb75afde6dfe436a99ab1f15e3f0cbb7434ced\", " +
|
||||
"\"51a56084c0b21c6b8f62b1bc792ec9bedac4c7c3964bb08ddcab868158c09982\", " +
|
||||
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")"
|
||||
buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]"
|
||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
||||
@@ -275,6 +283,18 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
android.variantFilter { variant ->
|
||||
def distribution = variant.getFlavors().get(0).name
|
||||
def environment = variant.getFlavors().get(1).name
|
||||
def buildType = variant.buildType.name
|
||||
|
||||
if (distribution == 'study' && buildType != 'perf' && buildType != 'mock') {
|
||||
variant.setIgnore(true)
|
||||
} else if (distribution != 'study' && buildType == 'mock') {
|
||||
variant.setIgnore(true)
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError true
|
||||
baseline file("lint-baseline.xml")
|
||||
@@ -315,8 +335,7 @@ dependencies {
|
||||
implementation "androidx.camera:camera-view:1.0.0-alpha18"
|
||||
implementation "androidx.concurrent:concurrent-futures:1.0.0"
|
||||
implementation "androidx.autofill:autofill:1.0.0"
|
||||
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
|
||||
implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
|
||||
implementation "androidx.biometric:biometric:1.1.0"
|
||||
|
||||
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
|
||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||
@@ -338,15 +357,16 @@ dependencies {
|
||||
implementation project(':paging')
|
||||
implementation project(':core-util')
|
||||
implementation project(':video')
|
||||
implementation project(':device-transfer')
|
||||
|
||||
implementation 'org.signal:zkgroup-android:0.7.0'
|
||||
implementation 'org.whispersystems:signal-client-android:0.1.5'
|
||||
implementation 'org.whispersystems:signal-client-android:0.1.7'
|
||||
implementation 'com.google.protobuf:protobuf-javalite:3.10.0'
|
||||
implementation 'org.signal:argon2:13.1@aar'
|
||||
|
||||
implementation 'org.signal:ringrtc-android:2.8.9'
|
||||
implementation 'org.signal:ringrtc-android:2.9.4'
|
||||
|
||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||
implementation "me.leolin:ShortcutBadger:1.1.22"
|
||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
||||
@@ -382,7 +402,7 @@ dependencies {
|
||||
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
||||
}
|
||||
|
||||
implementation 'com.airbnb.android:lottie:3.0.7'
|
||||
implementation 'com.airbnb.android:lottie:3.6.0'
|
||||
|
||||
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
||||
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
||||
@@ -492,6 +512,15 @@ def getLastCommitTimestamp() {
|
||||
}
|
||||
}
|
||||
|
||||
def getGitHash() {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'git', 'rev-parse', '--short', 'HEAD'
|
||||
standardOutput = stdout
|
||||
}
|
||||
return stdout.toString().trim()
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
testLogging {
|
||||
events "failed"
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
|
||||
<!-- So we can add a TextSecure 'Account' -->
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
@@ -159,12 +158,15 @@
|
||||
<activity android:name=".preferences.MmsPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity android:name=".sharing.ShareActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:noHistory="true"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
<intent-filter>
|
||||
@@ -252,6 +254,16 @@
|
||||
<data android:scheme="https"
|
||||
android:host="signal.group"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https"
|
||||
android:host="signal.tube" />
|
||||
<data android:scheme="sgnl"
|
||||
android:host="signal.tube" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".conversation.ConversationActivity"
|
||||
@@ -347,13 +359,34 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ApplicationPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".wallpaper.ChatWallpaperActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".wallpaper.ChatWallpaperPreviewActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
@@ -460,6 +493,10 @@
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity android:name=".profiles.manage.ManageProfileActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
@@ -535,7 +572,16 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<service android:enabled="true" android:name=".service.WebRtcCallService"/>
|
||||
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/TextSecure.FullScreenMedia" />
|
||||
|
||||
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Signal.WallpaperCropper" />
|
||||
|
||||
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService"/>
|
||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
|
||||
@@ -735,6 +781,13 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".service.LocalBackupListener">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
|
||||
@@ -65,6 +65,8 @@ import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -82,7 +84,7 @@ import java.util.concurrent.Executor;
|
||||
@RequiresApi(21)
|
||||
@SuppressLint("RestrictedApi")
|
||||
public final class SignalCameraView extends FrameLayout {
|
||||
static final String TAG = SignalCameraView.class.getSimpleName();
|
||||
static final String TAG = Log.tag(SignalCameraView.class);
|
||||
|
||||
static final int INDEFINITE_VIDEO_DURATION = -1;
|
||||
static final int INDEFINITE_VIDEO_SIZE = -1;
|
||||
|
||||
@@ -24,7 +24,7 @@ public final class Log {
|
||||
}
|
||||
|
||||
public static void e(@NonNull String tag, @NonNull String message) {
|
||||
e(tag, message, null);
|
||||
SignalGlideCodecs.getLogProvider().e(tag, message, null);
|
||||
}
|
||||
|
||||
public static void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import org.signal.glide.Log;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.glide.apng.io.APNGReader;
|
||||
import org.signal.glide.apng.io.APNGWriter;
|
||||
import org.signal.glide.common.decode.Frame;
|
||||
@@ -32,7 +32,7 @@ import java.util.List;
|
||||
*/
|
||||
public class APNGDecoder extends FrameSeqDecoder<APNGReader, APNGWriter> {
|
||||
|
||||
private static final String TAG = APNGDecoder.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(APNGDecoder.class);
|
||||
|
||||
private APNGWriter apngWriter;
|
||||
private int mLoopCount;
|
||||
|
||||
@@ -21,7 +21,7 @@ import android.os.Message;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
|
||||
|
||||
import org.signal.glide.Log;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.glide.common.decode.FrameSeqDecoder;
|
||||
import org.signal.glide.common.loader.Loader;
|
||||
|
||||
@@ -35,7 +35,7 @@ import java.util.Set;
|
||||
* @CreateDate: 2019/3/27
|
||||
*/
|
||||
public abstract class FrameAnimationDrawable<Decoder extends FrameSeqDecoder> extends Drawable implements Animatable2Compat, FrameSeqDecoder.RenderListener {
|
||||
private static final String TAG = FrameAnimationDrawable.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(FrameAnimationDrawable.class);
|
||||
private final Paint paint = new Paint();
|
||||
private final Decoder frameSeqDecoder;
|
||||
private DrawFilter drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
|
||||
@@ -15,7 +15,7 @@ import android.os.Looper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.glide.Log;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.glide.common.executor.FrameDecoderExecutor;
|
||||
import org.signal.glide.common.io.Reader;
|
||||
import org.signal.glide.common.io.Writer;
|
||||
@@ -39,7 +39,7 @@ import java.util.concurrent.locks.LockSupport;
|
||||
* @CreateDate: 2019/3/27
|
||||
*/
|
||||
public abstract class FrameSeqDecoder<R extends Reader, W extends Writer> {
|
||||
private static final String TAG = FrameSeqDecoder.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(FrameSeqDecoder.class);
|
||||
private final int taskId;
|
||||
|
||||
private final Loader mLoader;
|
||||
|
||||
@@ -43,6 +43,7 @@ public final class AppInitialization {
|
||||
SignalStore.onFirstEverAppLaunch();
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||
}
|
||||
@@ -53,8 +54,10 @@ public final class AppInitialization {
|
||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||
SignalStore.onFirstEverAppLaunch();
|
||||
SignalStore.onboarding().clearAll();
|
||||
TextSecurePreferences.onPostBackupRestore(context);
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||
}
|
||||
@@ -76,6 +79,7 @@ public final class AppInitialization {
|
||||
SignalStore.onFirstEverAppLaunch();
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||
}
|
||||
|
||||
@@ -17,23 +17,18 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
import androidx.multidex.MultiDexApplication;
|
||||
|
||||
import com.google.android.gms.security.ProviderInstaller;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||
import org.signal.core.util.ShakeDetector;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.AndroidLogger;
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -46,7 +41,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
||||
@@ -57,6 +51,7 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||
import org.thoughtcrime.securesms.logging.LogSecretProvider;
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
@@ -71,8 +66,8 @@ import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||
import org.thoughtcrime.securesms.shakereport.ShakeToReport;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
@@ -98,16 +93,14 @@ import java.util.concurrent.TimeUnit;
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ApplicationContext extends MultiDexApplication implements DefaultLifecycleObserver {
|
||||
public class ApplicationContext extends MultiDexApplication implements AppForegroundObserver.Listener {
|
||||
|
||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ApplicationContext.class);
|
||||
|
||||
private ExpiringMessageManager expiringMessageManager;
|
||||
private ViewOnceMessageManager viewOnceMessageManager;
|
||||
private PersistentLogger persistentLogger;
|
||||
|
||||
private volatile boolean isAppVisible;
|
||||
|
||||
public static ApplicationContext getInstance(Context context) {
|
||||
return (ApplicationContext)context.getApplicationContext();
|
||||
}
|
||||
@@ -133,11 +126,12 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
.addBlocking("crash-handling", this::initializeCrashHandling)
|
||||
.addBlocking("eat-db", () -> DatabaseFactory.getInstance(this))
|
||||
.addBlocking("app-dependencies", this::initializeAppDependencies)
|
||||
.addBlocking("notification-channels", () -> NotificationChannels.create(this))
|
||||
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
|
||||
.addBlocking("app-migrations", this::initializeApplicationMigrations)
|
||||
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this))
|
||||
.addBlocking("lifecycle-observer", () -> ProcessLifecycleOwner.get().getLifecycle().addObserver(this))
|
||||
.addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this))
|
||||
.addBlocking("message-retriever", this::initializeMessageRetrieval)
|
||||
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
|
||||
.addBlocking("vector-compat", () -> {
|
||||
@@ -145,6 +139,13 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
}
|
||||
})
|
||||
.addBlocking("proxy-init", () -> {
|
||||
if (SignalStore.proxy().isProxyEnabled()) {
|
||||
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
|
||||
Conscrypt.setUseEngineSocketByDefault(true);
|
||||
}
|
||||
})
|
||||
.addBlocking("blob-provider", this::initializeBlobProvider)
|
||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||
.addNonBlocking(this::initializeGcmCheck)
|
||||
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
||||
@@ -158,20 +159,15 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
||||
.addPostRender(this::initializeExpiringMessageManager)
|
||||
.addPostRender(this::initializeBlobProvider)
|
||||
.addPostRender(() -> NotificationChannels.create(this))
|
||||
.execute();
|
||||
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||
|
||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
Tracer.getInstance().end("Application#onCreate()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(@NonNull LifecycleOwner owner) {
|
||||
public void onForeground() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
isAppVisible = true;
|
||||
Log.i(TAG, "App is now visible.");
|
||||
|
||||
ApplicationDependencies.getFrameRateTracker().begin();
|
||||
@@ -192,8 +188,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(@NonNull LifecycleOwner owner) {
|
||||
isAppVisible = false;
|
||||
public void onBackground() {
|
||||
Log.i(TAG, "App is no longer visible.");
|
||||
KeyCachingService.onAppBackgrounded(this);
|
||||
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
||||
@@ -212,10 +207,6 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
return viewOnceMessageManager;
|
||||
}
|
||||
|
||||
public boolean isAppVisible() {
|
||||
return isAppVisible;
|
||||
}
|
||||
|
||||
public PersistentLogger getPersistentLogger() {
|
||||
return persistentLogger;
|
||||
}
|
||||
@@ -272,7 +263,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
}
|
||||
|
||||
private void initializeAppDependencies() {
|
||||
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this, new SignalServiceNetworkAccess(this)));
|
||||
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this));
|
||||
}
|
||||
|
||||
private void initializeFirstEverAppLaunch() {
|
||||
@@ -287,6 +278,9 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 90) {
|
||||
Log.i(TAG, "Detected a new install that doesn't have passphrases disabled -- assuming bad initialization.");
|
||||
AppInitialization.onRepairFirstEverAppLaunch(this);
|
||||
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 912) {
|
||||
Log.i(TAG, "Detected a not-recent install that doesn't have passphrases disabled -- disabling now.");
|
||||
TextSecurePreferences.setPasswordDisabled(this, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +313,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
DirectoryRefreshListener.schedule(this);
|
||||
LocalBackupListener.schedule(this);
|
||||
RotateSenderCertificateListener.schedule(this);
|
||||
MessageProcessReceiver.startOrUpdateAlarm(this);
|
||||
|
||||
if (BuildConfig.PLAY_STORE_DISABLED) {
|
||||
UpdateApkRefreshListener.schedule(this);
|
||||
@@ -384,7 +379,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
FcmJobService.schedule(this);
|
||||
} else {
|
||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
|
||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||
}
|
||||
TextSecurePreferences.setNeedsMessagePull(this, false);
|
||||
}
|
||||
@@ -392,7 +387,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
|
||||
@WorkerThread
|
||||
private void initializeBlobProvider() {
|
||||
BlobProvider.getInstance().onSessionStart(this);
|
||||
BlobProvider.getInstance().initialize(this);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.help.HelpFragment;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
|
||||
@@ -40,11 +41,13 @@ import org.thoughtcrime.securesms.preferences.BackupsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.DataAndStoragePreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.EditProxyFragment;
|
||||
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.UsernamePreference;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
@@ -64,11 +67,13 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
|
||||
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
|
||||
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
|
||||
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
|
||||
public static final String LAUNCH_TO_PROXY_FRAGMENT = "launch.to.proxy.fragment";
|
||||
public static final String LAUNCH_TO_NOTIFICATIONS_FRAGMENT = "launch.to.notifications.fragment";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ApplicationPreferencesActivity.class);
|
||||
|
||||
private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile";
|
||||
private static final String PREFERENCE_CATEGORY_USERNAME = "preference_category_username";
|
||||
@@ -107,6 +112,10 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
initFragment(android.R.id.content, new BackupsPreferenceFragment());
|
||||
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_HELP_FRAGMENT, false)) {
|
||||
initFragment(android.R.id.content, new HelpFragment());
|
||||
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_PROXY_FRAGMENT, false)) {
|
||||
initFragment(android.R.id.content, EditProxyFragment.newInstance());
|
||||
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_NOTIFICATIONS_FRAGMENT, false)) {
|
||||
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
|
||||
} else if (icicle == null) {
|
||||
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
||||
} else {
|
||||
@@ -345,7 +354,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
requireActivity().startActivity(EditProfileActivity.getIntentForUserProfileEdit(preference.getContext()));
|
||||
requireActivity().startActivity(ManageProfileActivity.getIntent(requireActivity()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -353,7 +362,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
private class UsernameClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
requireActivity().startActivity(EditProfileActivity.getIntentForUsernameEdit(preference.getContext()));
|
||||
requireActivity().startActivity(ManageProfileActivity.getIntentForUsernameEdit(preference.getContext()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ public interface BindableConversationItem extends Unbindable {
|
||||
@NonNull Set<ConversationMessage> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseMention);
|
||||
boolean pulseMention,
|
||||
boolean hasWallpaper,
|
||||
boolean isMessageRequestAccepted);
|
||||
|
||||
ConversationMessage getConversationMessage();
|
||||
|
||||
@@ -65,6 +67,7 @@ public interface BindableConversationItem extends Unbindable {
|
||||
void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient);
|
||||
void onJoinGroupCallClicked();
|
||||
void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId);
|
||||
void onEnableCallNotificationsClicked();
|
||||
|
||||
/** @return true if handled, false if you want to let the normal url handling continue */
|
||||
boolean onUrlClicked(@NonNull String url);
|
||||
|
||||
@@ -11,6 +11,8 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
@@ -27,17 +29,16 @@ import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.VerifySpan;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
public class ConfirmIdentityDialog extends AlertDialog {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = ConfirmIdentityDialog.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ConfirmIdentityDialog.class);
|
||||
|
||||
private OnClickListener callback;
|
||||
|
||||
@@ -94,7 +95,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
|
||||
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1);
|
||||
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext());
|
||||
|
||||
@@ -136,7 +137,6 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
|
||||
private void processIncomingMessageRecord(MessageRecord messageRecord) {
|
||||
try {
|
||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getContext());
|
||||
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
||||
|
||||
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
||||
@@ -155,9 +155,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
0,
|
||||
null);
|
||||
|
||||
long pushId = pushDatabase.insert(envelope);
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), pushId, messageRecord.getId()));
|
||||
ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), envelope, messageRecord.getId()));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
ContactSelectionListFragment.OnContactSelectedListener,
|
||||
ContactSelectionListFragment.ScrollCallback
|
||||
{
|
||||
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ContactSelectionActivity.class);
|
||||
|
||||
public static final String EXTRA_LAYOUT_RES_ID = "layout_res_id";
|
||||
|
||||
@@ -99,7 +99,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
|
||||
private void initializeResources() {
|
||||
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||
contactsFragment.setOnContactSelectedListener(this);
|
||||
contactsFragment.setOnRefreshListener(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
||||
import org.thoughtcrime.securesms.components.emoji.WarningTextView;
|
||||
import org.thoughtcrime.securesms.contacts.AbstractContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.contacts.ContactChip;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||
@@ -76,6 +77,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
||||
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
@@ -110,22 +112,27 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
public static final String SELECTION_LIMITS = "selection_limits";
|
||||
public static final String CURRENT_SELECTION = "current_selection";
|
||||
public static final String HIDE_COUNT = "hide_count";
|
||||
public static final String CAN_SELECT_SELF = "can_select_self";
|
||||
public static final String DISPLAY_CHIPS = "display_chips";
|
||||
|
||||
private ConstraintLayout constraintLayout;
|
||||
private TextView emptyText;
|
||||
private OnContactSelectedListener onContactSelectedListener;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
private View showContactsLayout;
|
||||
private Button showContactsButton;
|
||||
private TextView showContactsDescription;
|
||||
private ProgressWheel showContactsProgress;
|
||||
private String cursorFilter;
|
||||
private RecyclerView recyclerView;
|
||||
private RecyclerViewFastScroller fastScroller;
|
||||
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
||||
private ChipGroup chipGroup;
|
||||
private HorizontalScrollView chipGroupScrollContainer;
|
||||
private WarningTextView groupLimit;
|
||||
private OnSelectionLimitReachedListener onSelectionLimitReachedListener;
|
||||
private AbstractContactsCursorLoaderFactoryProvider cursorFactoryProvider;
|
||||
|
||||
private ConstraintLayout constraintLayout;
|
||||
private TextView emptyText;
|
||||
private OnContactSelectedListener onContactSelectedListener;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
private View showContactsLayout;
|
||||
private Button showContactsButton;
|
||||
private TextView showContactsDescription;
|
||||
private ProgressWheel showContactsProgress;
|
||||
private String cursorFilter;
|
||||
private RecyclerView recyclerView;
|
||||
private RecyclerViewFastScroller fastScroller;
|
||||
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
||||
private ChipGroup chipGroup;
|
||||
private HorizontalScrollView chipGroupScrollContainer;
|
||||
private WarningTextView groupLimit;
|
||||
|
||||
@Nullable private FixedViewsAdapter headerAdapter;
|
||||
@Nullable private FixedViewsAdapter footerAdapter;
|
||||
@@ -136,6 +143,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
private Set<RecipientId> currentSelection;
|
||||
private boolean isMulti;
|
||||
private boolean hideCount;
|
||||
private boolean canSelectSelf;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
@@ -148,6 +156,22 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
if (context instanceof ScrollCallback) {
|
||||
scrollCallback = (ScrollCallback) context;
|
||||
}
|
||||
|
||||
if (context instanceof OnContactSelectedListener) {
|
||||
onContactSelectedListener = (OnContactSelectedListener) context;
|
||||
}
|
||||
|
||||
if (context instanceof OnSelectionLimitReachedListener) {
|
||||
onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) context;
|
||||
}
|
||||
|
||||
if (context instanceof AbstractContactsCursorLoaderFactoryProvider) {
|
||||
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context;
|
||||
}
|
||||
|
||||
if (getParentFragment() instanceof AbstractContactsCursorLoaderFactoryProvider) {
|
||||
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,6 +241,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
hideCount = intent.getBooleanExtra(HIDE_COUNT, false);
|
||||
selectionLimit = intent.getParcelableExtra(SELECTION_LIMITS);
|
||||
isMulti = selectionLimit != null;
|
||||
canSelectSelf = intent.getBooleanExtra(CAN_SELECT_SELF, !isMulti);
|
||||
|
||||
if (!isMulti) {
|
||||
selectionLimit = SelectionLimits.NO_LIMITS;
|
||||
@@ -295,7 +320,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
}
|
||||
|
||||
recyclerView.setAdapter(concatenateAdapter);
|
||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
|
||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true, 0));
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
@@ -372,10 +397,15 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
|
||||
@Override
|
||||
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
FragmentActivity activity = requireActivity();
|
||||
return new ContactsCursorLoader(activity,
|
||||
activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL),
|
||||
cursorFilter, activity.getIntent().getBooleanExtra(RECENTS, false));
|
||||
FragmentActivity activity = requireActivity();
|
||||
int displayMode = activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL);
|
||||
boolean displayRecents = activity.getIntent().getBooleanExtra(RECENTS, false);
|
||||
|
||||
if (cursorFactoryProvider != null) {
|
||||
return cursorFactoryProvider.get().create();
|
||||
} else {
|
||||
return new ContactsCursorLoader.Factory(activity, displayMode, cursorFilter, displayRecents).create();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -464,14 +494,18 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
||||
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
||||
|
||||
if (isMulti && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||
if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isMulti || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) {
|
||||
if (selectionHardLimitReached()) {
|
||||
GroupLimitDialog.showHardLimitMessage(requireContext());
|
||||
if (onSelectionLimitReachedListener != null) {
|
||||
onSelectionLimitReachedListener.onHardLimitReached(selectionLimit.getHardLimit());
|
||||
} else {
|
||||
GroupLimitDialog.showHardLimitMessage(requireContext());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -489,11 +523,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
if (onContactSelectedListener != null) {
|
||||
if (onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null)) {
|
||||
markContactSelected(selected);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
}
|
||||
} else {
|
||||
markContactSelected(selected);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
}
|
||||
} else {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
@@ -507,16 +541,16 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
if (onContactSelectedListener != null) {
|
||||
if (onContactSelectedListener.onBeforeContactSelected(contact.getRecipientId(), contact.getNumber())) {
|
||||
markContactSelected(selectedContact);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
}
|
||||
} else {
|
||||
markContactSelected(selectedContact);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
markContactUnselected(selectedContact);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
|
||||
@@ -611,7 +645,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
chipGroup.addView(chip);
|
||||
updateGroupLimit(getChipCount());
|
||||
if (selectionWarningLimitReachedExactly()) {
|
||||
GroupLimitDialog.showRecommendedLimitMessage(requireContext());
|
||||
if (onSelectionLimitReachedListener != null) {
|
||||
onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit());
|
||||
} else {
|
||||
GroupLimitDialog.showRecommendedLimitMessage(requireContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +671,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
}
|
||||
|
||||
private void setChipGroupVisibility(int visibility) {
|
||||
if (!requireActivity().getIntent().getBooleanExtra(DISPLAY_CHIPS, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
|
||||
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
@@ -641,16 +683,12 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
constraintSet.applyTo(constraintLayout);
|
||||
}
|
||||
|
||||
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
||||
this.onContactSelectedListener = onContactSelectedListener;
|
||||
}
|
||||
|
||||
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
|
||||
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
||||
}
|
||||
|
||||
private void smoothScrollChipsToEnd() {
|
||||
int x = chipGroupScrollContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? chipGroup.getWidth() : 0;
|
||||
int x = ViewUtil.isLtr(chipGroupScrollContainer) ? chipGroup.getWidth() : 0;
|
||||
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
||||
}
|
||||
|
||||
@@ -660,6 +698,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
||||
}
|
||||
|
||||
public interface OnSelectionLimitReachedListener {
|
||||
void onSuggestedLimitReached(int limit);
|
||||
void onHardLimitReached(int limit);
|
||||
}
|
||||
|
||||
public interface ListCallback {
|
||||
void onInvite();
|
||||
void onNewGroup(boolean forceV1);
|
||||
@@ -668,4 +711,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
public interface ScrollCallback {
|
||||
void onBeginScroll();
|
||||
}
|
||||
|
||||
public interface AbstractContactsCursorLoaderFactoryProvider {
|
||||
@NonNull AbstractContactsCursorLoader.Factory get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
@@ -28,7 +29,6 @@ import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
@@ -45,7 +45,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
||||
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
|
||||
{
|
||||
|
||||
private static final String TAG = DeviceActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(DeviceActivity.class);
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
@@ -123,7 +123,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
||||
|
||||
@Override
|
||||
public void onQrDataFound(final String data) {
|
||||
Util.runOnMain(() -> {
|
||||
ThreadUtil.runOnMain(() -> {
|
||||
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
||||
Uri uri = Uri.parse(data);
|
||||
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
|
||||
|
||||
@@ -40,7 +40,7 @@ public class DeviceListFragment extends ListFragment
|
||||
ListView.OnItemClickListener, Button.OnClickListener
|
||||
{
|
||||
|
||||
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(DeviceListFragment.class);
|
||||
|
||||
private SignalServiceAccountManager accountManager;
|
||||
private Locale locale;
|
||||
|
||||
@@ -6,10 +6,12 @@ import android.view.Window;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
|
||||
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = DeviceProvisioningActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(DeviceProvisioningActivity.class);
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
|
||||
@@ -7,6 +7,8 @@ import android.graphics.PorterDuff;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
@@ -40,6 +42,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -105,9 +108,16 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||
|
||||
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
|
||||
inviteText.addTextChangedListener(new AfterTextChanged(editable -> {
|
||||
boolean isEnabled = editable.length() > 0;
|
||||
smsButton.setEnabled(isEnabled);
|
||||
shareButton.setEnabled(isEnabled);
|
||||
smsButton.animate().alpha(isEnabled ? 1f : 0.5f);
|
||||
shareButton.animate().alpha(isEnabled ? 1f : 0.5f);
|
||||
}));
|
||||
|
||||
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||
|
||||
contactsFragment.setOnContactSelectedListener(this);
|
||||
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
|
||||
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
||||
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
||||
|
||||
@@ -15,6 +15,12 @@ public abstract class LoggingFragment extends Fragment {
|
||||
|
||||
private static final String TAG = Log.tag(LoggingFragment.class);
|
||||
|
||||
public LoggingFragment() { }
|
||||
|
||||
public LoggingFragment(int contentLayoutId) {
|
||||
super(contentLayoutId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
logEvent("onCreate()");
|
||||
|
||||
@@ -9,6 +9,8 @@ import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLockedDialog;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
@@ -41,6 +43,7 @@ public class MainActivity extends PassphraseRequiredActivity {
|
||||
navigator.onCreate(savedInstanceState);
|
||||
|
||||
handleGroupLinkInIntent(getIntent());
|
||||
handleProxyInIntent(getIntent());
|
||||
|
||||
CachedInflater.from(this).clear();
|
||||
}
|
||||
@@ -56,6 +59,7 @@ public class MainActivity extends PassphraseRequiredActivity {
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleGroupLinkInIntent(intent);
|
||||
handleProxyInIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,6 +72,9 @@ public class MainActivity extends PassphraseRequiredActivity {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
if (SignalStore.misc().isOldDeviceTransferLocked()) {
|
||||
OldDeviceTransferLockedDialog.show(getSupportFragmentManager());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,4 +102,11 @@ public class MainActivity extends PassphraseRequiredActivity {
|
||||
CommunicationActions.handlePotentialGroupLinkUrl(this, data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleProxyInIntent(Intent intent) {
|
||||
Uri data = intent.getData();
|
||||
if (data != null) {
|
||||
CommunicationActions.handlePotentialProxyLinkUrl(this, data.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
||||
MediaPreviewFragment.Events
|
||||
{
|
||||
|
||||
private final static String TAG = MediaPreviewActivity.class.getSimpleName();
|
||||
private final static String TAG = Log.tag(MediaPreviewActivity.class);
|
||||
|
||||
private static final int NOT_IN_A_THREAD = -2;
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@ public class MuteDialog extends AlertDialog {
|
||||
|
||||
switch (which) {
|
||||
case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
||||
case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2); break;
|
||||
case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(8); break;
|
||||
case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
|
||||
case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
|
||||
case 4: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365); break;
|
||||
case 4: muteUntil = Long.MAX_VALUE; break;
|
||||
default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||
{
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = NewConversationActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(NewConversationActivity.class);
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, boolean ready) {
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
*/
|
||||
public abstract class PassphraseActivity extends BaseActivity {
|
||||
|
||||
private static final String TAG = PassphraseActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(PassphraseActivity.class);
|
||||
|
||||
private KeyCachingService keyCachingService;
|
||||
private MasterSecret masterSecret;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -46,9 +45,11 @@ import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import androidx.core.os.CancellationSignal;
|
||||
import androidx.biometric.BiometricManager;
|
||||
import androidx.biometric.BiometricManager.Authenticators;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
@@ -57,8 +58,10 @@ import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.DynamicIntroTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.SupportEmailUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
/**
|
||||
@@ -68,7 +71,12 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
*/
|
||||
public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
|
||||
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(PassphrasePromptActivity.class);
|
||||
private static final int BIOMETRIC_AUTHENTICATORS = Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK;
|
||||
private static final int ALLOWED_AUTHENTICATORS = BIOMETRIC_AUTHENTICATORS | Authenticators.DEVICE_CREDENTIAL;
|
||||
private static final short AUTHENTICATE_REQUEST_CODE = 1007;
|
||||
private static final String BUNDLE_ALREADY_SHOWN = "bundle_already_shown";
|
||||
public static final String FROM_FOREGROUND = "from_foreground";
|
||||
|
||||
private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme();
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
@@ -82,12 +90,13 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
private ImageButton hideButton;
|
||||
private AnimatingToggle visibilityToggle;
|
||||
|
||||
private FingerprintManagerCompat fingerprintManager;
|
||||
private CancellationSignal fingerprintCancellationSignal;
|
||||
private FingerprintListener fingerprintListener;
|
||||
private BiometricManager biometricManager;
|
||||
private BiometricPrompt biometricPrompt;
|
||||
private BiometricPrompt.PromptInfo biometricPromptInfo;
|
||||
|
||||
private boolean authenticated;
|
||||
private boolean failure;
|
||||
private boolean hadFailure;
|
||||
private boolean alreadyShown;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@@ -100,6 +109,15 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
|
||||
setContentView(R.layout.prompt_passphrase_activity);
|
||||
initializeResources();
|
||||
|
||||
alreadyShown = (savedInstanceState != null && savedInstanceState.getBoolean(BUNDLE_ALREADY_SHOWN)) ||
|
||||
getIntent().getBooleanExtra(FROM_FOREGROUND, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(BUNDLE_ALREADY_SHOWN, alreadyShown);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,20 +128,12 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
|
||||
setLockTypeVisibility();
|
||||
|
||||
if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !failure) {
|
||||
resumeScreenLock();
|
||||
if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !hadFailure) {
|
||||
resumeScreenLock(!alreadyShown);
|
||||
alreadyShown = true;
|
||||
}
|
||||
|
||||
failure = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||
pauseScreenLock();
|
||||
}
|
||||
hadFailure = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,7 +147,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
MenuInflater inflater = this.getMenuInflater();
|
||||
menu.clear();
|
||||
|
||||
inflater.inflate(R.menu.log_submit, menu);
|
||||
inflater.inflate(R.menu.passphrase_prompt, menu);
|
||||
|
||||
super.onCreateOptionsMenu(menu);
|
||||
return true;
|
||||
@@ -146,23 +156,28 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_submit_debug_logs: handleLogSubmit(); return true;
|
||||
if (item.getItemId() == R.id.menu_submit_debug_logs) {
|
||||
handleLogSubmit();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_contact_support) {
|
||||
sendEmailToSupport();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("MissingSuperCall") // no fragments to dispatch to
|
||||
public void onActivityResult(int requestCode, int resultcode, Intent data) {
|
||||
if (requestCode != 1) return;
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultcode == RESULT_OK) {
|
||||
if (requestCode != AUTHENTICATE_REQUEST_CODE) return;
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
handleAuthenticated();
|
||||
} else {
|
||||
Log.w(TAG, "Authentication failed");
|
||||
failure = true;
|
||||
hadFailure = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,16 +228,20 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
ImageButton okButton = findViewById(R.id.ok_button);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
|
||||
showButton = findViewById(R.id.passphrase_visibility);
|
||||
hideButton = findViewById(R.id.passphrase_visibility_off);
|
||||
visibilityToggle = findViewById(R.id.button_toggle);
|
||||
passphraseText = findViewById(R.id.passphrase_edit);
|
||||
passphraseAuthContainer = findViewById(R.id.password_auth_container);
|
||||
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
|
||||
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
||||
fingerprintManager = FingerprintManagerCompat.from(this);
|
||||
fingerprintCancellationSignal = new CancellationSignal();
|
||||
fingerprintListener = new FingerprintListener();
|
||||
showButton = findViewById(R.id.passphrase_visibility);
|
||||
hideButton = findViewById(R.id.passphrase_visibility_off);
|
||||
visibilityToggle = findViewById(R.id.button_toggle);
|
||||
passphraseText = findViewById(R.id.passphrase_edit);
|
||||
passphraseAuthContainer = findViewById(R.id.password_auth_container);
|
||||
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
|
||||
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
||||
biometricManager = BiometricManager.from(this);
|
||||
biometricPrompt = new BiometricPrompt(this, new BiometricAuthenticationListener());
|
||||
biometricPromptInfo = new BiometricPrompt.PromptInfo
|
||||
.Builder()
|
||||
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
|
||||
.setTitle(getString(R.string.PassphrasePromptActivity_unlock_signal))
|
||||
.build();
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setTitle("");
|
||||
@@ -242,20 +261,15 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
|
||||
|
||||
lockScreenButton.setOnClickListener(v -> resumeScreenLock());
|
||||
lockScreenButton.setOnClickListener(v -> resumeScreenLock(true));
|
||||
}
|
||||
|
||||
private void setLockTypeVisibility() {
|
||||
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||
passphraseAuthContainer.setVisibility(View.GONE);
|
||||
|
||||
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
||||
fingerprintPrompt.setVisibility(View.VISIBLE);
|
||||
lockScreenButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
fingerprintPrompt.setVisibility(View.GONE);
|
||||
lockScreenButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
fingerprintPrompt.setVisibility(biometricManager.canAuthenticate(BIOMETRIC_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS ? View.VISIBLE
|
||||
: View.GONE);
|
||||
lockScreenButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
passphraseAuthContainer.setVisibility(View.VISIBLE);
|
||||
fingerprintPrompt.setVisibility(View.GONE);
|
||||
@@ -263,7 +277,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void resumeScreenLock() {
|
||||
private void resumeScreenLock(boolean force) {
|
||||
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
||||
|
||||
assert keyguardManager != null;
|
||||
@@ -274,24 +288,36 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
||||
Log.i(TAG, "Listening for fingerprints...");
|
||||
fingerprintCancellationSignal = new CancellationSignal();
|
||||
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
|
||||
} else if (Build.VERSION.SDK_INT >= 21){
|
||||
Log.i(TAG, "firing intent...");
|
||||
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.PassphrasePromptActivity_unlock_signal), "");
|
||||
startActivityForResult(intent, 1);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private void pauseScreenLock() {
|
||||
if (fingerprintCancellationSignal != null) {
|
||||
fingerprintCancellationSignal.cancel();
|
||||
}
|
||||
private void sendEmailToSupport() {
|
||||
String body = SupportEmailUtil.generateSupportEmailBody(this,
|
||||
R.string.PassphrasePromptActivity_signal_android_lock_screen,
|
||||
null,
|
||||
null);
|
||||
CommunicationActions.openEmail(this,
|
||||
SupportEmailUtil.getSupportEmailAddress(this),
|
||||
getString(R.string.PassphrasePromptActivity_signal_android_lock_screen),
|
||||
body);
|
||||
}
|
||||
|
||||
private class PassphraseActionListener implements TextView.OnEditorActionListener {
|
||||
@@ -342,15 +368,19 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
System.gc();
|
||||
}
|
||||
|
||||
private class FingerprintListener extends FingerprintManagerCompat.AuthenticationCallback {
|
||||
private class BiometricAuthenticationListener extends BiometricPrompt.AuthenticationCallback {
|
||||
@Override
|
||||
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
||||
Log.w(TAG, "Authentication error: " + errMsgId + " " + errString);
|
||||
onAuthenticationFailed();
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errorString) {
|
||||
Log.w(TAG, "Authentication error: " + errorCode);
|
||||
hadFailure = true;
|
||||
|
||||
if (errorCode != BiometricPrompt.ERROR_CANCELED && errorCode != BiometricPrompt.ERROR_USER_CANCELED) {
|
||||
onAuthenticationFailed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
Log.i(TAG, "onAuthenticationSucceeded");
|
||||
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
|
||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
|
||||
@@ -367,8 +397,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
Log.w(TAG, "onAuthenticatoinFailed()");
|
||||
FingerprintManagerCompat.AuthenticationCallback callback = this;
|
||||
Log.w(TAG, "onAuthenticationFailed()");
|
||||
|
||||
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
||||
@@ -392,6 +421,5 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
|
||||
fingerprintPrompt.startAnimation(shake);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.util.tracing.Tracer;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
@@ -32,7 +35,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import java.util.Locale;
|
||||
|
||||
public abstract class PassphraseRequiredActivity extends BaseActivity implements MasterSecretListener {
|
||||
private static final String TAG = PassphraseRequiredActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(PassphraseRequiredActivity.class);
|
||||
|
||||
public static final String LOCALE_EXTRA = "locale_extra";
|
||||
public static final String NEXT_INTENT_EXTRA = "next_intent";
|
||||
@@ -45,6 +48,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
private static final int STATE_ENTER_SIGNAL_PIN = 5;
|
||||
private static final int STATE_CREATE_PROFILE_NAME = 6;
|
||||
private static final int STATE_CREATE_SIGNAL_PIN = 7;
|
||||
private static final int STATE_TRANSFER_ONGOING = 8;
|
||||
private static final int STATE_TRANSFER_LOCKED = 9;
|
||||
|
||||
private SignalServiceNetworkAccess networkAccess;
|
||||
private BroadcastReceiver clearKeyReceiver;
|
||||
@@ -78,7 +83,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
super.onResume();
|
||||
|
||||
if (networkAccess.isCensored(this)) {
|
||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
|
||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +96,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
@Override
|
||||
public void onMasterSecretCleared() {
|
||||
Log.d(TAG, "onMasterSecretCleared()");
|
||||
if (ApplicationContext.getInstance(this).isAppVisible()) routeApplicationState(true);
|
||||
else finish();
|
||||
if (ApplicationDependencies.getAppForegroundObserver().isForegrounded()) routeApplicationState(true);
|
||||
else finish();
|
||||
}
|
||||
|
||||
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||
@@ -146,6 +151,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
case STATE_ENTER_SIGNAL_PIN: return getEnterSignalPinIntent();
|
||||
case STATE_CREATE_SIGNAL_PIN: return getCreateSignalPinIntent();
|
||||
case STATE_CREATE_PROFILE_NAME: return getCreateProfileNameIntent();
|
||||
case STATE_TRANSFER_ONGOING: return getOldDeviceTransferIntent();
|
||||
case STATE_TRANSFER_LOCKED: return getOldDeviceTransferLockedIntent();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -165,6 +172,10 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
return STATE_CREATE_PROFILE_NAME;
|
||||
} else if (userMustCreateSignalPin()) {
|
||||
return STATE_CREATE_SIGNAL_PIN;
|
||||
} else if (EventBus.getDefault().getStickyEvent(TransferStatus.class) != null && getClass() != OldDeviceTransferActivity.class) {
|
||||
return STATE_TRANSFER_ONGOING;
|
||||
} else if (SignalStore.misc().isOldDeviceTransferLocked()) {
|
||||
return STATE_TRANSFER_LOCKED;
|
||||
} else {
|
||||
return STATE_NORMAL;
|
||||
}
|
||||
@@ -183,7 +194,9 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
}
|
||||
|
||||
private Intent getPromptPassphraseIntent() {
|
||||
return getRoutedIntent(PassphrasePromptActivity.class, getIntent());
|
||||
Intent intent = getRoutedIntent(PassphrasePromptActivity.class, getIntent());
|
||||
intent.putExtra(PassphrasePromptActivity.FROM_FOREGROUND, ApplicationDependencies.getAppForegroundObserver().isForegrounded());
|
||||
return intent;
|
||||
}
|
||||
|
||||
private Intent getUiBlockingUpgradeIntent() {
|
||||
@@ -194,7 +207,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
}
|
||||
|
||||
private Intent getPushRegistrationIntent() {
|
||||
return RegistrationNavigationActivity.newIntentForNewRegistration(this);
|
||||
return RegistrationNavigationActivity.newIntentForNewRegistration(this, getIntent());
|
||||
}
|
||||
|
||||
private Intent getEnterSignalPinIntent() {
|
||||
@@ -217,6 +230,19 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
return getRoutedIntent(EditProfileActivity.class, getIntent());
|
||||
}
|
||||
|
||||
private Intent getOldDeviceTransferIntent() {
|
||||
Intent intent = new Intent(this, OldDeviceTransferActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private @Nullable Intent getOldDeviceTransferLockedIntent() {
|
||||
if (getClass() == MainActivity.class) {
|
||||
return null;
|
||||
}
|
||||
return MainActivity.clearTop(this);
|
||||
}
|
||||
|
||||
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
|
||||
final Intent intent = new Intent(this, destination);
|
||||
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.os.Bundle;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
@@ -38,7 +39,7 @@ public class PushContactSelectionActivity extends ContactSelectionActivity {
|
||||
public static final String KEY_SELECTED_RECIPIENTS = "recipients";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final static String TAG = PushContactSelectionActivity.class.getSimpleName();
|
||||
private final static String TAG = Log.tag(PushContactSelectionActivity.class);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, boolean ready) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.net.URISyntaxException;
|
||||
|
||||
public class SmsSendtoActivity extends Activity {
|
||||
|
||||
private static final String TAG = SmsSendtoActivity.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(SmsSendtoActivity.class);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.CharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.MmsCharacterCalculator;
|
||||
@@ -25,7 +26,7 @@ import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
|
||||
public class TransportOptions {
|
||||
|
||||
private static final String TAG = TransportOptions.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(TransportOptions.class);
|
||||
|
||||
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
|
||||
private final Context context;
|
||||
|
||||
@@ -60,9 +60,12 @@ import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@@ -78,7 +81,6 @@ import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.DynamicDarkActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
@@ -91,14 +93,13 @@ import org.whispersystems.libsignal.fingerprint.Fingerprint;
|
||||
import org.whispersystems.libsignal.fingerprint.FingerprintParsingException;
|
||||
import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
|
||||
import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
/**
|
||||
* Activity for verifying identity keys.
|
||||
*
|
||||
@@ -113,7 +114,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
||||
private static final String IDENTITY_EXTRA = "recipient_identity";
|
||||
private static final String VERIFIED_EXTRA = "verified_state";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicDarkActionBarTheme();
|
||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
|
||||
private final VerifyDisplayFragment displayFragment = new VerifyDisplayFragment();
|
||||
private final VerifyScanFragment scanFragment = new VerifyScanFragment();
|
||||
@@ -161,11 +162,6 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.AndroidManifest__verify_safety_number);
|
||||
|
||||
LiveRecipient recipient = Recipient.live(getIntent().getParcelableExtra(RECIPIENT_EXTRA));
|
||||
recipient.observe(this, r -> setActionBarNotificationBarColor(r.getColor()));
|
||||
|
||||
setActionBarNotificationBarColor(recipient.get().getColor());
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putParcelable(VerifyDisplayFragment.RECIPIENT_ID, getIntent().getParcelableExtra(RECIPIENT_EXTRA));
|
||||
extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(IDENTITY_EXTRA));
|
||||
@@ -190,7 +186,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
||||
|
||||
@Override
|
||||
public void onQrDataFound(final String data) {
|
||||
Util.runOnMain(() -> {
|
||||
ThreadUtil.runOnMain(() -> {
|
||||
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
||||
|
||||
getSupportFragmentManager().popBackStack();
|
||||
@@ -306,6 +302,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
||||
byte[] localId;
|
||||
byte[] remoteId;
|
||||
|
||||
//noinspection WrongThread
|
||||
Recipient resolved = recipient.resolve();
|
||||
|
||||
if (FeatureFlags.verifyV2() && resolved.getUuid().isPresent()) {
|
||||
@@ -622,36 +619,35 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
|
||||
new AsyncTask<Recipient, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Recipient... params) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
if (isChecked) {
|
||||
Log.i(TAG, "Saving identity: " + params[0].getId());
|
||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||
.saveIdentity(params[0].getId(),
|
||||
remoteIdentity,
|
||||
VerifiedStatus.VERIFIED, false,
|
||||
System.currentTimeMillis(), true);
|
||||
} else {
|
||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||
.setVerified(params[0].getId(),
|
||||
remoteIdentity,
|
||||
VerifiedStatus.DEFAULT);
|
||||
}
|
||||
final Recipient recipient = this.recipient.get();
|
||||
final RecipientId recipientId = recipient.getId();
|
||||
|
||||
ApplicationDependencies.getJobManager()
|
||||
.add(new MultiDeviceVerifiedUpdateJob(recipient.getId(),
|
||||
remoteIdentity,
|
||||
isChecked ? VerifiedStatus.VERIFIED :
|
||||
VerifiedStatus.DEFAULT));
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
|
||||
IdentityUtil.markIdentityVerified(getActivity(), recipient.get(), isChecked, false);
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
|
||||
if (isChecked) {
|
||||
Log.i(TAG, "Saving identity: " + recipientId);
|
||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||
.saveIdentity(recipientId,
|
||||
remoteIdentity,
|
||||
VerifiedStatus.VERIFIED, false,
|
||||
System.currentTimeMillis(), true);
|
||||
} else {
|
||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||
.setVerified(recipientId,
|
||||
remoteIdentity,
|
||||
VerifiedStatus.DEFAULT);
|
||||
}
|
||||
return null;
|
||||
|
||||
ApplicationDependencies.getJobManager()
|
||||
.add(new MultiDeviceVerifiedUpdateJob(recipientId,
|
||||
remoteIdentity,
|
||||
isChecked ? VerifiedStatus.VERIFIED
|
||||
: VerifiedStatus.DEFAULT));
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
|
||||
IdentityUtil.markIdentityVerified(getActivity(), recipient, isChecked, false);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient.get());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,7 @@ import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Rational;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -37,14 +35,13 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionListenerAdapter;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
|
||||
import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil;
|
||||
@@ -53,22 +50,20 @@ import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
||||
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.service.webrtc.SignalCallManager;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.EllapsedTimeFormatter;
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -85,6 +80,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
||||
|
||||
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
||||
private DeviceOrientationMonitor deviceOrientationMonitor;
|
||||
|
||||
private FullscreenHelper fullscreenHelper;
|
||||
private WebRtcCallView callScreen;
|
||||
@@ -166,9 +162,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
if (!viewModel.isCallStarting()) {
|
||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().cancelPreJoin();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,7 +234,12 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
}
|
||||
|
||||
private void initializeViewModel() {
|
||||
viewModel = ViewModelProviders.of(this).get(WebRtcCallViewModel.class);
|
||||
deviceOrientationMonitor = new DeviceOrientationMonitor(this);
|
||||
getLifecycle().addObserver(deviceOrientationMonitor);
|
||||
|
||||
WebRtcCallViewModel.Factory factory = new WebRtcCallViewModel.Factory(deviceOrientationMonitor);
|
||||
|
||||
viewModel = ViewModelProviders.of(this, factory).get(WebRtcCallViewModel.class);
|
||||
viewModel.setIsInPipMode(isInPipMode());
|
||||
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
|
||||
viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls);
|
||||
@@ -256,12 +255,25 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||
if (state != null) {
|
||||
if (state.needsNewRequestSizes()) {
|
||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().updateRenderedResolutions();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getOrientation().observe(this, orientation -> {
|
||||
ApplicationDependencies.getSignalCallManager().orientationChanged(orientation.getDegrees());
|
||||
|
||||
switch (orientation) {
|
||||
case LANDSCAPE_LEFT_EDGE:
|
||||
callScreen.rotateControls(90);
|
||||
break;
|
||||
case LANDSCAPE_RIGHT_EDGE:
|
||||
callScreen.rotateControls(-90);
|
||||
break;
|
||||
case PORTRAIT_BOTTOM_EDGE:
|
||||
callScreen.rotateControls(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
|
||||
@@ -308,30 +320,19 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
}
|
||||
|
||||
private void handleSetAudioHandset() {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().setAudioSpeaker(false);
|
||||
}
|
||||
|
||||
private void handleSetAudioSpeaker() {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_SPEAKER, true);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().setAudioSpeaker(true);
|
||||
}
|
||||
|
||||
private void handleSetAudioBluetooth() {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_BLUETOOTH);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_BLUETOOTH, true);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().setAudioBluetooth(true);
|
||||
}
|
||||
|
||||
private void handleSetMuteAudio(boolean enabled) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_AUDIO);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_MUTE, enabled);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().setMuteAudio(enabled);
|
||||
}
|
||||
|
||||
private void handleSetMuteVideo(boolean muted) {
|
||||
@@ -345,20 +346,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName), R.drawable.ic_video_solid_24_tinted)
|
||||
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName))
|
||||
.onAllGranted(() -> {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_ENABLE_VIDEO);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_ENABLE, !muted);
|
||||
startService(intent);
|
||||
})
|
||||
.onAllGranted(() -> ApplicationDependencies.getSignalCallManager().setMuteVideo(!muted))
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFlipCamera() {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_FLIP_CAMERA);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().flipCamera();
|
||||
}
|
||||
|
||||
private void handleAnswerWithAudio() {
|
||||
@@ -375,9 +369,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
callScreen.setRecipient(recipient);
|
||||
callScreen.setStatus(getString(R.string.RedPhone_answering));
|
||||
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().acceptCall(false);
|
||||
})
|
||||
.onAnyDenied(this::handleDenyCall)
|
||||
.execute();
|
||||
@@ -398,10 +390,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
callScreen.setRecipient(recipient);
|
||||
callScreen.setStatus(getString(R.string.RedPhone_answering));
|
||||
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_ANSWER_WITH_VIDEO, true);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().acceptCall(true);
|
||||
|
||||
handleSetMuteVideo(false);
|
||||
})
|
||||
@@ -414,9 +403,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
Recipient recipient = viewModel.getRecipient().get();
|
||||
|
||||
if (!recipient.equals(Recipient.UNKNOWN)) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_DENY_CALL);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().denyCall();
|
||||
|
||||
callScreen.setRecipient(recipient);
|
||||
callScreen.setStatus(getString(R.string.RedPhone_ending_call));
|
||||
@@ -426,9 +413,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
|
||||
private void handleEndCall() {
|
||||
Log.i(TAG, "Hangup pressed, handling termination now...");
|
||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_LOCAL_HANGUP);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().localHangup();
|
||||
}
|
||||
|
||||
private void handleOutgoingCall(@NonNull WebRtcViewModel event) {
|
||||
@@ -459,7 +444,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
private void handleCallBusy() {
|
||||
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||
callScreen.setStatus(getString(R.string.RedPhone_busy));
|
||||
delayedFinish(WebRtcCallService.BUSY_TONE_LENGTH);
|
||||
delayedFinish(SignalCallManager.BUSY_TONE_LENGTH);
|
||||
}
|
||||
|
||||
private void handleCallConnected(@NonNull WebRtcViewModel event) {
|
||||
@@ -497,6 +482,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
final Recipient recipient = event.getRemoteParticipants().get(0).getRecipient();
|
||||
|
||||
if (theirKey == null) {
|
||||
Log.w(TAG, "Untrusted identity without an identity key, terminating call.");
|
||||
handleTerminate(recipient, HangupMessage.Type.NORMAL);
|
||||
}
|
||||
|
||||
@@ -515,7 +501,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
}
|
||||
|
||||
private void updateGroupMembersForGroupCall() {
|
||||
startService(new Intent(this, WebRtcCallService.class).setAction(WebRtcCallService.ACTION_GROUP_REQUEST_UPDATE_MEMBERS));
|
||||
ApplicationDependencies.getSignalCallManager().requestUpdateGroupMembers();
|
||||
}
|
||||
|
||||
private void updateSpeakerHint(boolean showSpeakerHint) {
|
||||
@@ -529,11 +515,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
@Override
|
||||
public void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> changedRecipients) {
|
||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.getGroupCallState().isConnected()) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_GROUP_APPROVE_SAFETY_CHANGE)
|
||||
.putExtra(WebRtcCallService.EXTRA_RECIPIENT_IDS, RecipientId.toSerializedList(changedRecipients));
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().groupApproveSafetyChange(changedRecipients);
|
||||
} else {
|
||||
viewModel.startCall(state.getLocalParticipant().isVideoEnabled());
|
||||
}
|
||||
@@ -547,9 +535,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||
if (state != null && state.getGroupCallState().isNotIdle()) {
|
||||
if (state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
|
||||
startService(intent);
|
||||
ApplicationDependencies.getSignalCallManager().cancelPreJoin();
|
||||
finish();
|
||||
} else {
|
||||
handleEndCall();
|
||||
@@ -615,11 +601,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
private void startCall(boolean isVideoCall) {
|
||||
enableVideoIfAvailable = isVideoCall;
|
||||
|
||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(viewModel.getRecipient().getId()))
|
||||
.putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, (isVideoCall ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL).getCode());
|
||||
startService(intent);
|
||||
if (isVideoCall) {
|
||||
ApplicationDependencies.getSignalCallManager().startOutgoingVideoCall(viewModel.getRecipient().get());
|
||||
} else {
|
||||
ApplicationDependencies.getSignalCallManager().startOutgoingAudioCall(viewModel.getRecipient().get());
|
||||
}
|
||||
|
||||
MessageSender.onMessageSent();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.nio.ByteBuffer;
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class AudioCodec {
|
||||
|
||||
private static final String TAG = AudioCodec.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(AudioCodec.class);
|
||||
|
||||
private static final int SAMPLE_RATE = 44100;
|
||||
private static final int SAMPLE_RATE_INDEX = 4;
|
||||
|
||||
@@ -8,11 +8,11 @@ import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
@@ -23,7 +23,7 @@ import java.util.concurrent.ExecutorService;
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class AudioRecorder {
|
||||
|
||||
private static final String TAG = AudioRecorder.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(AudioRecorder.class);
|
||||
|
||||
private static final ExecutorService executor = SignalExecutors.newCachedSingleThreadExecutor("signal-AudioRecorder");
|
||||
|
||||
@@ -90,10 +90,10 @@ public class AudioRecorder {
|
||||
}
|
||||
|
||||
private <T> void sendToFuture(final SettableFuture<T> future, final Exception exception) {
|
||||
Util.runOnMain(() -> future.setException(exception));
|
||||
ThreadUtil.runOnMain(() -> future.setException(exception));
|
||||
}
|
||||
|
||||
private <T> void sendToFuture(final SettableFuture<T> future, final T result) {
|
||||
Util.runOnMain(() -> future.set(result));
|
||||
ThreadUtil.runOnMain(() -> future.set(result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.core.util.Consumer;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
@@ -26,7 +27,6 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormDat
|
||||
import org.thoughtcrime.securesms.media.DecryptableUriMediaInput;
|
||||
import org.thoughtcrime.securesms.media.MediaInput;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -61,13 +61,13 @@ public final class AudioWaveForm {
|
||||
|
||||
if (uri == null) {
|
||||
Log.w(TAG, "No uri");
|
||||
Util.runOnMain(onFailure);
|
||||
ThreadUtil.runOnMain(onFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(attachment instanceof DatabaseAttachment)) {
|
||||
Log.i(TAG, "Not yet in database");
|
||||
Util.runOnMain(onFailure);
|
||||
ThreadUtil.runOnMain(onFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public final class AudioWaveForm {
|
||||
AudioFileInfo cached = WAVE_FORM_CACHE.get(cacheKey);
|
||||
if (cached != null) {
|
||||
Log.i(TAG, "Loaded wave form from cache " + cacheKey);
|
||||
Util.runOnMain(() -> onSuccess.accept(cached));
|
||||
ThreadUtil.runOnMain(() -> onSuccess.accept(cached));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public final class AudioWaveForm {
|
||||
AudioFileInfo cachedInExecutor = WAVE_FORM_CACHE.get(cacheKey);
|
||||
if (cachedInExecutor != null) {
|
||||
Log.i(TAG, "Loaded wave form from cache inside executor" + cacheKey);
|
||||
Util.runOnMain(() -> onSuccess.accept(cachedInExecutor));
|
||||
ThreadUtil.runOnMain(() -> onSuccess.accept(cachedInExecutor));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,14 +92,14 @@ public final class AudioWaveForm {
|
||||
AudioFileInfo audioFileInfo = AudioFileInfo.fromDatabaseProtobuf(audioHash.getAudioWaveForm());
|
||||
if (audioFileInfo.waveForm.length == 0) {
|
||||
Log.w(TAG, "Recovering from a wave form generation error " + cacheKey);
|
||||
Util.runOnMain(onFailure);
|
||||
ThreadUtil.runOnMain(onFailure);
|
||||
return;
|
||||
} else if (audioFileInfo.waveForm.length != BAR_COUNT) {
|
||||
Log.w(TAG, "Wave form from database does not match bar count, regenerating " + cacheKey);
|
||||
} else {
|
||||
WAVE_FORM_CACHE.put(cacheKey, audioFileInfo);
|
||||
Log.i(TAG, "Loaded wave form from DB " + cacheKey);
|
||||
Util.runOnMain(() -> onSuccess.accept(audioFileInfo));
|
||||
ThreadUtil.runOnMain(() -> onSuccess.accept(audioFileInfo));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -120,10 +120,10 @@ public final class AudioWaveForm {
|
||||
attachmentDatabase.writeAudioHash(dbAttachment.getAttachmentId(), fileInfo.toDatabaseProtobuf());
|
||||
|
||||
WAVE_FORM_CACHE.put(cacheKey, fileInfo);
|
||||
Util.runOnMain(() -> onSuccess.accept(fileInfo));
|
||||
ThreadUtil.runOnMain(() -> onSuccess.accept(fileInfo));
|
||||
} catch (Throwable e) {
|
||||
Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e);
|
||||
Util.runOnMain(onFailure);
|
||||
ThreadUtil.runOnMain(onFailure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public final class BackupPassphrase {
|
||||
private BackupPassphrase() {
|
||||
}
|
||||
|
||||
private static final String TAG = BackupPassphrase.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(BackupPassphrase.class);
|
||||
|
||||
public static @Nullable String get(@NonNull Context context) {
|
||||
String passphrase = TextSecurePreferences.getBackupPassphrase(context);
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
@@ -13,7 +14,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
public abstract class FullBackupBase {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = FullBackupBase.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(FullBackupBase.class);
|
||||
|
||||
static class BackupStream {
|
||||
static @NonNull byte[] getBackupKey(@NonNull String passphrase, @Nullable byte[] salt) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import com.annimon.stream.function.Consumer;
|
||||
import com.annimon.stream.function.Predicate;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
@@ -26,7 +25,6 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.JobDatabase;
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
@@ -36,9 +34,13 @@ import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.kdf.HKDFv3;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
@@ -66,8 +68,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class FullBackupExporter extends FullBackupBase {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = FullBackupExporter.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(FullBackupExporter.class);
|
||||
|
||||
private static final Set<String> BLACKLISTED_TABLES = SetUtil.newHashSet(
|
||||
SignedPreKeyDatabase.TABLE_NAME,
|
||||
@@ -81,11 +82,12 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
@NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull File output,
|
||||
@NonNull String passphrase)
|
||||
@NonNull String passphrase,
|
||||
@NonNull BackupCancellationSignal cancellationSignal)
|
||||
throws IOException
|
||||
{
|
||||
try (OutputStream outputStream = new FileOutputStream(output)) {
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase);
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase, true, cancellationSignal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,19 +96,32 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
@NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull DocumentFile output,
|
||||
@NonNull String passphrase)
|
||||
@NonNull String passphrase,
|
||||
@NonNull BackupCancellationSignal cancellationSignal)
|
||||
throws IOException
|
||||
{
|
||||
try (OutputStream outputStream = Objects.requireNonNull(context.getContentResolver().openOutputStream(output.getUri()))) {
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase);
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase, true, cancellationSignal);
|
||||
}
|
||||
}
|
||||
|
||||
public static void transfer(@NonNull Context context,
|
||||
@NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull OutputStream outputStream,
|
||||
@NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase, false, () -> false);
|
||||
}
|
||||
|
||||
private static void internalExport(@NonNull Context context,
|
||||
@NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull OutputStream fileOutputStream,
|
||||
@NonNull String passphrase)
|
||||
@NonNull String passphrase,
|
||||
boolean closeOutputStream,
|
||||
@NonNull BackupCancellationSignal cancellationSignal)
|
||||
throws IOException
|
||||
{
|
||||
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase);
|
||||
@@ -114,36 +129,51 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
|
||||
try {
|
||||
outputStream.writeDatabaseVersion(input.getVersion());
|
||||
count++;
|
||||
|
||||
List<String> tables = exportSchema(input, outputStream);
|
||||
count += tables.size() * 3;
|
||||
|
||||
Stopwatch stopwatch = new Stopwatch("Backup");
|
||||
|
||||
for (String table : tables) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
if (table.equals(MmsDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count);
|
||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, cancellationSignal);
|
||||
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count);
|
||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count, cancellationSignal);
|
||||
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count);
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count, cancellationSignal);
|
||||
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), cursor -> exportAttachment(attachmentSecret, cursor, outputStream), count);
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount), count, cancellationSignal);
|
||||
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> true, cursor -> exportSticker(attachmentSecret, cursor, outputStream), count);
|
||||
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount), count, cancellationSignal);
|
||||
} else if (!BLACKLISTED_TABLES.contains(table) && !table.startsWith("sqlite_")) {
|
||||
count = exportTable(table, input, outputStream, null, null, count);
|
||||
count = exportTable(table, input, outputStream, null, null, count, cancellationSignal);
|
||||
}
|
||||
stopwatch.split("table::" + table);
|
||||
}
|
||||
|
||||
for (BackupProtos.SharedPreference preference : IdentityKeyUtil.getBackupRecord(context)) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(preference);
|
||||
}
|
||||
|
||||
for (BackupProtos.SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(preference);
|
||||
}
|
||||
|
||||
stopwatch.split("prefs");
|
||||
|
||||
count = exportKeyValues(outputStream, SignalStore.getKeysToIncludeInBackup(), count, cancellationSignal);
|
||||
|
||||
stopwatch.split("key_values");
|
||||
|
||||
for (AvatarHelper.Avatar avatar : AvatarHelper.getAvatars(context)) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
if (avatar != null) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(avatar.getFilename(), avatar.getInputStream(), avatar.getLength());
|
||||
@@ -155,11 +185,19 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
|
||||
outputStream.writeEnd();
|
||||
} finally {
|
||||
outputStream.close();
|
||||
if (closeOutputStream) {
|
||||
outputStream.close();
|
||||
}
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count));
|
||||
}
|
||||
}
|
||||
|
||||
private static void throwIfCanceled(@NonNull BackupCancellationSignal cancellationSignal) throws BackupCanceledException {
|
||||
if (cancellationSignal.isCanceled()) {
|
||||
throw new BackupCanceledException();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> exportSchema(@NonNull SQLiteDatabase input, @NonNull BackupFrameOutputStream outputStream)
|
||||
throws IOException
|
||||
{
|
||||
@@ -190,19 +228,20 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
return tables;
|
||||
}
|
||||
|
||||
private static int exportTable(@NonNull String table,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull BackupFrameOutputStream outputStream,
|
||||
@Nullable Predicate<Cursor> predicate,
|
||||
@Nullable Consumer<Cursor> postProcess,
|
||||
int count)
|
||||
private static int exportTable(@NonNull String table,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull BackupFrameOutputStream outputStream,
|
||||
@Nullable Predicate<Cursor> predicate,
|
||||
@Nullable PostProcessor postProcess,
|
||||
int count,
|
||||
@NonNull BackupCancellationSignal cancellationSignal)
|
||||
throws IOException
|
||||
{
|
||||
String template = "INSERT INTO " + table + " VALUES ";
|
||||
|
||||
try (Cursor cursor = input.rawQuery("SELECT * FROM " + table, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
throwIfCanceled(cancellationSignal);
|
||||
|
||||
if (predicate == null || predicate.test(cursor)) {
|
||||
StringBuilder statement = new StringBuilder(template);
|
||||
@@ -234,9 +273,12 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
|
||||
statement.append(')');
|
||||
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(statementBuilder.setStatement(statement.toString()).build());
|
||||
|
||||
if (postProcess != null) postProcess.accept(cursor);
|
||||
if (postProcess != null) {
|
||||
count = postProcess.postProcess(cursor, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,7 +286,7 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream) {
|
||||
private static int exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count) {
|
||||
try {
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID));
|
||||
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID));
|
||||
@@ -269,14 +311,17 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
if (random != null && random.length == 32) inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
|
||||
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
|
||||
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream) {
|
||||
private static int exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count) {
|
||||
try {
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH));
|
||||
@@ -285,12 +330,15 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM));
|
||||
|
||||
if (!TextUtils.isEmpty(data) && size > 0) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
|
||||
outputStream.writeSticker(rowId, inputStream, size);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static long calculateVeryOldStreamLength(@NonNull AttachmentSecret attachmentSecret, @Nullable byte[] random, @NonNull String data) throws IOException {
|
||||
@@ -310,6 +358,46 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int exportKeyValues(@NonNull BackupFrameOutputStream outputStream,
|
||||
@NonNull List<String> keysToIncludeInBackup,
|
||||
int count,
|
||||
BackupCancellationSignal cancellationSignal) throws IOException
|
||||
{
|
||||
KeyValueDataSet dataSet = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication())
|
||||
.getDataSet();
|
||||
|
||||
for (String key : keysToIncludeInBackup) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
if (!dataSet.containsKey(key)) {
|
||||
continue;
|
||||
}
|
||||
BackupProtos.KeyValue.Builder builder = BackupProtos.KeyValue.newBuilder()
|
||||
.setKey(key);
|
||||
|
||||
Class<?> type = dataSet.getType(key);
|
||||
if (type == byte[].class) {
|
||||
builder.setBlobValue(ByteString.copyFrom(dataSet.getBlob(key, null)));
|
||||
} else if (type == Boolean.class) {
|
||||
builder.setBooleanValue(dataSet.getBoolean(key, false));
|
||||
} else if (type == Float.class) {
|
||||
builder.setFloatValue(dataSet.getFloat(key, 0));
|
||||
} else if (type == Integer.class) {
|
||||
builder.setIntegerValue(dataSet.getInteger(key, 0));
|
||||
} else if (type == Long.class) {
|
||||
builder.setLongValue(dataSet.getLong(key, 0));
|
||||
} else if (type == String.class) {
|
||||
builder.setStringValue(dataSet.getString(key, null));
|
||||
} else {
|
||||
throw new AssertionError("Unknown type: " + type);
|
||||
}
|
||||
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(builder.build());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static boolean isNonExpiringMmsMessage(@NonNull Cursor cursor) {
|
||||
return cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 &&
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) <= 0;
|
||||
@@ -381,6 +469,10 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder().setPreference(preference).build());
|
||||
}
|
||||
|
||||
public void write(BackupProtos.KeyValue keyValue) throws IOException {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder().setKeyValue(keyValue).build());
|
||||
}
|
||||
|
||||
public void write(BackupProtos.SqlStatement statement) throws IOException {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder().setStatement(statement).build());
|
||||
}
|
||||
@@ -495,4 +587,14 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
public interface PostProcessor {
|
||||
int postProcess(@NonNull Cursor cursor, int count);
|
||||
}
|
||||
|
||||
public interface BackupCancellationSignal {
|
||||
boolean isCanceled();
|
||||
}
|
||||
|
||||
public static final class BackupCanceledException extends IOException { }
|
||||
}
|
||||
|
||||
@@ -26,8 +26,11 @@ import org.thoughtcrime.securesms.backup.BackupProtos.Sticker;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||
@@ -45,6 +48,8 @@ import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -60,25 +65,37 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
public class FullBackupImporter extends FullBackupBase {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = FullBackupImporter.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(FullBackupImporter.class);
|
||||
|
||||
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
try (InputStream is = getInputStream(context, uri)) {
|
||||
importFile(context, attachmentSecret, db, is, passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase db, @NonNull InputStream is, @NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
try (InputStream is = getInputStream(context, uri)) {
|
||||
SQLiteDatabase keyValueDatabase = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).getSqlCipherDatabase();
|
||||
try {
|
||||
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
|
||||
|
||||
db.beginTransaction();
|
||||
keyValueDatabase.beginTransaction();
|
||||
|
||||
dropAllTables(db);
|
||||
|
||||
BackupFrame frame;
|
||||
|
||||
while (!(frame = inputStream.readFrame()).getEnd()) {
|
||||
if (count++ % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count));
|
||||
if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count));
|
||||
count++;
|
||||
|
||||
if (frame.hasVersion()) processVersion(db, frame.getVersion());
|
||||
else if (frame.hasStatement()) processStatement(db, frame.getStatement());
|
||||
@@ -86,18 +103,22 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
else if (frame.hasAttachment()) processAttachment(context, attachmentSecret, db, frame.getAttachment(), inputStream);
|
||||
else if (frame.hasSticker()) processSticker(context, attachmentSecret, db, frame.getSticker(), inputStream);
|
||||
else if (frame.hasAvatar()) processAvatar(context, db, frame.getAvatar(), inputStream);
|
||||
else if (frame.hasKeyValue()) processKeyValue(frame.getKeyValue());
|
||||
else count--;
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
keyValueDatabase.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
keyValueDatabase.endTransaction();
|
||||
}
|
||||
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, count));
|
||||
}
|
||||
|
||||
private static @NonNull InputStream getInputStream(@NonNull Context context, @NonNull Uri uri) throws IOException{
|
||||
if (BackupUtil.isUserSelectionRequired(context)) {
|
||||
if (BackupUtil.isUserSelectionRequired(context) || uri.getScheme().equals("content")) {
|
||||
return Objects.requireNonNull(context.getContentResolver().openInputStream(uri));
|
||||
} else {
|
||||
return new FileInputStream(new File(Objects.requireNonNull(uri.getPath())));
|
||||
@@ -201,10 +222,40 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
}
|
||||
}
|
||||
|
||||
private static void processKeyValue(BackupProtos.KeyValue keyValue) {
|
||||
KeyValueDataSet dataSet = new KeyValueDataSet();
|
||||
|
||||
if (keyValue.hasBlobValue()) {
|
||||
dataSet.putBlob(keyValue.getKey(), keyValue.getBlobValue().toByteArray());
|
||||
} else if (keyValue.hasBooleanValue()) {
|
||||
dataSet.putBoolean(keyValue.getKey(), keyValue.getBooleanValue());
|
||||
} else if (keyValue.hasFloatValue()) {
|
||||
dataSet.putFloat(keyValue.getKey(), keyValue.getFloatValue());
|
||||
} else if (keyValue.hasIntegerValue()) {
|
||||
dataSet.putInteger(keyValue.getKey(), keyValue.getIntegerValue());
|
||||
} else if (keyValue.hasLongValue()) {
|
||||
dataSet.putLong(keyValue.getKey(), keyValue.getLongValue());
|
||||
} else if (keyValue.hasStringValue()) {
|
||||
dataSet.putString(keyValue.getKey(), keyValue.getStringValue());
|
||||
} else {
|
||||
Log.i(TAG, "Unknown KeyValue backup value, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).writeDataSet(dataSet, Collections.emptyList());
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private static void processPreference(@NonNull Context context, SharedPreference preference) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(preference.getFile(), 0);
|
||||
preferences.edit().putString(preference.getKey(), preference.getValue()).commit();
|
||||
|
||||
if (preference.hasValue()) {
|
||||
preferences.edit().putString(preference.getKey(), preference.getValue()).commit();
|
||||
} else if (preference.hasBooleanValue()) {
|
||||
preferences.edit().putBoolean(preference.getKey(), preference.getBooleanValue()).commit();
|
||||
} else if (preference.hasIsStringSetValue() && preference.getIsStringSetValue()) {
|
||||
preferences.edit().putStringSet(preference.getKey(), new HashSet<>(preference.getStringSetValueList())).commit();
|
||||
}
|
||||
}
|
||||
|
||||
private static void dropAllTables(@NonNull SQLiteDatabase db) {
|
||||
|
||||
@@ -124,8 +124,6 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
||||
ContactSelectionListFragment fragment = new ContactSelectionListFragment();
|
||||
Intent intent = getIntent();
|
||||
|
||||
fragment.setOnContactSelectedListener(this);
|
||||
|
||||
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
||||
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, 1);
|
||||
intent.putExtra(ContactSelectionListFragment.HIDE_COUNT, true);
|
||||
|
||||
@@ -9,11 +9,12 @@ import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class AlertView extends LinearLayout {
|
||||
|
||||
private static final String TAG = AlertView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(AlertView.class);
|
||||
|
||||
private ImageView approvalIndicator;
|
||||
private ImageView failedIndicator;
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.audio.AudioWaveForm;
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
||||
@@ -42,7 +43,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class AudioView extends FrameLayout {
|
||||
|
||||
private static final String TAG = AudioView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(AudioView.class);
|
||||
|
||||
private static final int FORWARDS = 1;
|
||||
private static final int REVERSE = -1;
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
||||
@@ -39,7 +40,7 @@ public final class AvatarImageView extends AppCompatImageView {
|
||||
private static final int SIZE_SMALL = 2;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = AvatarImageView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(AvatarImageView.class);
|
||||
|
||||
private static final Paint LIGHT_THEME_OUTLINE_PAINT = new Paint();
|
||||
private static final Paint DARK_THEME_OUTLINE_PAINT = new Paint();
|
||||
|
||||
@@ -365,7 +365,7 @@ public class ComposeText extends EmojiEditText {
|
||||
|
||||
private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener {
|
||||
|
||||
private static final String TAG = CommitContentListener.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(CommitContentListener.class);
|
||||
|
||||
private final InputPanel.MediaListener mediaListener;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -130,6 +131,20 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
presentDeliveryStatus(messageRecord);
|
||||
}
|
||||
|
||||
public void enableBubbleBackground(@DrawableRes int drawableRes, @Nullable Integer tint) {
|
||||
setBackgroundResource(drawableRes);
|
||||
|
||||
if (tint != null) {
|
||||
getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY);
|
||||
} else {
|
||||
getBackground().clearColorFilter();
|
||||
}
|
||||
}
|
||||
|
||||
public void disableBubbleBackground() {
|
||||
setBackground(null);
|
||||
}
|
||||
|
||||
private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale) {
|
||||
dateView.forceLayout();
|
||||
if (messageRecord.isFailed()) {
|
||||
@@ -260,8 +275,8 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
addView(audioDuration, 0);
|
||||
|
||||
int padStart = ViewUtil.dpToPx(60);
|
||||
int padLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR ? padStart : 0;
|
||||
int padRight = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? padStart : 0;
|
||||
int padLeft = ViewUtil.isLtr(this) ? padStart : 0;
|
||||
int padRight = ViewUtil.isRtl(this) ? padStart : 0;
|
||||
|
||||
audioDuration.setPadding(padLeft, 0, padRight, 0);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ConversationTypingView extends LinearLayout {
|
||||
bubble.getBackground().setColorFilter(typist.getColor().toConversationColor(getContext()), PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
if (isGroupThread) {
|
||||
avatar.setAvatar(glideRequests, typist, false);
|
||||
avatar.setAvatar(glideRequests, typist, true);
|
||||
avatar.setVisibility(VISIBLE);
|
||||
} else {
|
||||
avatar.setVisibility(GONE);
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.net.URISyntaxException;
|
||||
|
||||
public class CustomDefaultPreference extends DialogPreference {
|
||||
|
||||
private static final String TAG = CustomDefaultPreference.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(CustomDefaultPreference.class);
|
||||
|
||||
private final int inputType;
|
||||
private final String customPreference;
|
||||
|
||||
@@ -10,11 +10,12 @@ import android.view.animation.RotateAnimation;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class DeliveryStatusView extends FrameLayout {
|
||||
|
||||
private static final String TAG = DeliveryStatusView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(DeliveryStatusView.class);
|
||||
|
||||
private static final RotateAnimation ROTATION_ANIMATION = new RotateAnimation(0, 360f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
|
||||
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
@@ -29,7 +30,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class DocumentView extends FrameLayout {
|
||||
|
||||
private static final String TAG = DocumentView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(DocumentView.class);
|
||||
|
||||
private final @NonNull AnimatingToggle controlToggle;
|
||||
private final @NonNull ImageView downloadButton;
|
||||
|
||||
@@ -6,8 +6,8 @@ import android.util.AttributeSet;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -67,7 +67,7 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag
|
||||
else stopped = false;
|
||||
}
|
||||
|
||||
Util.runOnMainDelayed(new AnimationUpdateRunnable(this), calculateAnimationDelay(this.startedAt, this.expiresIn));
|
||||
ThreadUtil.runOnMainDelayed(new AnimationUpdateRunnable(this), calculateAnimationDelay(this.startedAt, this.expiresIn));
|
||||
}
|
||||
|
||||
public void stopAnimation() {
|
||||
@@ -116,7 +116,7 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag
|
||||
}
|
||||
}
|
||||
|
||||
Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn));
|
||||
ThreadUtil.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,14 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
public class FromTextView extends EmojiTextView {
|
||||
|
||||
private static final String TAG = FromTextView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(FromTextView.class);
|
||||
|
||||
public FromTextView(Context context) {
|
||||
super(context);
|
||||
|
||||
@@ -22,10 +22,10 @@ import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
@@ -42,7 +42,6 @@ import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
@@ -59,7 +58,7 @@ public class InputPanel extends LinearLayout
|
||||
ConversationStickerSuggestionAdapter.EventListener
|
||||
{
|
||||
|
||||
private static final String TAG = InputPanel.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(InputPanel.class);
|
||||
|
||||
private static final long QUOTE_REVEAL_DURATION_MILLIS = 150;
|
||||
private static final int FADE_TIME = 150;
|
||||
@@ -74,6 +73,7 @@ public class InputPanel extends LinearLayout
|
||||
private View buttonToggle;
|
||||
private View recordingContainer;
|
||||
private View recordLockCancel;
|
||||
private View composeContainer;
|
||||
|
||||
private MicrophoneRecorderView microphoneRecorderView;
|
||||
private SlideToCancel slideToCancel;
|
||||
@@ -103,6 +103,7 @@ public class InputPanel extends LinearLayout
|
||||
|
||||
View quoteDismiss = findViewById(R.id.quote_dismiss);
|
||||
|
||||
this.composeContainer = findViewById(R.id.compose_bubble);
|
||||
this.stickerSuggestion = findViewById(R.id.input_panel_sticker_suggestion);
|
||||
this.quoteView = findViewById(R.id.quote_view);
|
||||
this.linkPreview = findViewById(R.id.link_preview);
|
||||
@@ -286,6 +287,16 @@ public class InputPanel extends LinearLayout
|
||||
return mediaKeyboard;
|
||||
}
|
||||
|
||||
public void setWallpaperEnabled(boolean enabled) {
|
||||
if (enabled) {
|
||||
setBackgroundColor(getContext().getResources().getColor(R.color.wallpaper_compose_background));
|
||||
composeContainer.setBackgroundResource(R.drawable.compose_background_wallpaper);
|
||||
} else {
|
||||
setBackgroundColor(getResources().getColor(R.color.signal_background_primary));
|
||||
composeContainer.setBackgroundResource(R.drawable.compose_background);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordPermissionRequired() {
|
||||
if (listener != null) listener.onRecorderPermissionRequired();
|
||||
@@ -323,11 +334,10 @@ public class InputPanel extends LinearLayout
|
||||
public void onRecordMoved(float offsetX, float absoluteX) {
|
||||
slideToCancel.moveTo(offsetX);
|
||||
|
||||
int direction = ViewCompat.getLayoutDirection(this);
|
||||
float position = absoluteX / recordingContainer.getWidth();
|
||||
|
||||
if (direction == ViewCompat.LAYOUT_DIRECTION_LTR && position <= 0.5 ||
|
||||
direction == ViewCompat.LAYOUT_DIRECTION_RTL && position >= 0.6)
|
||||
if (ViewUtil.isLtr(this) && position <= 0.5 ||
|
||||
ViewUtil.isRtl(this) && position >= 0.6)
|
||||
{
|
||||
this.microphoneRecorderView.cancelAction();
|
||||
}
|
||||
@@ -490,7 +500,7 @@ public class InputPanel extends LinearLayout
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.recordTimeView.setText(DateUtils.formatElapsedTime(0));
|
||||
ViewUtil.fadeIn(this.recordTimeView, FADE_TIME);
|
||||
Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1));
|
||||
ThreadUtil.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1));
|
||||
microphone.setVisibility(View.VISIBLE);
|
||||
microphone.startAnimation(pulseAnimation());
|
||||
}
|
||||
@@ -516,7 +526,7 @@ public class InputPanel extends LinearLayout
|
||||
onLimitHit.run();
|
||||
} else {
|
||||
recordTimeView.setText(DateUtils.formatElapsedTime(elapsedSeconds));
|
||||
Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1));
|
||||
ThreadUtil.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.Guideline;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class InsetAwareConstraintLayout extends ConstraintLayout {
|
||||
|
||||
public InsetAwareConstraintLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public InsetAwareConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public InsetAwareConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public InsetAwareConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fitSystemWindows(Rect insets) {
|
||||
Guideline statusBarGuideline = findViewById(R.id.status_bar_guideline);
|
||||
Guideline navigationBarGuideline = findViewById(R.id.navigation_bar_guideline);
|
||||
Guideline parentStartGuideline = findViewById(R.id.parent_start_guideline);
|
||||
Guideline parentEndGuideline = findViewById(R.id.parent_end_guideline);
|
||||
|
||||
if (statusBarGuideline != null) {
|
||||
statusBarGuideline.setGuidelineBegin(insets.top);
|
||||
}
|
||||
|
||||
if (navigationBarGuideline != null) {
|
||||
navigationBarGuideline.setGuidelineEnd(insets.bottom);
|
||||
}
|
||||
|
||||
if (parentStartGuideline != null) {
|
||||
if (ViewUtil.isLtr(this)) {
|
||||
parentStartGuideline.setGuidelineBegin(insets.left);
|
||||
} else {
|
||||
parentStartGuideline.setGuidelineBegin(insets.right);
|
||||
}
|
||||
}
|
||||
|
||||
if (parentEndGuideline != null) {
|
||||
if (ViewUtil.isLtr(this)) {
|
||||
parentEndGuideline.setGuidelineEnd(insets.right);
|
||||
} else {
|
||||
parentEndGuideline.setGuidelineEnd(insets.left);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ import java.util.Set;
|
||||
* has been opened and what its height would be.
|
||||
*/
|
||||
public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
|
||||
private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(KeyboardAwareLinearLayout.class);
|
||||
|
||||
private final Rect rect = new Rect();
|
||||
private final Set<OnKeyboardHiddenListener> hiddenListeners = new HashSet<>();
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -17,6 +16,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class LabeledEditText extends FrameLayout implements View.OnFocusChangeListener {
|
||||
|
||||
@@ -94,16 +94,6 @@ public class LabeledEditText extends FrameLayout implements View.OnFocusChangeLi
|
||||
}
|
||||
|
||||
public void focusAndMoveCursorToEndAndOpenKeyboard() {
|
||||
input.requestFocus();
|
||||
|
||||
int numberLength = getText().length();
|
||||
input.setSelection(numberLength, numberLength);
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT);
|
||||
|
||||
if (!imm.isAcceptingText()) {
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
}
|
||||
ViewUtil.focusAndMoveCursorToEndAndOpenKeyboard(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
/**
|
||||
* The view shown in the compose box or conversation that represents the state of the link preview.
|
||||
@@ -181,10 +182,16 @@ public class LinkPreviewView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void setCorners(int topLeft, int topRight) {
|
||||
cornerMask.setRadii(topLeft, topRight, 0, 0);
|
||||
outliner.setRadii(topLeft, topRight, 0, 0);
|
||||
thumbnail.setCorners(topLeft, defaultRadius, defaultRadius, defaultRadius);
|
||||
public void setCorners(int topStart, int topEnd) {
|
||||
if (ViewUtil.isRtl(this)) {
|
||||
cornerMask.setRadii(topEnd, topStart, 0, 0);
|
||||
outliner.setRadii(topEnd, topStart, 0, 0);
|
||||
thumbnail.setCorners(defaultRadius, topEnd, defaultRadius, defaultRadius);
|
||||
} else {
|
||||
cornerMask.setRadii(topStart, topEnd, 0, 0);
|
||||
outliner.setRadii(topStart, topEnd, 0, 0);
|
||||
thumbnail.setCorners(topStart, defaultRadius, defaultRadius, defaultRadius);
|
||||
}
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.core.view.ViewCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public final class MicrophoneRecorderView extends FrameLayout implements View.OnTouchListener {
|
||||
|
||||
@@ -224,8 +225,8 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
|
||||
}
|
||||
|
||||
private float getXOffset(float x) {
|
||||
return ViewCompat.getLayoutDirection(recordButtonFab) == ViewCompat.LAYOUT_DIRECTION_LTR ?
|
||||
-Math.max(0, this.startPositionX - x) : Math.max(0, x - this.startPositionX);
|
||||
return ViewUtil.isLtr(recordButtonFab) ? -Math.max(0, this.startPositionX - x)
|
||||
: Math.max(0, x - this.startPositionX);
|
||||
}
|
||||
|
||||
private float getYOffset(float y) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.RecipientsAdapter;
|
||||
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
|
||||
@@ -45,7 +46,7 @@ import java.util.StringTokenizer;
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class PushRecipientsPanel extends RelativeLayout implements RecipientForeverObserver {
|
||||
private final String TAG = PushRecipientsPanel.class.getSimpleName();
|
||||
private final String TAG = Log.tag(PushRecipientsPanel.class);
|
||||
private RecipientsPanelChangedListener panelChangeListener;
|
||||
|
||||
private RecipientsEditor recipientsText;
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.annotation.RequiresApi;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
||||
@@ -38,7 +39,7 @@ import java.util.List;
|
||||
|
||||
public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
|
||||
private static final String TAG = QuoteView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(QuoteView.class);
|
||||
|
||||
private static final int MESSAGE_TYPE_PREVIEW = 0;
|
||||
private static final int MESSAGE_TYPE_OUTGOING = 1;
|
||||
|
||||
@@ -10,7 +10,9 @@ import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
|
||||
@@ -21,10 +23,10 @@ public class RatingManager {
|
||||
private static final int DAYS_SINCE_INSTALL_THRESHOLD = 7;
|
||||
private static final int DAYS_UNTIL_REPROMPT_THRESHOLD = 4;
|
||||
|
||||
private static final String TAG = RatingManager.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(RatingManager.class);
|
||||
|
||||
public static void showRatingDialogIfNecessary(Context context) {
|
||||
if (!TextSecurePreferences.isRatingEnabled(context)) return;
|
||||
if (!TextSecurePreferences.isRatingEnabled(context) || BuildConfig.PLAY_STORE_DISABLED) return;
|
||||
|
||||
long daysSinceInstall = VersionTracker.getDaysSinceFirstInstalled(context);
|
||||
long laterTimestamp = TextSecurePreferences.getRatingLaterTimestamp(context);
|
||||
@@ -44,7 +46,7 @@ public class RatingManager {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
TextSecurePreferences.setRatingEnabled(context, false);
|
||||
startPlayStore(context);
|
||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(context);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.RatingManager_no_thanks, new DialogInterface.OnClickListener() {
|
||||
@@ -62,15 +64,4 @@ public class RatingManager {
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private static void startPlayStore(Context context) {
|
||||
Uri uri = Uri.parse("market://details?id=" + context.getPackageName());
|
||||
try {
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w(TAG, e);
|
||||
Toast.makeText(context, R.string.RatingManager_whoops_the_play_store_app_does_not_appear_to_be_installed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||
import com.bumptech.glide.signature.MediaStoreSignature;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader;
|
||||
@@ -82,7 +83,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo
|
||||
private static class RecentPhotoAdapter extends CursorRecyclerViewAdapter<RecentPhotoAdapter.RecentPhotoViewHolder> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = RecentPhotoAdapter.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(RecentPhotoAdapter.class);
|
||||
|
||||
@NonNull private final Uri baseUri;
|
||||
@Nullable private OnItemClickedListener clickedListener;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
|
||||
|
||||
/**
|
||||
* Selection aware {@link EmojiEditText}. This view allows the developer to provide an
|
||||
* {@link OnSelectionChangedListener} that will be notified when the selection is changed.
|
||||
*/
|
||||
public class SelectionAwareEmojiEditText extends EmojiEditText {
|
||||
|
||||
private OnSelectionChangedListener onSelectionChangedListener;
|
||||
|
||||
public SelectionAwareEmojiEditText(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SelectionAwareEmojiEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SelectionAwareEmojiEditText(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setOnSelectionChangedListener(@Nullable OnSelectionChangedListener onSelectionChangedListener) {
|
||||
this.onSelectionChangedListener = onSelectionChangedListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSelectionChanged(int selStart, int selEnd) {
|
||||
if (onSelectionChangedListener != null) {
|
||||
onSelectionChangedListener.onSelectionChanged(selStart, selEnd);
|
||||
}
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
}
|
||||
|
||||
public interface OnSelectionChangedListener {
|
||||
void onSelectionChanged(int selStart, int selEnd);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
@@ -61,7 +62,7 @@ public class ThreadPhotoRailView extends FrameLayout {
|
||||
private static class ThreadPhotoRailAdapter extends CursorRecyclerViewAdapter<ThreadPhotoRailAdapter.ThreadPhotoViewHolder> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = ThreadPhotoRailAdapter.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ThreadPhotoRailAdapter.class);
|
||||
|
||||
@NonNull private final GlideRequests glideRequests;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOption
|
||||
|
||||
public class ThumbnailView extends FrameLayout {
|
||||
|
||||
private static final String TAG = ThumbnailView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ThumbnailView.class);
|
||||
private static final int WIDTH = 0;
|
||||
private static final int HEIGHT = 1;
|
||||
private static final int MIN_WIDTH = 0;
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -26,7 +27,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@SuppressLint("UseSparseArrays")
|
||||
public class TypingStatusRepository {
|
||||
|
||||
private static final String TAG = TypingStatusRepository.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(TypingStatusRepository.class);
|
||||
|
||||
private static final long RECIPIENT_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(15);
|
||||
|
||||
@@ -58,11 +59,11 @@ public class TypingStatusRepository {
|
||||
|
||||
Runnable timer = timers.get(typist);
|
||||
if (timer != null) {
|
||||
Util.cancelRunnableOnMain(timer);
|
||||
ThreadUtil.cancelRunnableOnMain(timer);
|
||||
}
|
||||
|
||||
timer = () -> onTypingStopped(context, threadId, author, device, false);
|
||||
Util.runOnMainDelayed(timer, RECIPIENT_TYPING_TIMEOUT);
|
||||
ThreadUtil.runOnMainDelayed(timer, RECIPIENT_TYPING_TIMEOUT);
|
||||
timers.put(typist, timer);
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ public class TypingStatusRepository {
|
||||
|
||||
Runnable timer = timers.get(typist);
|
||||
if (timer != null) {
|
||||
Util.cancelRunnableOnMain(timer);
|
||||
ThreadUtil.cancelRunnableOnMain(timer);
|
||||
timers.remove(typist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -17,7 +15,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@SuppressLint("UseSparseArrays")
|
||||
public class TypingStatusSender {
|
||||
|
||||
private static final String TAG = TypingStatusSender.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(TypingStatusSender.class);
|
||||
|
||||
private static final long REFRESH_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
private static final long PAUSE_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(3);
|
||||
@@ -36,16 +34,16 @@ public class TypingStatusSender {
|
||||
sendTyping(threadId, true);
|
||||
|
||||
Runnable start = new StartRunnable(threadId);
|
||||
Util.runOnMainDelayed(start, REFRESH_TYPING_TIMEOUT);
|
||||
ThreadUtil.runOnMainDelayed(start, REFRESH_TYPING_TIMEOUT);
|
||||
pair.setStart(start);
|
||||
}
|
||||
|
||||
if (pair.getStop() != null) {
|
||||
Util.cancelRunnableOnMain(pair.getStop());
|
||||
ThreadUtil.cancelRunnableOnMain(pair.getStop());
|
||||
}
|
||||
|
||||
Runnable stop = () -> onTypingStopped(threadId, true);
|
||||
Util.runOnMainDelayed(stop, PAUSE_TYPING_TIMEOUT);
|
||||
ThreadUtil.runOnMainDelayed(stop, PAUSE_TYPING_TIMEOUT);
|
||||
pair.setStop(stop);
|
||||
}
|
||||
|
||||
@@ -62,7 +60,7 @@ public class TypingStatusSender {
|
||||
selfTypingTimers.put(threadId, pair);
|
||||
|
||||
if (pair.getStart() != null) {
|
||||
Util.cancelRunnableOnMain(pair.getStart());
|
||||
ThreadUtil.cancelRunnableOnMain(pair.getStart());
|
||||
|
||||
if (notify) {
|
||||
sendTyping(threadId, false);
|
||||
@@ -70,7 +68,7 @@ public class TypingStatusSender {
|
||||
}
|
||||
|
||||
if (pair.getStop() != null) {
|
||||
Util.cancelRunnableOnMain(pair.getStop());
|
||||
ThreadUtil.cancelRunnableOnMain(pair.getStop());
|
||||
}
|
||||
|
||||
pair.setStart(null);
|
||||
@@ -92,7 +90,7 @@ public class TypingStatusSender {
|
||||
@Override
|
||||
public void run() {
|
||||
sendTyping(threadId, true);
|
||||
Util.runOnMainDelayed(this, REFRESH_TYPING_TIMEOUT);
|
||||
ThreadUtil.runOnMainDelayed(this, REFRESH_TYPING_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,13 +35,17 @@ import java.io.InputStream;
|
||||
|
||||
public class ZoomingImageView extends FrameLayout {
|
||||
|
||||
private static final String TAG = ZoomingImageView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ZoomingImageView.class);
|
||||
|
||||
private static final int ZOOM_TRANSITION_DURATION = 300;
|
||||
|
||||
private static final float ZOOM_LEVEL_MIN = 1.0f;
|
||||
private static final float ZOOM_LEVEL_MID = 1.5f;
|
||||
private static final float ZOOM_LEVEL_MAX = 2.0f;
|
||||
|
||||
private static final float LARGE_IMAGES_ZOOM_LEVEL_MID = 1.5f;
|
||||
private static final float LARGE_IMAGES_ZOOM_LEVEL_MAX = 2.0f;
|
||||
|
||||
private static final float SMALL_IMAGES_ZOOM_LEVEL_MID = 3.0f;
|
||||
private static final float SMALL_IMAGES_ZOOM_LEVEL_MAX = 8.0f;
|
||||
|
||||
private final PhotoView photoView;
|
||||
private final SubsamplingScaleImageView subsamplingImageView;
|
||||
@@ -65,10 +69,11 @@ public class ZoomingImageView extends FrameLayout {
|
||||
this.subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_USE_EXIF);
|
||||
|
||||
this.photoView.setZoomTransitionDuration(ZOOM_TRANSITION_DURATION);
|
||||
this.photoView.setScaleLevels(ZOOM_LEVEL_MIN, ZOOM_LEVEL_MID, ZOOM_LEVEL_MAX);
|
||||
this.photoView.setScaleLevels(ZOOM_LEVEL_MIN, SMALL_IMAGES_ZOOM_LEVEL_MID, SMALL_IMAGES_ZOOM_LEVEL_MAX);
|
||||
|
||||
this.subsamplingImageView.setDoubleTapZoomDuration(ZOOM_TRANSITION_DURATION);
|
||||
this.subsamplingImageView.setDoubleTapZoomScale(ZOOM_LEVEL_MID);
|
||||
this.subsamplingImageView.setDoubleTapZoomScale(LARGE_IMAGES_ZOOM_LEVEL_MID);
|
||||
this.subsamplingImageView.setMaxScale(LARGE_IMAGES_ZOOM_LEVEL_MAX);
|
||||
|
||||
this.photoView.setOnClickListener(v -> ZoomingImageView.this.callOnClick());
|
||||
this.subsamplingImageView.setOnClickListener(v -> ZoomingImageView.this.callOnClick());
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Locale;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class CameraUtils {
|
||||
private static final String TAG = CameraUtils.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(CameraUtils.class);
|
||||
/*
|
||||
* modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
@@ -50,7 +51,7 @@ import java.util.List;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class CameraView extends ViewGroup {
|
||||
private static final String TAG = CameraView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(CameraView.class);
|
||||
|
||||
private final CameraSurfaceView surface;
|
||||
private final OnOrientationChange onOrientationChange;
|
||||
@@ -320,7 +321,7 @@ public class CameraView extends ViewGroup {
|
||||
camera.startPreview();
|
||||
Log.i(TAG, "camera.startPreview() -> " + (System.currentTimeMillis() - previewStartMillis) + "ms");
|
||||
state = State.ACTIVE;
|
||||
Util.runOnMain(new Runnable() {
|
||||
ThreadUtil.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
requestLayout();
|
||||
@@ -495,9 +496,9 @@ public class CameraView extends ViewGroup {
|
||||
return;
|
||||
}
|
||||
|
||||
Util.runOnMainSync(this::onPreMain);
|
||||
ThreadUtil.runOnMainSync(this::onPreMain);
|
||||
final Result result = onRunBackground();
|
||||
Util.runOnMainSync(() -> onPostMain(result));
|
||||
ThreadUtil.runOnMainSync(() -> onPostMain(result));
|
||||
}
|
||||
|
||||
protected boolean onWait() { return true; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.InputFilter;
|
||||
import android.util.AttributeSet;
|
||||
@@ -9,13 +10,14 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
|
||||
public class EmojiEditText extends AppCompatEditText {
|
||||
private static final String TAG = EmojiEditText.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(EmojiEditText.class);
|
||||
|
||||
public EmojiEditText(Context context) {
|
||||
this(context, null);
|
||||
@@ -27,7 +29,12 @@ public class EmojiEditText extends AppCompatEditText {
|
||||
|
||||
public EmojiEditText(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
if (!TextSecurePreferences.isSystemEmojiPreferred(getContext())) {
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiTextView, 0, 0);
|
||||
boolean forceCustom = a.getBoolean(R.styleable.EmojiTextView_emoji_forceCustom, false);
|
||||
a.recycle();
|
||||
|
||||
if (forceCustom || !TextSecurePreferences.isSystemEmojiPreferred(getContext())) {
|
||||
setFilters(appendEmojiFilter(this.getFilters()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
|
||||
{
|
||||
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
|
||||
|
||||
private static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
|
||||
public static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
|
||||
|
||||
private final Context context;
|
||||
private final List<EmojiPageModel> models;
|
||||
|
||||
@@ -10,12 +10,13 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
|
||||
|
||||
public class EmojiPageView extends FrameLayout implements VariationSelectorListener {
|
||||
private static final String TAG = EmojiPageView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(EmojiPageView.class);
|
||||
|
||||
private EmojiPageModel model;
|
||||
private EmojiPageViewGridAdapter adapter;
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
|
||||
@@ -25,7 +26,6 @@ import org.thoughtcrime.securesms.components.emoji.parsing.EmojiPageBitmap;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiTree;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
@@ -33,7 +33,7 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
class EmojiProvider {
|
||||
|
||||
private static final String TAG = EmojiProvider.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(EmojiProvider.class);
|
||||
private static volatile EmojiProvider instance = null;
|
||||
private static final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
@@ -118,7 +118,7 @@ class EmojiProvider {
|
||||
final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale);
|
||||
drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
|
||||
@Override public void onSuccess(final Bitmap result) {
|
||||
Util.runOnMain(() -> drawable.setBitmap(result));
|
||||
ThreadUtil.runOnMain(() -> drawable.setBitmap(result));
|
||||
}
|
||||
|
||||
@Override public void onFailure(ExecutionException error) {
|
||||
@@ -170,7 +170,7 @@ class EmojiProvider {
|
||||
|
||||
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
|
||||
public void setBitmap(Bitmap bitmap) {
|
||||
Util.assertMainThread();
|
||||
ThreadUtil.assertMainThread();
|
||||
if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB_MR1 || bmp == null || !bmp.sameAs(bitmap)) {
|
||||
bmp = bitmap;
|
||||
invalidateSelf();
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -137,6 +138,10 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getLayoutParams() != null && getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public void setOverflowText(@Nullable CharSequence overflowText) {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -72,4 +79,15 @@ public final class EmojiUtil {
|
||||
String canonical = VARIATION_MAP.get(emoji);
|
||||
return canonical != null ? canonical : emoji;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided emoji string into a single drawable, if possible.
|
||||
*/
|
||||
public static @Nullable Drawable convertToDrawable(@NonNull Context context, @Nullable String emoji) {
|
||||
if (Util.isEmpty(emoji)) {
|
||||
return null;
|
||||
} else {
|
||||
return EmojiProvider.getInstance(context).getEmojiDrawable(emoji);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
private static final String TAG = RecentEmojiPageModel.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(RecentEmojiPageModel.class);
|
||||
private static final int EMOJI_LRU_SIZE = 50;
|
||||
|
||||
private final SharedPreferences prefs;
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.graphics.BitmapFactory;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
@@ -22,7 +23,7 @@ import java.util.concurrent.Callable;
|
||||
|
||||
public class EmojiPageBitmap {
|
||||
|
||||
private static final String TAG = EmojiPageBitmap.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(EmojiPageBitmap.class);
|
||||
|
||||
private final Context context;
|
||||
private final EmojiPageModel model;
|
||||
@@ -39,7 +40,7 @@ public class EmojiPageBitmap {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public ListenableFutureTask<Bitmap> get() {
|
||||
Util.assertMainThread();
|
||||
ThreadUtil.assertMainThread();
|
||||
|
||||
if (bitmapReference != null && bitmapReference.get() != null) {
|
||||
return new ListenableFutureTask<>(bitmapReference.get());
|
||||
|
||||
@@ -8,15 +8,15 @@ import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSessionLock;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
public class UntrustedSendDialog extends AlertDialog.Builder implements DialogInterface.OnClickListener {
|
||||
|
||||
private final List<IdentityRecord> untrustedRecords;
|
||||
@@ -43,7 +43,7 @@ public class UntrustedSendDialog extends AlertDialog.Builder implements DialogIn
|
||||
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
synchronized (SESSION_LOCK) {
|
||||
try(SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
identityDatabase.setApproval(identityRecord.getRecipientId(), true);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.List;
|
||||
|
||||
public class UnverifiedBannerView extends LinearLayout {
|
||||
|
||||
private static final String TAG = UnverifiedBannerView.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(UnverifiedBannerView.class);
|
||||
|
||||
private View container;
|
||||
private TextView text;
|
||||
|
||||
@@ -8,14 +8,14 @@ import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSessionLock;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogInterface.OnClickListener {
|
||||
|
||||
private final List<IdentityRecord> untrustedRecords;
|
||||
@@ -44,7 +44,7 @@ public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogI
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
try(SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
identityDatabase.setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.io.IOException;
|
||||
public class SignalPlace {
|
||||
|
||||
private static final String URL = "https://maps.google.com/maps";
|
||||
private static final String TAG = SignalPlace.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(SignalPlace.class);
|
||||
|
||||
@JsonProperty
|
||||
private CharSequence name;
|
||||
|
||||
@@ -13,7 +13,7 @@ public class GroupsV1MigrationInitiationReminder extends Reminder {
|
||||
|
||||
public GroupsV1MigrationInitiationReminder(@NonNull Context context) {
|
||||
super(null, context.getString(R.string.GroupsV1MigrationInitiationReminder_to_access_new_features_like_mentions));
|
||||
addAction(new Action(context.getString(R.string.GroupsV1MigrationInitiationReminder_update_group), R.id.reminder_action_gv1_initiation_update_group));
|
||||
addAction(new Action(context.getString(R.string.GroupsV1MigrationInitiationReminder_upgrade_group), R.id.reminder_action_gv1_initiation_update_group));
|
||||
addAction(new Action(context.getResources().getString(R.string.GroupsV1MigrationInitiationReminder_not_now), R.id.reminder_action_gv1_initiation_not_now));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.thoughtcrime.securesms.components.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
public final class DeviceOrientationMonitor implements DefaultLifecycleObserver {
|
||||
|
||||
private static final float MAGNITUDE_MAXIMUM = 1.5f;
|
||||
private static final float MAGNITUDE_MINIMUM = 0.75f;
|
||||
private static final float LANDSCAPE_PITCH_MINIMUM = -0.5f;
|
||||
private static final float LANDSCAPE_PITCH_MAXIMUM = 0.5f;
|
||||
|
||||
private final SensorManager sensorManager;
|
||||
private final EventListener eventListener = new EventListener();
|
||||
|
||||
private final float[] accelerometerReading = new float[3];
|
||||
private final float[] magnetometerReading = new float[3];
|
||||
|
||||
private final float[] rotationMatrix = new float[9];
|
||||
private final float[] orientationAngles = new float[3];
|
||||
|
||||
private final MutableLiveData<Orientation> orientation = new MutableLiveData<>();
|
||||
|
||||
public DeviceOrientationMonitor(@NonNull Context context) {
|
||||
this.sensorManager = ServiceUtil.getSensorManager(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(@NonNull LifecycleOwner owner) {
|
||||
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
if (accelerometer != null) {
|
||||
sensorManager.registerListener(eventListener,
|
||||
accelerometer,
|
||||
SensorManager.SENSOR_DELAY_NORMAL,
|
||||
SensorManager.SENSOR_DELAY_UI);
|
||||
}
|
||||
Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
|
||||
if (magneticField != null) {
|
||||
sensorManager.registerListener(eventListener,
|
||||
magneticField,
|
||||
SensorManager.SENSOR_DELAY_NORMAL,
|
||||
SensorManager.SENSOR_DELAY_UI);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(@NonNull LifecycleOwner owner) {
|
||||
sensorManager.unregisterListener(eventListener);
|
||||
}
|
||||
|
||||
public LiveData<Orientation> getOrientation() {
|
||||
return Transformations.distinctUntilChanged(orientation);
|
||||
}
|
||||
|
||||
private void updateOrientationAngles() {
|
||||
boolean success = SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading);
|
||||
if (!success) {
|
||||
SensorUtil.getRotationMatrixWithoutMagneticSensorData(rotationMatrix, accelerometerReading);
|
||||
}
|
||||
SensorManager.getOrientation(rotationMatrix, orientationAngles);
|
||||
|
||||
float pitch = orientationAngles[1];
|
||||
float roll = orientationAngles[2];
|
||||
float mag = (float) Math.sqrt(Math.pow(pitch, 2) + Math.pow(roll, 2));
|
||||
|
||||
if (mag > MAGNITUDE_MAXIMUM || mag < MAGNITUDE_MINIMUM) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pitch > LANDSCAPE_PITCH_MINIMUM && pitch < LANDSCAPE_PITCH_MAXIMUM) {
|
||||
orientation.setValue(roll > 0 ? Orientation.LANDSCAPE_RIGHT_EDGE : Orientation.LANDSCAPE_LEFT_EDGE);
|
||||
} else {
|
||||
orientation.setValue(Orientation.PORTRAIT_BOTTOM_EDGE);
|
||||
}
|
||||
}
|
||||
|
||||
private final class EventListener implements SensorEventListener {
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
||||
System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.length);
|
||||
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
|
||||
System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.length);
|
||||
}
|
||||
|
||||
updateOrientationAngles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.thoughtcrime.securesms.components.sensors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public enum Orientation {
|
||||
PORTRAIT_BOTTOM_EDGE(0),
|
||||
LANDSCAPE_LEFT_EDGE(90),
|
||||
LANDSCAPE_RIGHT_EDGE(270);
|
||||
|
||||
private final int degrees;
|
||||
|
||||
Orientation(int degrees) {
|
||||
this.degrees = degrees;
|
||||
}
|
||||
|
||||
public int getDegrees() {
|
||||
return degrees;
|
||||
}
|
||||
|
||||
public static @NonNull Orientation fromDegrees(int degrees) {
|
||||
for (Orientation orientation : Orientation.values()) {
|
||||
if (orientation.degrees == degrees) {
|
||||
return orientation;
|
||||
}
|
||||
}
|
||||
|
||||
return PORTRAIT_BOTTOM_EDGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.thoughtcrime.securesms.components.sensors;
|
||||
|
||||
public final class SensorUtil {
|
||||
|
||||
private SensorUtil() { }
|
||||
|
||||
public static void getRotationMatrixWithoutMagneticSensorData(float[] rotationMatrix, float[] accelerometerReading) {
|
||||
double gx, gy, gz;
|
||||
gx = accelerometerReading[0] / 9.81f;
|
||||
gy = accelerometerReading[1] / 9.81f;
|
||||
gz = accelerometerReading[2] / 9.81f;
|
||||
|
||||
float pitch = (float) -Math.atan(gy / Math.sqrt(gx * gx + gz * gz));
|
||||
float roll = (float) -Math.atan(gx / Math.sqrt(gy * gy + gz * gz));
|
||||
float azimuth = 0;
|
||||
|
||||
float[] fakeMagnetometerReading = { azimuth, pitch, roll };
|
||||
|
||||
System.arraycopy(getRotationMatrixForOrientation(fakeMagnetometerReading), 0, rotationMatrix, 0, rotationMatrix.length);
|
||||
}
|
||||
|
||||
private static float[] getRotationMatrixForOrientation(float[] o) {
|
||||
float[] xM = new float[9];
|
||||
float[] yM = new float[9];
|
||||
float[] zM = new float[9];
|
||||
|
||||
float sinX = (float) Math.sin(o[1]);
|
||||
float cosX = (float) Math.cos(o[1]);
|
||||
float sinY = (float) Math.sin(o[2]);
|
||||
float cosY = (float) Math.cos(o[2]);
|
||||
float sinZ = (float) Math.sin(o[0]);
|
||||
float cosZ = (float) Math.cos(o[0]);
|
||||
|
||||
xM[0] = 1.0f;
|
||||
xM[1] = 0.0f;
|
||||
xM[2] = 0.0f;
|
||||
|
||||
xM[3] = 0.0f;
|
||||
xM[4] = cosX;
|
||||
xM[5] = sinX;
|
||||
|
||||
xM[6] = 0.0f;
|
||||
xM[7] = -sinX;
|
||||
xM[8] = cosX;
|
||||
|
||||
yM[0] = cosY;
|
||||
yM[1] = 0.0f;
|
||||
yM[2] = sinY;
|
||||
|
||||
yM[3] = 0.0f;
|
||||
yM[4] = 1.0f;
|
||||
yM[5] = 0.0f;
|
||||
|
||||
yM[6] = -sinY;
|
||||
yM[7] = 0.0f;
|
||||
yM[8] = cosY;
|
||||
|
||||
zM[0] = cosZ;
|
||||
zM[1] = sinZ;
|
||||
zM[2] = 0.0f;
|
||||
|
||||
zM[3] = -sinZ;
|
||||
zM[4] = cosZ;
|
||||
zM[5] = 0.0f;
|
||||
|
||||
zM[6] = 0.0f;
|
||||
zM[7] = 0.0f;
|
||||
zM[8] = 1.0f;
|
||||
|
||||
float[] resultMatrix = matrixMultiplication(xM, yM);
|
||||
resultMatrix = matrixMultiplication(zM, resultMatrix);
|
||||
return resultMatrix;
|
||||
}
|
||||
|
||||
private static float[] matrixMultiplication(float[] A, float[] B) {
|
||||
float[] result = new float[9];
|
||||
|
||||
result[0] = A[0] * B[0] + A[1] * B[3] + A[2] * B[6];
|
||||
result[1] = A[0] * B[1] + A[1] * B[4] + A[2] * B[7];
|
||||
result[2] = A[0] * B[2] + A[1] * B[5] + A[2] * B[8];
|
||||
|
||||
result[3] = A[3] * B[0] + A[4] * B[3] + A[5] * B[6];
|
||||
result[4] = A[3] * B[1] + A[4] * B[4] + A[5] * B[7];
|
||||
result[5] = A[3] * B[2] + A[4] * B[5] + A[5] * B[8];
|
||||
|
||||
result[6] = A[6] * B[0] + A[7] * B[3] + A[8] * B[6];
|
||||
result[7] = A[6] * B[1] + A[7] * B[4] + A[8] * B[7];
|
||||
result[8] = A[6] * B[2] + A[7] * B[5] + A[8] * B[8];
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import java.io.InputStream;
|
||||
|
||||
public class AttachmentRegionDecoder implements ImageRegionDecoder {
|
||||
|
||||
private static final String TAG = AttachmentRegionDecoder.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(AttachmentRegionDecoder.class);
|
||||
|
||||
private SkiaImageRegionDecoder passthrough;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.signal.core.util.logging.Log;
|
||||
*/
|
||||
public class HackyViewPager extends ViewPager {
|
||||
|
||||
private static final String TAG = HackyViewPager.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(HackyViewPager.class);
|
||||
|
||||
public HackyViewPager(Context context) {
|
||||
super(context);
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.core.widget.ImageViewCompat;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
@@ -29,7 +30,6 @@ import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.webrtc.RendererCommon;
|
||||
|
||||
@@ -154,7 +154,7 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
|
||||
private boolean isMissingMediaKeys(@NonNull CallParticipant participant) {
|
||||
if (missingMediaKeysUpdater != null) {
|
||||
Util.cancelRunnableOnMain(missingMediaKeysUpdater);
|
||||
ThreadUtil.cancelRunnableOnMain(missingMediaKeysUpdater);
|
||||
missingMediaKeysUpdater = null;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
setCallParticipant(participant);
|
||||
}
|
||||
};
|
||||
Util.runOnMainDelayed(missingMediaKeysUpdater, DELAY_SHOWING_MISSING_MEDIA_KEYS - time);
|
||||
ThreadUtil.runOnMainDelayed(missingMediaKeysUpdater, DELAY_SHOWING_MISSING_MEDIA_KEYS - time);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.webrtc.VideoSink;
|
||||
|
||||
public final class OrientationAwareVideoSink implements VideoSink {
|
||||
|
||||
private final VideoSink delegate;
|
||||
|
||||
public OrientationAwareVideoSink(@NonNull VideoSink delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrame(VideoFrame videoFrame) {
|
||||
if (videoFrame.getRotatedHeight() < videoFrame.getRotatedWidth()) {
|
||||
delegate.onFrame(new VideoFrame(videoFrame.getBuffer(), 270, videoFrame.getTimestampNs()));
|
||||
} else {
|
||||
delegate.onFrame(videoFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import androidx.core.view.GestureDetectorCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -259,8 +260,8 @@ public class PictureInPictureGestureHelper extends GestureDetector.SimpleOnGestu
|
||||
|
||||
private Point findNearestCornerPosition(Point projection) {
|
||||
if (isLockedToBottomEnd) {
|
||||
return parent.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? calculateBottomRightCoordinates(parent)
|
||||
: calculateBottomLeftCoordinates(parent);
|
||||
return ViewUtil.isLtr(parent) ? calculateBottomRightCoordinates(parent)
|
||||
: calculateBottomLeftCoordinates(parent);
|
||||
}
|
||||
|
||||
Point maxPoint = null;
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.ResizeAnimation;
|
||||
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
|
||||
@@ -119,6 +120,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||
private final Set<View> topViews = new HashSet<>();
|
||||
private final Set<View> visibleViewSet = new HashSet<>();
|
||||
private final Set<View> adjustableMarginsSet = new HashSet<>();
|
||||
private final Set<View> rotatableControls = new HashSet<>();
|
||||
|
||||
private WebRtcControls controls = WebRtcControls.NONE;
|
||||
private final Runnable fadeOutRunnable = () -> {
|
||||
@@ -250,6 +252,16 @@ public class WebRtcCallView extends FrameLayout {
|
||||
controlsListener.onCancelStartCall();
|
||||
}
|
||||
});
|
||||
|
||||
rotatableControls.add(hangup);
|
||||
rotatableControls.add(answer);
|
||||
rotatableControls.add(answerWithAudio);
|
||||
rotatableControls.add(audioToggle);
|
||||
rotatableControls.add(micToggle);
|
||||
rotatableControls.add(videoToggle);
|
||||
rotatableControls.add(cameraDirectionToggle);
|
||||
rotatableControls.add(decline);
|
||||
rotatableControls.add(smallLocalRender.findViewById(R.id.call_participant_mic_muted));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -287,6 +299,12 @@ public class WebRtcCallView extends FrameLayout {
|
||||
cancelFadeOut();
|
||||
}
|
||||
|
||||
public void rotateControls(int degrees) {
|
||||
for (View view : rotatableControls) {
|
||||
view.animate().rotation(degrees);
|
||||
}
|
||||
}
|
||||
|
||||
public void setControlsListener(@Nullable ControlsListener controlsListener) {
|
||||
this.controlsListener = controlsListener;
|
||||
}
|
||||
@@ -330,7 +348,6 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
|
||||
public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
|
||||
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
largeLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
|
||||
smallLocalRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
|
||||
@@ -346,11 +363,12 @@ public class WebRtcCallView extends FrameLayout {
|
||||
if (state == WebRtcLocalRenderState.EXPANDED) {
|
||||
expandPip(localCallParticipant, focusedParticipant);
|
||||
return;
|
||||
} else if (state == WebRtcLocalRenderState.SMALL_RECTANGLE && pictureInPictureExpansionHelper.isExpandedOrExpanding()) {
|
||||
} else if ((state == WebRtcLocalRenderState.SMALL_RECTANGLE || state == WebRtcLocalRenderState.GONE) && pictureInPictureExpansionHelper.isExpandedOrExpanding()) {
|
||||
shrinkPip(localCallParticipant);
|
||||
return;
|
||||
} else {
|
||||
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
@@ -597,11 +615,14 @@ public class WebRtcCallView extends FrameLayout {
|
||||
@Override
|
||||
public void onPictureInPictureExpanded() {
|
||||
largeLocalRenderFrame.setVisibility(View.VISIBLE);
|
||||
largeLocalRenderNoVideo.setVisibility(View.GONE);
|
||||
largeLocalRenderNoVideoAvatar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureNotVisible() {
|
||||
smallLocalRender.setCallParticipant(focusedParticipant);
|
||||
smallLocalRender.setMirror(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -626,6 +647,11 @@ public class WebRtcCallView extends FrameLayout {
|
||||
@Override
|
||||
public void onPictureInPictureNotVisible() {
|
||||
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
|
||||
if (!localCallParticipant.isVideoEnabled()) {
|
||||
smallLocalRenderFrame.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,9 +10,12 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
@@ -31,23 +34,25 @@ import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class WebRtcCallViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
|
||||
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
|
||||
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls);
|
||||
private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>();
|
||||
private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
|
||||
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
|
||||
private final MutableLiveData<CallParticipantsState> participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE);
|
||||
private final SingleLiveEvent<CallParticipantListUpdate> callParticipantListUpdate = new SingleLiveEvent<>();
|
||||
private final MutableLiveData<Collection<RecipientId>> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList());
|
||||
private final LiveData<SafetyNumberChangeEvent> safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new);
|
||||
private final LiveData<Recipient> groupRecipient = LiveDataUtil.filter(Transformations.switchMap(liveRecipient, LiveRecipient::getLiveData), Recipient::isActiveGroup);
|
||||
private final LiveData<List<GroupMemberEntry.FullMember>> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1);
|
||||
private final LiveData<Boolean> shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint);
|
||||
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
|
||||
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
|
||||
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls);
|
||||
private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>();
|
||||
private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
|
||||
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
|
||||
private final MutableLiveData<CallParticipantsState> participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE);
|
||||
private final SingleLiveEvent<CallParticipantListUpdate> callParticipantListUpdate = new SingleLiveEvent<>();
|
||||
private final MutableLiveData<Collection<RecipientId>> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList());
|
||||
private final LiveData<SafetyNumberChangeEvent> safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new);
|
||||
private final LiveData<Recipient> groupRecipient = LiveDataUtil.filter(Transformations.switchMap(liveRecipient, LiveRecipient::getLiveData), Recipient::isActiveGroup);
|
||||
private final LiveData<List<GroupMemberEntry.FullMember>> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1);
|
||||
private final LiveData<Boolean> shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint);
|
||||
private final LiveData<Orientation> orientation;
|
||||
|
||||
private boolean canDisplayTooltipIfNeeded = true;
|
||||
private boolean hasEnabledLocalVideo = false;
|
||||
@@ -61,6 +66,20 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
|
||||
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
|
||||
|
||||
private WebRtcCallViewModel(@NonNull DeviceOrientationMonitor deviceOrientationMonitor) {
|
||||
orientation = LiveDataUtil.combineLatest(deviceOrientationMonitor.getOrientation(), webRtcControls, (deviceOrientation, controls) -> {
|
||||
if (controls.canRotateControls()) {
|
||||
return deviceOrientation;
|
||||
} else {
|
||||
return Orientation.PORTRAIT_BOTTOM_EDGE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Orientation> getOrientation() {
|
||||
return Transformations.distinctUntilChanged(orientation);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getMicrophoneEnabled() {
|
||||
return Transformations.distinctUntilChanged(microphoneEnabled);
|
||||
}
|
||||
@@ -394,4 +413,18 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
return recipientIds;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final DeviceOrientationMonitor deviceOrientationMonitor;
|
||||
|
||||
public Factory(@NonNull DeviceOrientationMonitor deviceOrientationMonitor) {
|
||||
this.deviceOrientationMonitor = deviceOrientationMonitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
return Objects.requireNonNull(modelClass.cast(new WebRtcCallViewModel(deviceOrientationMonitor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ public final class WebRtcControls {
|
||||
this.participantLimit = participantLimit;
|
||||
}
|
||||
|
||||
boolean canRotateControls() {
|
||||
return !isGroupCall();
|
||||
}
|
||||
|
||||
boolean displayErrorControls() {
|
||||
return isError();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.thoughtcrime.securesms.contacts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractContactsCursorLoader extends CursorLoader {
|
||||
|
||||
private final String filter;
|
||||
|
||||
protected AbstractContactsCursorLoader(@NonNull Context context, @Nullable String filter) {
|
||||
super(context);
|
||||
|
||||
this.filter = sanitizeFilter(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Cursor loadInBackground() {
|
||||
List<Cursor> cursorList = TextUtils.isEmpty(filter) ? getUnfilteredResults()
|
||||
: getFilteredResults();
|
||||
if (cursorList.size() > 0) {
|
||||
return new MergeCursor(cursorList.toArray(new Cursor[0]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final String getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
protected abstract List<Cursor> getUnfilteredResults();
|
||||
|
||||
protected abstract List<Cursor> getFilteredResults();
|
||||
|
||||
private static @NonNull String sanitizeFilter(@Nullable String filter) {
|
||||
if (filter == null) {
|
||||
return "";
|
||||
} else if (filter.startsWith("@")) {
|
||||
return filter.substring(1);
|
||||
} else {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
@NonNull AbstractContactsCursorLoader create();
|
||||
}
|
||||
}
|
||||
@@ -23,28 +23,37 @@ import android.database.MergeCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Phone;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
import android.provider.ContactsContract.PhoneLookup;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
@@ -65,6 +74,9 @@ public class ContactAccessor {
|
||||
|
||||
public static final String PUSH_COLUMN = "push";
|
||||
|
||||
private static final String GIVEN_NAME = ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME;
|
||||
private static final String FAMILY_NAME = ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME;
|
||||
|
||||
private static final ContactAccessor instance = new ContactAccessor();
|
||||
|
||||
public static synchronized ContactAccessor getInstance() {
|
||||
@@ -85,55 +97,42 @@ public class ContactAccessor {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and returns a cursor of data for all contacts, containing both phone number data and
|
||||
* structured name data.
|
||||
*
|
||||
* Cursor rows are ordered as follows:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Contact Lookup Key</li>
|
||||
* <li>Mimetype</li>
|
||||
* <li>id</li>
|
||||
* </ol>
|
||||
*
|
||||
* The lookup key is a fixed value that allows you to verify two rows in the database actually
|
||||
* belong to the same contact, since the contact uri can be unstable (if a sync fails, say.)
|
||||
*
|
||||
* We order by id explicitly here for the same contact sync failure error, which could result in
|
||||
* multiple structured name rows for the same user. By ordering by id DESC, we ensure we get
|
||||
* whatever the latest input data was.
|
||||
*
|
||||
* What this results in is a cursor that looks like:
|
||||
*
|
||||
* Alice phone 1
|
||||
* Alice phone 2
|
||||
* Alice structured name 2
|
||||
* Alice structured name 1
|
||||
* Bob phone 1
|
||||
* ... etc.
|
||||
*/
|
||||
public Cursor getAllSystemContacts(Context context) {
|
||||
return context.getContentResolver().query(Phone.CONTENT_URI, new String[] {Phone.NUMBER, Phone.DISPLAY_NAME, Phone.LABEL, Phone.PHOTO_URI, Phone._ID, Phone.LOOKUP_KEY, Phone.TYPE}, null, null, null);
|
||||
}
|
||||
Uri uri = ContactsContract.Data.CONTENT_URI;
|
||||
String[] projection = SqlUtil.buildArgs(ContactsContract.Data.MIMETYPE, Phone.NUMBER, Phone.DISPLAY_NAME, Phone.LABEL, Phone.PHOTO_URI, Phone._ID, Phone.LOOKUP_KEY, Phone.TYPE, GIVEN_NAME, FAMILY_NAME);
|
||||
String where = ContactsContract.Data.MIMETYPE + " IN (?, ?)";
|
||||
String[] args = SqlUtil.buildArgs(Phone.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
|
||||
String orderBy = Phone.LOOKUP_KEY + " ASC, " + ContactsContract.Data.MIMETYPE + " DESC, " + ContactsContract.CommonDataKinds.Phone._ID + " DESC";
|
||||
|
||||
public boolean isSystemContact(Context context, String number) {
|
||||
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
|
||||
String[] projection = new String[]{PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY,
|
||||
PhoneLookup._ID, PhoneLookup.NUMBER};
|
||||
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Collection<ContactData> getContactsWithPush(Context context) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME};
|
||||
|
||||
final List<String> registeredAddresses = Stream.of(DatabaseFactory.getRecipientDatabase(context).getRegistered())
|
||||
.map(Recipient::resolved)
|
||||
.filter(r -> r.getE164().isPresent())
|
||||
.map(Recipient::requireE164)
|
||||
.toList();
|
||||
final Collection<ContactData> lookupData = new ArrayList<>(registeredAddresses.size());
|
||||
|
||||
for (String registeredAddress : registeredAddresses) {
|
||||
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(registeredAddress));
|
||||
Cursor lookupCursor = resolver.query(uri, inProjection, null, null, null);
|
||||
|
||||
try {
|
||||
if (lookupCursor != null && lookupCursor.moveToFirst()) {
|
||||
final ContactData contactData = new ContactData(lookupCursor.getLong(0), lookupCursor.getString(1));
|
||||
contactData.numbers.add(new NumberData("TextSecure", registeredAddress));
|
||||
lookupData.add(contactData);
|
||||
}
|
||||
} finally {
|
||||
if (lookupCursor != null)
|
||||
lookupCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
return lookupData;
|
||||
return context.getContentResolver().query(uri, projection, where, args, orderBy);
|
||||
}
|
||||
|
||||
public String getNameFromContact(Context context, Uri uri) {
|
||||
@@ -160,13 +159,13 @@ public class ContactAccessor {
|
||||
|
||||
private ContactData getContactData(Context context, String displayName, long id) {
|
||||
ContactData contactData = new ContactData(id, displayName);
|
||||
Cursor numberCursor = null;
|
||||
|
||||
try {
|
||||
numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null,
|
||||
Phone.CONTACT_ID + " = ?",
|
||||
new String[] {contactData.id + ""}, null);
|
||||
|
||||
try (Cursor numberCursor = context.getContentResolver().query(Phone.CONTENT_URI,
|
||||
null,
|
||||
Phone.CONTACT_ID + " = ?",
|
||||
new String[] {contactData.id + ""},
|
||||
null))
|
||||
{
|
||||
while (numberCursor != null && numberCursor.moveToNext()) {
|
||||
int type = numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE));
|
||||
String label = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL));
|
||||
@@ -175,9 +174,6 @@ public class ContactAccessor {
|
||||
|
||||
contactData.numbers.add(new NumberData(typeLabel, number));
|
||||
}
|
||||
} finally {
|
||||
if (numberCursor != null)
|
||||
numberCursor.close();
|
||||
}
|
||||
|
||||
return contactData;
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
@@ -42,28 +43,29 @@ public class ContactRepository {
|
||||
static final String NUMBER_TYPE_COLUMN = "number_type";
|
||||
static final String LABEL_COLUMN = "label";
|
||||
static final String CONTACT_TYPE_COLUMN = "contact_type";
|
||||
static final String ABOUT_COLUMN = "about";
|
||||
|
||||
static final int NORMAL_TYPE = 0;
|
||||
static final int PUSH_TYPE = 1;
|
||||
static final int NEW_PHONE_TYPE = 2;
|
||||
static final int NEW_USERNAME_TYPE = 3;
|
||||
static final int RECENT_TYPE = 4;
|
||||
static final int DIVIDER_TYPE = 5;
|
||||
static final int PUSH_TYPE = 1 << 0;
|
||||
static final int NEW_PHONE_TYPE = 1 << 2;
|
||||
static final int NEW_USERNAME_TYPE = 1 << 3;
|
||||
static final int RECENT_TYPE = 1 << 4;
|
||||
static final int DIVIDER_TYPE = 1 << 5;
|
||||
|
||||
/** Maps the recipient results to the legacy contact column names */
|
||||
private static final List<Pair<String, ValueMapper>> SEARCH_CURSOR_MAPPERS = new ArrayList<Pair<String, ValueMapper>>() {{
|
||||
add(new Pair<>(ID_COLUMN, cursor -> cursor.getLong(cursor.getColumnIndexOrThrow(RecipientDatabase.ID))));
|
||||
add(new Pair<>(ID_COLUMN, cursor -> CursorUtil.requireLong(cursor, RecipientDatabase.ID)));
|
||||
|
||||
add(new Pair<>(NAME_COLUMN, cursor -> {
|
||||
String system = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_DISPLAY_NAME));
|
||||
String profile = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SEARCH_PROFILE_NAME));
|
||||
String system = CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_JOINED_NAME);
|
||||
String profile = CursorUtil.requireString(cursor, RecipientDatabase.SEARCH_PROFILE_NAME);
|
||||
|
||||
return Util.getFirstNonEmpty(system, profile);
|
||||
}));
|
||||
|
||||
add(new Pair<>(NUMBER_COLUMN, cursor -> {
|
||||
String phone = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.PHONE));
|
||||
String email = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.EMAIL));
|
||||
String phone = CursorUtil.requireString(cursor, RecipientDatabase.PHONE);
|
||||
String email = CursorUtil.requireString(cursor, RecipientDatabase.EMAIL);
|
||||
|
||||
if (phone != null) {
|
||||
phone = PhoneNumberFormatter.prettyPrint(phone);
|
||||
@@ -72,14 +74,31 @@ public class ContactRepository {
|
||||
return Util.getFirstNonEmpty(phone, email);
|
||||
}));
|
||||
|
||||
add(new Pair<>(NUMBER_TYPE_COLUMN, cursor -> cursor.getInt(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_PHONE_TYPE))));
|
||||
add(new Pair<>(NUMBER_TYPE_COLUMN, cursor -> CursorUtil.requireInt(cursor, RecipientDatabase.SYSTEM_PHONE_TYPE)));
|
||||
|
||||
add(new Pair<>(LABEL_COLUMN, cursor -> cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_PHONE_LABEL))));
|
||||
add(new Pair<>(LABEL_COLUMN, cursor -> CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_PHONE_LABEL)));
|
||||
|
||||
add(new Pair<>(CONTACT_TYPE_COLUMN, cursor -> {
|
||||
int registered = cursor.getInt(cursor.getColumnIndexOrThrow(RecipientDatabase.REGISTERED));
|
||||
int registered = CursorUtil.requireInt(cursor, RecipientDatabase.REGISTERED);
|
||||
return registered == RecipientDatabase.RegisteredState.REGISTERED.getId() ? PUSH_TYPE : NORMAL_TYPE;
|
||||
}));
|
||||
|
||||
add(new Pair<>(ABOUT_COLUMN, cursor -> {
|
||||
String aboutEmoji = CursorUtil.requireString(cursor, RecipientDatabase.ABOUT_EMOJI);
|
||||
String about = CursorUtil.requireString(cursor, RecipientDatabase.ABOUT);
|
||||
|
||||
if (!Util.isEmpty(aboutEmoji)) {
|
||||
if (!Util.isEmpty(about)) {
|
||||
return aboutEmoji + " " + about;
|
||||
} else {
|
||||
return aboutEmoji;
|
||||
}
|
||||
} else if (!Util.isEmpty(about)) {
|
||||
return about;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}));
|
||||
}};
|
||||
|
||||
public ContactRepository(@NonNull Context context) {
|
||||
@@ -106,7 +125,7 @@ public class ContactRepository {
|
||||
|
||||
if (shouldAdd) {
|
||||
MatrixCursor selfCursor = new MatrixCursor(RecipientDatabase.SEARCH_PROJECTION_NAMES);
|
||||
selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, null, self.getE164().or(""), self.getEmail().orNull(), null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), noteToSelfTitle });
|
||||
selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, self.getE164().or(""), self.getEmail().orNull(), null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), self.getAbout(), self.getAboutEmoji(), noteToSelfTitle, noteToSelfTitle });
|
||||
|
||||
cursor = cursor == null ? selfCursor : new MergeCursor(new Cursor[]{ cursor, selfCursor });
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolde
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapter;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
@@ -97,7 +98,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkboxVisible);
|
||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible);
|
||||
public abstract void unbind(@NonNull GlideRequests glideRequests);
|
||||
public abstract void setChecked(boolean checked);
|
||||
public abstract void setEnabled(boolean enabled);
|
||||
@@ -117,8 +118,8 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
return (ContactSelectionListItem) itemView;
|
||||
}
|
||||
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkBoxVisible) {
|
||||
getView().set(glideRequests, recipientId, type, name, number, label, color, checkBoxVisible);
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkBoxVisible) {
|
||||
getView().set(glideRequests, recipientId, type, name, number, label, about, color, checkBoxVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,7 +148,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkboxVisible) {
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible) {
|
||||
this.label.setText(name);
|
||||
}
|
||||
|
||||
@@ -204,23 +205,25 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
String rawId = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN));
|
||||
String rawId = CursorUtil.requireString(cursor, ContactRepository.ID_COLUMN);
|
||||
RecipientId id = rawId != null ? RecipientId.from(rawId) : null;
|
||||
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.CONTACT_TYPE_COLUMN));
|
||||
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN ));
|
||||
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN));
|
||||
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN ));
|
||||
String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.LABEL_COLUMN ));
|
||||
int contactType = CursorUtil.requireInt(cursor, ContactRepository.CONTACT_TYPE_COLUMN);
|
||||
String name = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
|
||||
String number = CursorUtil.requireString(cursor, ContactRepository.NUMBER_COLUMN);
|
||||
int numberType = CursorUtil.requireInt(cursor, ContactRepository.NUMBER_TYPE_COLUMN);
|
||||
String about = CursorUtil.requireString(cursor, ContactRepository.ABOUT_COLUMN);
|
||||
String label = CursorUtil.requireString(cursor, ContactRepository.LABEL_COLUMN);
|
||||
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(getContext().getResources(),
|
||||
numberType, label).toString();
|
||||
boolean isPush = (contactType & ContactRepository.PUSH_TYPE) > 0;
|
||||
|
||||
int color = (contactType == ContactRepository.PUSH_TYPE) ? ContextCompat.getColor(getContext(), R.color.signal_text_primary)
|
||||
: ContextCompat.getColor(getContext(), R.color.signal_inverse_transparent_60);
|
||||
int color = isPush ? ContextCompat.getColor(getContext(), R.color.signal_text_primary)
|
||||
: ContextCompat.getColor(getContext(), R.color.signal_inverse_transparent_60);
|
||||
|
||||
boolean currentContact = currentContacts.contains(id);
|
||||
|
||||
viewHolder.unbind(glideRequests);
|
||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, color, multiSelect || currentContact);
|
||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, about, color, multiSelect || currentContact);
|
||||
viewHolder.setEnabled(true);
|
||||
|
||||
if (currentContact) {
|
||||
@@ -239,10 +242,10 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
String rawId = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN));
|
||||
String rawId = CursorUtil.requireString(cursor, ContactRepository.ID_COLUMN);
|
||||
RecipientId id = rawId != null ? RecipientId.from(rawId) : null;
|
||||
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN));
|
||||
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN));
|
||||
int numberType = CursorUtil.requireInt(cursor, ContactRepository.NUMBER_TYPE_COLUMN);
|
||||
String number = CursorUtil.requireString(cursor, ContactRepository.NUMBER_COLUMN);
|
||||
|
||||
viewHolder.setEnabled(true);
|
||||
|
||||
@@ -258,7 +261,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public int getItemViewType(@NonNull Cursor cursor) {
|
||||
if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.CONTACT_TYPE_COLUMN)) == ContactRepository.DIVIDER_TYPE) {
|
||||
if (CursorUtil.requireInt(cursor, ContactRepository.CONTACT_TYPE_COLUMN) == ContactRepository.DIVIDER_TYPE) {
|
||||
return VIEW_TYPE_DIVIDER;
|
||||
} else {
|
||||
return VIEW_TYPE_CONTACT;
|
||||
@@ -266,12 +269,12 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) {
|
||||
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
|
||||
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
|
||||
((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position));
|
||||
}
|
||||
|
||||
@@ -312,12 +315,12 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
private @NonNull String getHeaderString(int position) {
|
||||
int contactType = getContactType(position);
|
||||
|
||||
if (contactType == ContactRepository.RECENT_TYPE || contactType == ContactRepository.DIVIDER_TYPE) {
|
||||
if ((contactType & ContactRepository.RECENT_TYPE) > 0 || contactType == ContactRepository.DIVIDER_TYPE) {
|
||||
return " ";
|
||||
}
|
||||
|
||||
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||
String letter = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN));
|
||||
String letter = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
|
||||
|
||||
if (letter != null) {
|
||||
letter = letter.trim();
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.FromTextView;
|
||||
@@ -19,13 +20,14 @@ import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
public class ContactSelectionListItem extends LinearLayout implements RecipientForeverObserver {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = ContactSelectionListItem.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ContactSelectionListItem.class);
|
||||
|
||||
private AvatarImageView contactPhotoImage;
|
||||
private TextView numberView;
|
||||
@@ -65,6 +67,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
String name,
|
||||
String number,
|
||||
String label,
|
||||
String about,
|
||||
int color,
|
||||
boolean checkboxVisible)
|
||||
{
|
||||
@@ -87,7 +90,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.numberView.setTextColor(color);
|
||||
this.contactPhotoImage.setAvatar(glideRequests, recipientSnapshot, false);
|
||||
|
||||
setText(recipientSnapshot, type, name, number, label);
|
||||
setText(recipientSnapshot, type, name, number, label, about);
|
||||
|
||||
this.checkBox.setVisibility(checkboxVisible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
@@ -110,7 +113,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void setText(@Nullable Recipient recipient, int type, String name, String number, String label) {
|
||||
private void setText(@Nullable Recipient recipient, int type, String name, String number, String label, @Nullable String about) {
|
||||
if (number == null || number.isEmpty()) {
|
||||
this.nameView.setEnabled(false);
|
||||
this.numberView.setText("");
|
||||
@@ -120,7 +123,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.numberView.setText(getGroupMemberCount(recipient));
|
||||
this.labelView.setVisibility(View.GONE);
|
||||
} else if (type == ContactRepository.PUSH_TYPE) {
|
||||
this.numberView.setText(number);
|
||||
this.numberView.setText(!Util.isEmpty(about) ? about : number);
|
||||
this.nameView.setEnabled(true);
|
||||
this.labelView.setVisibility(View.GONE);
|
||||
} else if (type == ContactRepository.NEW_USERNAME_TYPE) {
|
||||
@@ -129,7 +132,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.labelView.setText(label);
|
||||
this.labelView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
this.numberView.setText(number);
|
||||
this.numberView.setText(!Util.isEmpty(about) ? about : number);
|
||||
this.nameView.setEnabled(true);
|
||||
this.labelView.setText(label != null && !label.equals("null") ? label : "");
|
||||
this.labelView.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -20,13 +20,8 @@ import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@@ -37,7 +32,6 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
@@ -51,9 +45,9 @@ import java.util.List;
|
||||
*
|
||||
* @author Jake McGinty
|
||||
*/
|
||||
public class ContactsCursorLoader extends CursorLoader {
|
||||
public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
||||
|
||||
private static final String TAG = ContactsCursorLoader.class.getSimpleName();
|
||||
private static final String TAG = Log.tag(ContactsCursorLoader.class);
|
||||
|
||||
public static final class DisplayMode {
|
||||
public static final int FLAG_PUSH = 1;
|
||||
@@ -63,59 +57,31 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
public static final int FLAG_SELF = 1 << 4;
|
||||
public static final int FLAG_BLOCK = 1 << 5;
|
||||
public static final int FLAG_HIDE_GROUPS_V1 = 1 << 5;
|
||||
public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_ACTIVE_GROUPS | FLAG_INACTIVE_GROUPS | FLAG_SELF;
|
||||
public static final int FLAG_HIDE_NEW = 1 << 6;
|
||||
public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_ACTIVE_GROUPS | FLAG_INACTIVE_GROUPS | FLAG_SELF;
|
||||
}
|
||||
|
||||
private static final String[] CONTACT_PROJECTION = new String[]{ContactRepository.ID_COLUMN,
|
||||
ContactRepository.NAME_COLUMN,
|
||||
ContactRepository.NUMBER_COLUMN,
|
||||
ContactRepository.NUMBER_TYPE_COLUMN,
|
||||
ContactRepository.LABEL_COLUMN,
|
||||
ContactRepository.CONTACT_TYPE_COLUMN};
|
||||
|
||||
private static final int RECENT_CONVERSATION_MAX = 25;
|
||||
|
||||
private final String filter;
|
||||
private final int mode;
|
||||
private final boolean recents;
|
||||
|
||||
private final ContactRepository contactRepository;
|
||||
|
||||
public ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents)
|
||||
private ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents)
|
||||
{
|
||||
super(context);
|
||||
super(context, filter);
|
||||
|
||||
if (flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS) && !flagSet(mode, DisplayMode.FLAG_ACTIVE_GROUPS)) {
|
||||
throw new AssertionError("Inactive group flag set, but the active group flag isn't!");
|
||||
}
|
||||
|
||||
this.filter = sanitizeFilter(filter);
|
||||
this.mode = mode;
|
||||
this.recents = recents;
|
||||
this.contactRepository = new ContactRepository(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
List<Cursor> cursorList = TextUtils.isEmpty(filter) ? getUnfilteredResults()
|
||||
: getFilteredResults();
|
||||
if (cursorList.size() > 0) {
|
||||
return new MergeCursor(cursorList.toArray(new Cursor[0]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @NonNull String sanitizeFilter(@Nullable String filter) {
|
||||
if (filter == null) {
|
||||
return "";
|
||||
} else if (filter.startsWith("@")) {
|
||||
return filter.substring(1);
|
||||
} else {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Cursor> getUnfilteredResults() {
|
||||
protected final List<Cursor> getUnfilteredResults() {
|
||||
ArrayList<Cursor> cursorList = new ArrayList<>();
|
||||
|
||||
if (groupsOnly(mode)) {
|
||||
@@ -129,13 +95,16 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
private List<Cursor> getFilteredResults() {
|
||||
protected final List<Cursor> getFilteredResults() {
|
||||
ArrayList<Cursor> cursorList = new ArrayList<>();
|
||||
|
||||
addContactsSection(cursorList);
|
||||
addGroupsSection(cursorList);
|
||||
addNewNumberSection(cursorList);
|
||||
addUsernameSearchSection(cursorList);
|
||||
|
||||
if (!hideNewNumberOrUsername(mode)) {
|
||||
addNewNumberSection(cursorList);
|
||||
addUsernameSearchSection(cursorList);
|
||||
}
|
||||
|
||||
return cursorList;
|
||||
}
|
||||
@@ -148,7 +117,7 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
Cursor recentConversations = getRecentConversationsCursor();
|
||||
|
||||
if (recentConversations.getCount() > 0) {
|
||||
cursorList.add(getRecentsHeaderCursor());
|
||||
cursorList.add(ContactsCursorRows.forRecentsHeader(getContext()));
|
||||
cursorList.add(recentConversations);
|
||||
}
|
||||
}
|
||||
@@ -157,8 +126,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
List<Cursor> contacts = getContactsCursors();
|
||||
|
||||
if (!isCursorListEmpty(contacts)) {
|
||||
cursorList.add(getContactsHeaderCursor());
|
||||
cursorList.addAll(getContactsCursors());
|
||||
cursorList.add(ContactsCursorRows.forContactsHeader(getContext()));
|
||||
cursorList.addAll(contacts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +139,7 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
Cursor groups = getRecentConversationsCursor(true);
|
||||
|
||||
if (groups.getCount() > 0) {
|
||||
cursorList.add(getRecentsHeaderCursor());
|
||||
cursorList.add(ContactsCursorRows.forRecentsHeader(getContext()));
|
||||
cursorList.add(groups);
|
||||
}
|
||||
}
|
||||
@@ -183,84 +152,28 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
Cursor groups = getGroupsCursor();
|
||||
|
||||
if (groups.getCount() > 0) {
|
||||
cursorList.add(getGroupsHeaderCursor());
|
||||
cursorList.add(ContactsCursorRows.forGroupsHeader(getContext()));
|
||||
cursorList.add(groups);
|
||||
}
|
||||
}
|
||||
|
||||
private void addNewNumberSection(@NonNull List<Cursor> cursorList) {
|
||||
if (FeatureFlags.usernames() && NumberUtil.isVisuallyValidNumberOrEmail(filter)) {
|
||||
cursorList.add(getPhoneNumberSearchHeaderCursor());
|
||||
if (FeatureFlags.usernames() && NumberUtil.isVisuallyValidNumberOrEmail(getFilter())) {
|
||||
cursorList.add(ContactsCursorRows.forPhoneNumberSearchHeader(getContext()));
|
||||
cursorList.add(getNewNumberCursor());
|
||||
} else if (!FeatureFlags.usernames() && NumberUtil.isValidSmsOrEmail(filter)){
|
||||
cursorList.add(getPhoneNumberSearchHeaderCursor());
|
||||
} else if (!FeatureFlags.usernames() && NumberUtil.isValidSmsOrEmail(getFilter())) {
|
||||
cursorList.add(ContactsCursorRows.forPhoneNumberSearchHeader(getContext()));
|
||||
cursorList.add(getNewNumberCursor());
|
||||
}
|
||||
}
|
||||
|
||||
private void addUsernameSearchSection(@NonNull List<Cursor> cursorList) {
|
||||
if (FeatureFlags.usernames() && UsernameUtil.isValidUsernameForSearch(filter)) {
|
||||
cursorList.add(getUsernameSearchHeaderCursor());
|
||||
if (FeatureFlags.usernames() && UsernameUtil.isValidUsernameForSearch(getFilter())) {
|
||||
cursorList.add(ContactsCursorRows.forUsernameSearchHeader(getContext()));
|
||||
cursorList.add(getUsernameSearchCursor());
|
||||
}
|
||||
}
|
||||
|
||||
private Cursor getRecentsHeaderCursor() {
|
||||
MatrixCursor recentsHeader = new MatrixCursor(CONTACT_PROJECTION);
|
||||
recentsHeader.addRow(new Object[]{ null,
|
||||
getContext().getString(R.string.ContactsCursorLoader_recent_chats),
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
return recentsHeader;
|
||||
}
|
||||
|
||||
private Cursor getContactsHeaderCursor() {
|
||||
MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
contactsHeader.addRow(new Object[] { null,
|
||||
getContext().getString(R.string.ContactsCursorLoader_contacts),
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
return contactsHeader;
|
||||
}
|
||||
|
||||
private Cursor getGroupsHeaderCursor() {
|
||||
MatrixCursor groupHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
groupHeader.addRow(new Object[]{ null,
|
||||
getContext().getString(R.string.ContactsCursorLoader_groups),
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
return groupHeader;
|
||||
}
|
||||
|
||||
private Cursor getPhoneNumberSearchHeaderCursor() {
|
||||
MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
contactsHeader.addRow(new Object[] { null,
|
||||
getContext().getString(R.string.ContactsCursorLoader_phone_number_search),
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
return contactsHeader;
|
||||
}
|
||||
|
||||
private Cursor getUsernameSearchHeaderCursor() {
|
||||
MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
contactsHeader.addRow(new Object[] { null,
|
||||
getContext().getString(R.string.ContactsCursorLoader_username_search),
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
return contactsHeader;
|
||||
}
|
||||
|
||||
|
||||
private Cursor getRecentConversationsCursor() {
|
||||
return getRecentConversationsCursor(false);
|
||||
}
|
||||
@@ -268,20 +181,12 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
private Cursor getRecentConversationsCursor(boolean groupsOnly) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(getContext());
|
||||
|
||||
MatrixCursor recentConversations = new MatrixCursor(CONTACT_PROJECTION, RECENT_CONVERSATION_MAX);
|
||||
try (Cursor rawConversations = threadDatabase.getRecentConversationList(RECENT_CONVERSATION_MAX, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), groupsOnly, hideGroupsV1(mode))) {
|
||||
MatrixCursor recentConversations = ContactsCursorRows.createMatrixCursor(RECENT_CONVERSATION_MAX);
|
||||
try (Cursor rawConversations = threadDatabase.getRecentConversationList(RECENT_CONVERSATION_MAX, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), groupsOnly, hideGroupsV1(mode), !smsEnabled(mode))) {
|
||||
ThreadDatabase.Reader reader = threadDatabase.readerFor(rawConversations);
|
||||
ThreadRecord threadRecord;
|
||||
ThreadRecord threadRecord;
|
||||
while ((threadRecord = reader.getNext()) != null) {
|
||||
Recipient recipient = threadRecord.getRecipient();
|
||||
String stringId = recipient.isGroup() ? recipient.requireGroupId().toString() : recipient.getE164().transform(PhoneNumberFormatter::prettyPrint).or(recipient.getEmail()).or("");
|
||||
|
||||
recentConversations.addRow(new Object[] { recipient.getId().serialize(),
|
||||
recipient.getDisplayName(getContext()),
|
||||
stringId,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.RECENT_TYPE });
|
||||
recentConversations.addRow(ContactsCursorRows.forRecipient(getContext(), threadRecord.getRecipient()));
|
||||
}
|
||||
}
|
||||
return recentConversations;
|
||||
@@ -295,53 +200,34 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
}
|
||||
|
||||
if (pushEnabled(mode)) {
|
||||
cursorList.add(contactRepository.querySignalContacts(filter, selfEnabled(mode)));
|
||||
cursorList.add(contactRepository.querySignalContacts(getFilter(), selfEnabled(mode)));
|
||||
}
|
||||
|
||||
if (pushEnabled(mode) && smsEnabled(mode)) {
|
||||
cursorList.add(contactRepository.queryNonSignalContacts(filter));
|
||||
cursorList.add(contactRepository.queryNonSignalContacts(getFilter()));
|
||||
} else if (smsEnabled(mode)) {
|
||||
cursorList.add(filterNonPushContacts(contactRepository.queryNonSignalContacts(filter)));
|
||||
cursorList.add(filterNonPushContacts(contactRepository.queryNonSignalContacts(getFilter())));
|
||||
}
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
private Cursor getGroupsCursor() {
|
||||
MatrixCursor groupContacts = new MatrixCursor(CONTACT_PROJECTION);
|
||||
try (GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(getContext()).getGroupsFilteredByTitle(filter, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode))) {
|
||||
MatrixCursor groupContacts = ContactsCursorRows.createMatrixCursor();
|
||||
try (GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(getContext()).getGroupsFilteredByTitle(getFilter(), flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode))) {
|
||||
GroupDatabase.GroupRecord groupRecord;
|
||||
while ((groupRecord = reader.getNext()) != null) {
|
||||
groupContacts.addRow(new Object[] { groupRecord.getRecipientId().serialize(),
|
||||
groupRecord.getTitle(),
|
||||
groupRecord.getId(),
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||
"",
|
||||
ContactRepository.NORMAL_TYPE });
|
||||
groupContacts.addRow(ContactsCursorRows.forGroup(groupRecord));
|
||||
}
|
||||
}
|
||||
return groupContacts;
|
||||
}
|
||||
|
||||
private Cursor getNewNumberCursor() {
|
||||
MatrixCursor newNumberCursor = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
newNumberCursor.addRow(new Object[] { null,
|
||||
getUnknownContactTitle(),
|
||||
filter,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||
"\u21e2",
|
||||
ContactRepository.NEW_PHONE_TYPE});
|
||||
return newNumberCursor;
|
||||
return ContactsCursorRows.forNewNumber(getUnknownContactTitle(), getFilter());
|
||||
}
|
||||
|
||||
private Cursor getUsernameSearchCursor() {
|
||||
MatrixCursor cursor = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
cursor.addRow(new Object[] { null,
|
||||
getUnknownContactTitle(),
|
||||
filter,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||
"\u21e2",
|
||||
ContactRepository.NEW_USERNAME_TYPE});
|
||||
return cursor;
|
||||
return ContactsCursorRows.forUsernameSearch(getUnknownContactTitle(), getFilter());
|
||||
}
|
||||
|
||||
private String getUnknownContactTitle() {
|
||||
@@ -356,19 +242,14 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
|
||||
private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) {
|
||||
try {
|
||||
final long startMillis = System.currentTimeMillis();
|
||||
final MatrixCursor matrix = new MatrixCursor(CONTACT_PROJECTION);
|
||||
final long startMillis = System.currentTimeMillis();
|
||||
final MatrixCursor matrix = ContactsCursorRows.createMatrixCursor();
|
||||
while (cursor.moveToNext()) {
|
||||
final RecipientId id = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN)));
|
||||
final Recipient recipient = Recipient.resolved(id);
|
||||
|
||||
if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) {
|
||||
matrix.addRow(new Object[]{cursor.getLong(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.LABEL_COLUMN)),
|
||||
ContactRepository.NORMAL_TYPE});
|
||||
matrix.addRow(ContactsCursorRows.forNonPushContact(cursor));
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms");
|
||||
@@ -418,7 +299,31 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
return flagSet(mode, DisplayMode.FLAG_HIDE_GROUPS_V1);
|
||||
}
|
||||
|
||||
private static boolean hideNewNumberOrUsername(int mode) {
|
||||
return flagSet(mode, DisplayMode.FLAG_HIDE_NEW);
|
||||
}
|
||||
|
||||
private static boolean flagSet(int mode, int flag) {
|
||||
return (mode & flag) > 0;
|
||||
}
|
||||
|
||||
public static class Factory implements AbstractContactsCursorLoader.Factory {
|
||||
|
||||
private final Context context;
|
||||
private final int displayMode;
|
||||
private final String cursorFilter;
|
||||
private final boolean displayRecents;
|
||||
|
||||
public Factory(Context context, int displayMode, String cursorFilter, boolean displayRecents) {
|
||||
this.context = context;
|
||||
this.displayMode = displayMode;
|
||||
this.cursorFilter = cursorFilter;
|
||||
this.displayRecents = displayRecents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull AbstractContactsCursorLoader create() {
|
||||
return new ContactsCursorLoader(context, displayMode, cursorFilter, displayRecents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user