diff --git a/build.gradle b/build.gradle
index f09e16b5a..8a76aaa27 100644
--- a/build.gradle
+++ b/build.gradle
@@ -77,7 +77,7 @@ dependencies {
compile 'com.google.android.exoplayer:exoplayer-core:2.8.4'
compile 'com.google.android.exoplayer:exoplayer-ui:2.8.4'
- compile 'org.whispersystems:signal-service-android:2.10.0-RC1'
+ compile 'org.whispersystems:signal-service-android:2.10.0'
compile 'org.whispersystems:webrtc-android:M69'
compile "me.leolin:ShortcutBadger:1.1.16"
@@ -147,6 +147,127 @@ dependencies {
}
}
+dependencyVerification {
+ verify = [
+ 'com.android.support:design:7874ad1904eedc74aa41cffffb7f759d8990056f3bbbc9264911651c67c42f5f',
+ 'com.android.support:preference-v14:8133c6e19233fa51e036a341e6d3f4adeead3375cebf777efced0fe154c3267e',
+ 'com.android.support:preference-v7:75eabe936d1fc3b178450a554c4d433466036f2be6d6dccdf971eac9590fdbf5',
+ 'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
+ 'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
+ 'mobi.upod:time-duration-picker:db469ce0f48dd96b892eac424ed76870e54bf00fe0a28cdcddfbe5f2a226a0e1',
+ 'com.codewaves.stickyheadergrid:stickyheadergrid:5b4aa6a52a957cfd55f60f4220c11c0c371385a3cb9786cae03c260dcdef5794',
+ 'com.android.support:appcompat-v7:a3a8e5230359746ed91801579b5fbe4668e3b1c4e6a14c7d67c8f58cb0311752',
+ 'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
+ 'com.android.support:recyclerview-v7:eb296414c1f6d4c7b522f69fe50588ea85297855db0e7806c24eb4f75409587d',
+ 'com.android.support:support-v13:491f940c5d6d2ec7678fa2f14bd4bbbe8bf776e2c776d04bf0e5c2175975be43',
+ 'com.android.support:cardview-v7:bc9e6b0e06ce1205f1db34f0e6193019613d19cfeb54cdccea722340d1c60f26',
+ 'com.android.support:gridlayout-v7:5029529f7db66f8773426bf7318645f0840fc50d74f66355cd60c5e58d2da087',
+ 'com.android.support:exifinterface:bbf44e519edd6333a24a3285aa21fd00181b920b81ca8aa89a8899f03ab4d6b0',
+ 'com.android.support:multidex:ecf6098572e23b5155bab3b9a82b2fd1530eda6c6c157745e0f5287c66eec60c',
+ 'android.arch.work:work-runtime:eda29b2cad202dee05a2e5aafe0a37c93ba9cde8f7cc0d0c8926a9f1a9498a8f',
+ 'android.arch.lifecycle:extensions:429426b2feec2245ffc5e75b3b5309bedb36159cf06dc71843ae43526ac289b6',
+ 'android.arch.lifecycle:common-java8:7078b5c8ccb94203df9cc2a463c69cf0021596e6cf966d78fbfd697aaafe0630',
+ 'com.google.android.gms:play-services-gcm:312e61253a236f2d9b750b9c04fc92fd190d23b0b2755c99de6ce4a28b259dae',
+ 'com.google.android.gms:play-services-places:abf3a4a3b146ec7e6e753be62775e512868cf37d6f88ffe2d81167b33b57132b',
+ 'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e',
+ 'com.google.android.exoplayer:exoplayer-ui:027557b2d69b15e1852a2530b36971f0dcc177abae240ee35e05f63502cdb0a7',
+ 'com.google.android.exoplayer:exoplayer-core:e69b409e11887c955deb373357c30eeabf183395db0092b4817e0f80bb467d5b',
+ 'org.whispersystems:signal-service-android:f6f144dc5df938b3a7d58dacf734f3f673e402b241a51b429de6b19ea3ef0393',
+ 'org.whispersystems:webrtc-android:5493c92141ce884fc5ce8240d783232f4fe14bd17a8d0d7d1bd4944d0bd1682f',
+ 'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
+ 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
+ 'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
+ 'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
+ 'com.github.chrisbanes:PhotoView:ed06775308da260e1fd86d1d3288988fcd3d80db24ce0d7c9fcfedc39e622292',
+ 'com.github.bumptech.glide:glide:997de7ac95be6c944d3b8cbe13de11307736ea45451c1b09a6cec7c328ead59f',
+ 'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1',
+ 'org.greenrobot:eventbus:180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c',
+ 'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
+ 'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
+ 'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
+ 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
+ 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
+ 'com.davemorrissey.labs:subsampling-scale-image-view:550c5baa07e0bb4ff0a18b705e96d34436d22619248bd8c08c08c730b1f55cfe',
+ 'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729',
+ 'com.tomergoldst.android:tooltips:4c56697dd1ad64b8066535c61f961a6d901e7ae5d97ae27084ba40ad620349b6',
+ 'com.klinkerapps:android-smsmms:e7c3328a0f3a8dd44daa8129de4e99996f3057a4546e47891b036b81e0ebf1d1',
+ 'com.annimon:stream:5da6e2e3e0551d61a3ea7014f04312276549e3dd739cf637996e4cf43c5535b9',
+ 'com.takisoft.fix:colorpicker:f5d0dbabe406a1800498ca9c1faf34db36e021d8488bf10360f29961fe3ab0d1',
+ 'com.github.dmytrodanylyk.circular-progress-button:library:8dc6a29a5a8db7b2ad5a9a7fda1dc9ae0893f4c8f0545732b2c63854ea693e8e',
+ 'org.signal:android-database-sqlcipher:33d4063336893af00b9d68b418e7b290cace74c20ce8aacffddc0911010d3d73',
+ 'com.googlecode.ez-vcard:ez-vcard:7e24ad50b222d2f70ac91bdccfa3c0f6200b078d797cb784837f75e77bb4210f',
+ 'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
+ 'com.google.android.gms:play-services-base:0ca636a8fc9a5af45e607cdcd61783bf5d561cbbb0f862021ce69606eee5ad49',
+ 'com.google.android.gms:play-services-tasks:69ec265168e601d0203d04cd42e34bb019b2f029aa1e16fabd38a5153eea2086',
+ 'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d',
+ 'com.android.support:support-v4:8b9031381c678d628c9e47b566ae1d161e1c9710f7855c759beeac7596cecf30',
+ 'com.android.support:support-fragment:3772fc738ada86824ba1a4b3f197c3dbd67b7ddcfe2c9db1de95ef2e3487a915',
+ 'com.android.support:animated-vector-drawable:271ecbc906cda8dcd9e655ba0473129c3408a4189c806f616c378e6fd18fb3b7',
+ 'com.android.support:support-core-ui:bbc7f65fc95649464733af373361532ab5f9f3b749c3badaa2bbf27e574b6c6f',
+ 'com.android.support:transition:45d09fc51284c17bbab300f5122512ac7d7348a6d23bda2051648bbe76cc9aa5',
+ 'com.android.support:viewpager:013c4c53058758ec104dbae970be58159f75dfe342ba8b937d15ff5282e35ffc',
+ 'com.android.support:coordinatorlayout:9dfacd80423dc979048fbaed83c0ee543c46259feb2417377e79a656888d3892',
+ 'com.android.support:drawerlayout:8f6809afae4793550c37461c9810e954ae6a23dbb4d23e5333bf18148df1150a',
+ 'com.android.support:slidingpanelayout:d1d234f66a1b36a9aee9b94fa6c66f97128c0828078c8e889e9037ec898cd600',
+ 'com.android.support:customview:98db03845f994e08248bf701c1ff0ccaa12e70f94251ec9272900f0f694e072b',
+ 'com.android.support:swiperefreshlayout:a3b41f7f6730866b49865e86e49f988d4858699765f534300fb2ff5f9325e712',
+ 'com.android.support:asynclayoutinflater:115bde87721f7334579b0c735f60dd7c98af1bb7f34010c5b0553b95dc351aa2',
+ 'android.arch.persistence.room:runtime:c21810eaafce370f1c9df1365393f55f962370a0d8b0b38b4771052c7021b737',
+ 'com.android.support:support-core-utils:c81e1e98ca3cb2edae002c69cf35b22aec364b8cb2f1042c97e206eb5790ac41',
+ 'com.android.support:loader:920b85efd72dc33e915b0f88a883fe73b88483c6df8751a741e17611f2460341',
+ 'com.android.support:support-vector-drawable:f658986d968172bccfed28578471c96050780fe5e133861e4d331069cc373f4d',
+ 'com.android.support:support-media-compat:266eff9605f515013eee1ebdbd8818a9270696dc807f34bbcc5fc11fb61a22c7',
+ 'com.android.support:support-compat:e17e3b01dbea3f9ea1c86943292f903ca93d2231c6242e456e0b6a9c5817118a',
+ 'com.android.support:versionedparcelable:60eb1cb08f71b65c3f6123135e03ebeb5930b5e126e1e5b2ac91b386908c9d02',
+ 'com.android.support:collections:93c258c8a09f531a267653829742c0f8f6da0e348b11cb8655b0855628f2d4f0',
+ 'android.arch.lifecycle:livedata:50ab0490c1ff1a7cfb4e554032998b080888946d0dd424f39900efc4a1bcd750',
+ 'android.arch.lifecycle:livedata-core:d6fdd8b985d6178d7ea2f16986a24e83f1bee936b74d43167c69e08d3cc12c50',
+ 'android.arch.lifecycle:runtime:c4e4be66c1b2f0abec593571454e1de14013f7e0f96bf2a9f212931a48cae550',
+ 'android.arch.lifecycle:common:8d378e88ebd5189e09eef623414812c868fd90aa519d6160e2311fb8b81cff56',
+ 'com.github.bumptech.glide:gifdecoder:59ccf3bb0cec11dab4b857382cbe0b171111b6fc62bf141adce4e1180889af15',
+ 'com.android.support:interpolator:7bc7ee86a0db39a4b51956f3e89842d2bd962118d57d779eb6ed6b34ba0677ea',
+ 'com.android.support:cursoradapter:87feffe742b8d62ca8a9833abe564838bf6a672e31c7ad1306ec4006adf90d21',
+ 'android.arch.persistence.room:common:7cf36bcd5f59ddc4876f887e36511bfd7b111f1eb717c0e9b6e2bcc710305ae6',
+ 'android.arch.persistence:db-framework:bd665448330acb90a6f551a87b0ba69169da2b8ec168b92f387997339cc14311',
+ 'android.arch.persistence:db:504e8c4307bfd53084924776ba3d49fed11b6f76d82dd80d5121c2d907fdfef6',
+ 'android.arch.core:runtime:c3215aa5873311b3f88a6f4e4a3c25ad89971bc127de8c3e1291c57f93a05c39',
+ 'android.arch.core:common:3a616a32f433e9e23f556b38575c31b013613d3ae85206263b7625fe1f4c151a',
+ 'android.arch.lifecycle:viewmodel:7de29cfaba77d6b5d5be234c57f6812d0150d087e63941af22ba1d1f8e2bc96a',
+ 'com.android.support:documentfile:47cdcd3e9302b7b064923f05487a5c03babbd9bbda4726b71e97791fab5d4779',
+ 'com.android.support:localbroadcastmanager:d287c823af5fdde72c099fcfc5f630efe9687af7a914343ae6fd92de32c8a806',
+ 'com.android.support:print:4be8a812d73e4a80e35b91ceae127def3f0bb9726bf3bc439aa0cc81503f5728',
+ 'com.android.support:support-annotations:5d5b9414f02d3fa0ee7526b8d5ddae0da67c8ecc8c4d63ffa6cf91488a93b927',
+ 'androidx.concurrent:futures:1f63078c41efd29d20ee3444fba93c6cdfaeeb862c6d3b6166ff8debd37d471a',
+ 'org.signal:signal-metadata-android:d9d798aab7ee7200373ecff8718baf8aaeb632c123604e8a41b7b4c0c97eeee1',
+ 'org.whispersystems:signal-service-java:092b0c23d914a5c6c04188fd06097e3351ad251d631e95cd0119ac60c85969d8',
+ 'com.github.bumptech.glide:disklrucache:c1b1b6f5bbd01e2fcdc9d7f60913c8d338bdb65ed4a93bfa02b56f19daaade4b',
+ 'com.github.bumptech.glide:annotations:bede99ef9f71517a4274bac18fd3e483e9f2b6108d7d6fe8f4949be4aa4d9512',
+ 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
+ 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
+ 'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541',
+ 'com.google.android:flexbox:a9989fd13ae2ee42765dfc515fe362edf4f326e74925d02a10369df8092a4935',
+ 'org.jsoup:jsoup:abeaf34795a4de70f72aed6de5966d2955ec7eb348eeb813324f23c999575473',
+ 'com.google.guava:listenablefuture:e4ad7607e5c0477c6f890ef26a49cb8d1bb4dffb650bab4502afee64644e3069',
+ 'androidx.annotation:annotation:04f22f257944ce223701d5aa1bdc36fb7f4594e87b539044045cd161d965468e',
+ 'org.whispersystems:signal-protocol-android:b41b7c3a47b0a4ce35802474ae7c5ddc8f033d80209e98abf7ec669543b2b3ee',
+ 'org.signal:signal-metadata-java:af1d0dd766b1e301ed1c44e65161084cf03e2587fe97fdd29ecbea58c6aa6930',
+ 'org.whispersystems:signal-protocol-java:b08207f7e1847228f2a1f0d49e113f93c96c6ed8490be14edddd4be55b2a4a4e',
+ 'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
+ 'com.googlecode.libphonenumber:libphonenumber:183392c0565be16d3f6f86680b4106bbde6fe31a402ad21bf9823d938c0c8706',
+ 'com.fasterxml.jackson.core:jackson-databind:0fb4e079c118e752cc94c15ad22e6782b0dfc5dc09145f4813fb39d82e686047',
+ 'com.squareup.okhttp3:okhttp:7265adbd6f028aade307f58569d814835cd02bc9beffb70c25f72c9de50d61c4',
+ 'com.madgag.spongycastle:pkix:0d9cca6991f68eb373cfad309d5268c9fc38db5efb5fe00dcccf5c973af1eca1',
+ 'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
+ 'org.threeten:threetenbp:f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7',
+ 'org.whispersystems:curve25519-android:82595394422b957d4a5b5f1b27b75ba25cf6dc4db4d312418ca38cd6fff279ca',
+ 'com.fasterxml.jackson.core:jackson-annotations:45d32ac61ef8a744b464c54c2b3414be571016dd46bfc2bec226761cf7ae457a',
+ 'com.fasterxml.jackson.core:jackson-core:a2bebaa325ad25455b02149c67e6052367a7d7fc1ce77de000eed284a5214eac',
+ 'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850',
+ 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
+ 'org.whispersystems:curve25519-java:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
+ ]
+}
+
android {
flavorDimensions "none"
compileSdkVersion 28
@@ -169,7 +290,7 @@ android {
project.ext.set("archivesBaseName", "Signal");
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
- buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
+ buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
@@ -178,7 +299,7 @@ android {
buildConfigField "String", "USER_AGENT", "\"OWA\""
buildConfigField "boolean", "DEV_BUILD", "false"
buildConfigField "String", "MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
- buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
+ buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86"
diff --git a/res/drawable-hdpi/ic_unidentified_delivery.png b/res/drawable-hdpi/ic_unidentified_delivery.png
new file mode 100644
index 000000000..348d322de
Binary files /dev/null and b/res/drawable-hdpi/ic_unidentified_delivery.png differ
diff --git a/res/drawable-mdpi/ic_unidentified_delivery.png b/res/drawable-mdpi/ic_unidentified_delivery.png
new file mode 100644
index 000000000..d1f48a19f
Binary files /dev/null and b/res/drawable-mdpi/ic_unidentified_delivery.png differ
diff --git a/res/drawable-xhdpi/ic_unidentified_delivery.png b/res/drawable-xhdpi/ic_unidentified_delivery.png
new file mode 100644
index 000000000..68127a40b
Binary files /dev/null and b/res/drawable-xhdpi/ic_unidentified_delivery.png differ
diff --git a/res/drawable-xxhdpi/ic_unidentified_delivery.png b/res/drawable-xxhdpi/ic_unidentified_delivery.png
new file mode 100644
index 000000000..76aa7807b
Binary files /dev/null and b/res/drawable-xxhdpi/ic_unidentified_delivery.png differ
diff --git a/res/drawable-xxxhdpi/ic_unidentified_delivery.png b/res/drawable-xxxhdpi/ic_unidentified_delivery.png
new file mode 100644
index 000000000..695d6f12c
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_unidentified_delivery.png differ
diff --git a/res/layout/message_details_header.xml b/res/layout/message_details_header.xml
index 8ae2dad37..286b70917 100644
--- a/res/layout/message_details_header.xml
+++ b/res/layout/message_details_header.xml
@@ -20,17 +20,39 @@
android:paddingLeft="10dp"
android:paddingRight="10dp">
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d9e13ca52..f2aa4acb3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -86,6 +86,9 @@
Remove
Remove profile photo?
+
+ No web browser found.
+
Your safety number with %1$s has changed. This could either mean that someone is trying to intercept your communication, or that %2$s simply reinstalled Signal.
You may wish to verify your safety number with this contact.
@@ -1204,6 +1207,12 @@
Message font size
Contact joined Signal
Priority
+ Sealed Sender
+ Display indicators
+ Show a status icon when you select "Message details" on messages that were delivered using sealed sender.
+ Allow from anyone
+ Enable sealed sender for incoming messages from non-contacts and people with whom you have not shared your profile.
+ Learn more
diff --git a/res/xml/preferences_app_protection.xml b/res/xml/preferences_app_protection.xml
index 466b4cac4..ce193d443 100644
--- a/res/xml/preferences_app_protection.xml
+++ b/res/xml/preferences_app_protection.xml
@@ -69,6 +69,27 @@
+
+
+
+
+
+
+
+
+
+
+
UPGRADE_VERSIONS = new TreeSet() {{
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
@@ -120,6 +120,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
add(IMAGE_CACHE_CLEANUP);
add(WORKMANAGER_MIGRATION);
add(COLOR_MIGRATION);
+ add(UNIDENTIFIED_DELIVERY);
}};
private MasterSecret masterSecret;
@@ -358,6 +359,18 @@ public class DatabaseUpgradeActivity extends BaseActivity {
Log.i(TAG, "Color migration took " + (System.currentTimeMillis() - startTime) + " ms");
}
+ if (params[0] < UNIDENTIFIED_DELIVERY) {
+ if (TextSecurePreferences.isMultiDevice(context)) {
+ Log.i(TAG, "MultiDevice: Disabling UD (will be re-enabled if possible after pending refresh).");
+ TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
+ }
+
+ Log.i(TAG, "Scheduling UD attributes refresh.");
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new RefreshAttributesJob(context));
+ }
+
return null;
}
diff --git a/src/org/thoughtcrime/securesms/DeviceActivity.java b/src/org/thoughtcrime/securesms/DeviceActivity.java
index 5475a5595..479fb8e05 100644
--- a/src/org/thoughtcrime/securesms/DeviceActivity.java
+++ b/src/org/thoughtcrime/securesms/DeviceActivity.java
@@ -11,6 +11,7 @@ import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.transition.TransitionInflater;
+
import org.thoughtcrime.securesms.logging.Log;
import android.view.MenuItem;
import android.view.View;
@@ -179,6 +180,7 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
Optional profileKey = Optional.of(ProfileKeyUtil.getProfileKey(getContext()));
TextSecurePreferences.setMultiDevice(DeviceActivity.this, true);
+ TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, profileKey, verificationCode);
return SUCCESS;
diff --git a/src/org/thoughtcrime/securesms/DeviceListFragment.java b/src/org/thoughtcrime/securesms/DeviceListFragment.java
index 044b8947f..b94a13959 100644
--- a/src/org/thoughtcrime/securesms/DeviceListFragment.java
+++ b/src/org/thoughtcrime/securesms/DeviceListFragment.java
@@ -9,6 +9,8 @@ import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AlertDialog;
+
+import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.logging.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -172,6 +174,10 @@ public class DeviceListFragment extends ListFragment
protected Void doInBackground(Void... params) {
try {
accountManager.removeDevice(deviceId);
+
+ ApplicationContext.getInstance(getContext())
+ .getJobManager()
+ .add(new RefreshUnidentifiedDeliveryAbilityJob(getContext()));
} catch (IOException e) {
Log.w(TAG, e);
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
index 20cc4eeb5..df6f5b469 100644
--- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
+++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
@@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
+import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -87,6 +88,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
private View metadataContainer;
private View expiresContainer;
private TextView errorText;
+ private View resendButton;
private TextView sentDate;
private TextView receivedDate;
private TextView expiresInText;
@@ -176,6 +178,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
recipientsList = findViewById(R.id.recipients_list);
metadataContainer = header.findViewById(R.id.metadata_container);
errorText = header.findViewById(R.id.error_text);
+ resendButton = header.findViewById(R.id.resend_button);
sentDate = header.findViewById(R.id.sent_time);
receivedContainer = header.findViewById(R.id.received_container);
receivedDate = header.findViewById(R.id.received_time);
@@ -362,7 +365,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
List recipients = new LinkedList<>();
if (!messageRecord.getRecipient().isGroupRecipient()) {
- recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), -1));
+ recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), messageRecord.isUnidentified(), -1));
} else {
List receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId());
@@ -370,12 +373,13 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
List group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().getAddress().toGroupString(), false);
for (Recipient recipient : group) {
- recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, -1));
+ recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1));
}
} else {
for (GroupReceiptInfo info : receiptInfoList) {
recipients.add(new RecipientDeliveryStatus(Recipient.from(context, info.getAddress(), true),
- getStatusFor(info.getStatus(), messageRecord.isPending()),
+ getStatusFor(info.getStatus(), messageRecord.isPending(), messageRecord.isFailed()),
+ info.isUnidentified(),
info.getTimestamp()));
}
}
@@ -392,16 +396,28 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
}
inflateMessageViewIfAbsent(messageRecord);
-
updateRecipients(messageRecord, messageRecord.getRecipient(), recipients);
- if (messageRecord.isFailed()) {
+
+ boolean isGroupNetworkFailure = messageRecord.isFailed() && !messageRecord.getNetworkFailures().isEmpty();
+ boolean isIndividualNetworkFailure = messageRecord.isFailed() && !isPushGroup && messageRecord.getIdentityKeyMismatches().isEmpty();
+
+ if (isGroupNetworkFailure || isIndividualNetworkFailure) {
errorText.setVisibility(View.VISIBLE);
+ resendButton.setVisibility(View.VISIBLE);
+ resendButton.setOnClickListener(this::onResendClicked);
+ metadataContainer.setVisibility(View.GONE);
+ } else if (messageRecord.isFailed()) {
+ errorText.setVisibility(View.VISIBLE);
+ resendButton.setVisibility(View.GONE);
+ resendButton.setOnClickListener(null);
metadataContainer.setVisibility(View.GONE);
} else {
updateTransport(messageRecord);
updateTime(messageRecord);
updateExpirationTime(messageRecord);
errorText.setVisibility(View.GONE);
+ resendButton.setVisibility(View.GONE);
+ resendButton.setOnClickListener(null);
metadataContainer.setVisibility(View.VISIBLE);
}
}
@@ -413,14 +429,19 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
else return RecipientDeliveryStatus.Status.PENDING;
}
- private RecipientDeliveryStatus.Status getStatusFor(int groupStatus, boolean pending) {
+ private RecipientDeliveryStatus.Status getStatusFor(int groupStatus, boolean pending, boolean failed) {
if (groupStatus == GroupReceiptDatabase.STATUS_READ) return RecipientDeliveryStatus.Status.READ;
else if (groupStatus == GroupReceiptDatabase.STATUS_DELIVERED) return RecipientDeliveryStatus.Status.DELIVERED;
+ else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && failed) return RecipientDeliveryStatus.Status.UNKNOWN;
else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && !pending) return RecipientDeliveryStatus.Status.SENT;
else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED) return RecipientDeliveryStatus.Status.PENDING;
else if (groupStatus == GroupReceiptDatabase.STATUS_UNKNOWN) return RecipientDeliveryStatus.Status.UNKNOWN;
throw new AssertionError();
}
+ private void onResendClicked(View v) {
+ MessageSender.resend(MessageDetailsActivity.this, messageRecord);
+ resendButton.setVisibility(View.GONE);
+ }
}
}
diff --git a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java
index bb1ddfec1..cca89cfec 100644
--- a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java
+++ b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java
@@ -81,11 +81,13 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
private final Recipient recipient;
private final Status deliveryStatus;
+ private final boolean isUnidentified;
private final long timestamp;
- RecipientDeliveryStatus(Recipient recipient, Status deliveryStatus, long timestamp) {
+ RecipientDeliveryStatus(Recipient recipient, Status deliveryStatus, boolean isUnidentified, long timestamp) {
this.recipient = recipient;
this.deliveryStatus = deliveryStatus;
+ this.isUnidentified = isUnidentified;
this.timestamp = timestamp;
}
@@ -93,6 +95,10 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
return deliveryStatus;
}
+ boolean isUnidentified() {
+ return isUnidentified;
+ }
+
public long getTimestamp() {
return timestamp;
}
diff --git a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java
index 6ca94abbe..8b5bc8b83 100644
--- a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java
+++ b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java
@@ -16,13 +16,12 @@
*/
package org.thoughtcrime.securesms;
-import android.annotation.SuppressLint;
import android.content.Context;
-import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
+import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -30,15 +29,13 @@ import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDelive
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.DeliveryStatusView;
import org.thoughtcrime.securesms.components.FromTextView;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
-import org.thoughtcrime.securesms.sms.MessageSender;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
/**
@@ -58,8 +55,8 @@ public class MessageRecipientListItem extends RelativeLayout
private TextView errorDescription;
private TextView actionDescription;
private Button conflictButton;
- private Button resendButton;
private AvatarImageView contactPhotoImage;
+ private ImageView unidentifiedDeliveryIcon;
private DeliveryStatusView deliveryStatusView;
public MessageRecipientListItem(Context context) {
@@ -73,13 +70,13 @@ public class MessageRecipientListItem extends RelativeLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- this.fromView = findViewById(R.id.from);
- this.errorDescription = findViewById(R.id.error_description);
- this.actionDescription = findViewById(R.id.action_description);
- this.contactPhotoImage = findViewById(R.id.contact_photo_image);
- this.conflictButton = findViewById(R.id.conflict_button);
- this.resendButton = findViewById(R.id.resend_button);
- this.deliveryStatusView = findViewById(R.id.delivery_status);
+ this.fromView = findViewById(R.id.from);
+ this.errorDescription = findViewById(R.id.error_description);
+ this.actionDescription = findViewById(R.id.action_description);
+ this.contactPhotoImage = findViewById(R.id.contact_photo_image);
+ this.conflictButton = findViewById(R.id.conflict_button);
+ this.unidentifiedDeliveryIcon = findViewById(R.id.ud_indicator);
+ this.deliveryStatusView = findViewById(R.id.delivery_status);
}
public void set(final GlideRequests glideRequests,
@@ -94,6 +91,7 @@ public class MessageRecipientListItem extends RelativeLayout
fromView.setText(member.getRecipient());
contactPhotoImage.setAvatar(glideRequests, member.getRecipient(), false);
setIssueIndicators(record, isPushGroup);
+ unidentifiedDeliveryIcon.setVisibility(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()) && member.isUnidentified() ? VISIBLE : GONE);
}
private void setIssueIndicators(final MessageRecord record,
@@ -105,25 +103,13 @@ public class MessageRecipientListItem extends RelativeLayout
String errorText = "";
if (keyMismatch != null) {
- resendButton.setVisibility(View.GONE);
conflictButton.setVisibility(View.VISIBLE);
errorText = getContext().getString(R.string.MessageDetailsRecipient_new_safety_number);
conflictButton.setOnClickListener(v -> new ConfirmIdentityDialog(getContext(), record, keyMismatch).show());
- } else if (networkFailure != null || (!isPushGroup && record.isFailed())) {
- resendButton.setVisibility(View.VISIBLE);
- resendButton.setEnabled(true);
- resendButton.requestFocus();
+ } else if ((networkFailure != null && !record.isPending()) || (!isPushGroup && record.isFailed())) {
conflictButton.setVisibility(View.GONE);
-
errorText = getContext().getString(R.string.MessageDetailsRecipient_failed_to_send);
- resendButton.setOnClickListener(v -> {
- resendButton.setVisibility(View.GONE);
- errorDescription.setVisibility(View.GONE);
- actionDescription.setVisibility(View.VISIBLE);
- actionDescription.setText(R.string.message_recipients_list_item__resending);
- new ResendAsyncTask(record, networkFailure).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- });
} else {
if (record.isOutgoing()) {
if (member.getDeliveryStatus() == RecipientDeliveryStatus.Status.PENDING || member.getDeliveryStatus() == RecipientDeliveryStatus.Status.UNKNOWN) {
@@ -142,7 +128,6 @@ public class MessageRecipientListItem extends RelativeLayout
deliveryStatusView.setVisibility(View.GONE);
}
- resendButton.setVisibility(View.GONE);
conflictButton.setVisibility(View.GONE);
}
@@ -183,31 +168,4 @@ public class MessageRecipientListItem extends RelativeLayout
contactPhotoImage.setAvatar(glideRequests, recipient, false);
});
}
-
- @SuppressLint("StaticFieldLeak")
- private class ResendAsyncTask extends AsyncTask {
- private final Context context;
- private final MessageRecord record;
- private final NetworkFailure failure;
-
- ResendAsyncTask(MessageRecord record, NetworkFailure failure) {
- this.context = getContext().getApplicationContext();
- this.record = record;
- this.failure = failure;
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
- mmsDatabase.removeFailure(record.getId(), failure);
-
- if (record.getRecipient().isPushGroupRecipient()) {
- MessageSender.resendGroupMessage(context, record, failure.getAddress());
- } else {
- MessageSender.resend(context, record);
- }
- return null;
- }
- }
-
}
diff --git a/src/org/thoughtcrime/securesms/ReadReceiptsIntroFragment.java b/src/org/thoughtcrime/securesms/ReadReceiptsIntroFragment.java
index cc2f4b8ea..2a6828436 100644
--- a/src/org/thoughtcrime/securesms/ReadReceiptsIntroFragment.java
+++ b/src/org/thoughtcrime/securesms/ReadReceiptsIntroFragment.java
@@ -7,9 +7,8 @@ import android.support.v7.widget.SwitchCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
-import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob;
+import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -39,7 +38,9 @@ public class ReadReceiptsIntroFragment extends Fragment {
TextSecurePreferences.setReadReceiptsEnabled(getContext(), isChecked);
ApplicationContext.getInstance(getContext())
.getJobManager()
- .add(new MultiDeviceReadReceiptUpdateJob(getContext(), isChecked));
+ .add(new MultiDeviceConfigurationUpdateJob(getContext(),
+ isChecked,
+ TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext())));
});
return v;
diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
index 7d821777a..df5010dee 100644
--- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
+++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
@@ -30,6 +30,7 @@ import android.telephony.PhoneNumberUtils;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.database.GroupDatabase;
+import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair;
import android.view.MenuItem;
@@ -740,6 +741,12 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
}
}
+ if (blocked && (recipient.resolve().isSystemContact() || recipient.resolve().isProfileSharing())) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new RotateProfileKeyJob(context));
+ }
+
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceBlockedUpdateJob(context));
diff --git a/src/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java
index 0b10de6b5..319d2b6a8 100644
--- a/src/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java
+++ b/src/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java
@@ -31,4 +31,8 @@ public class ProfileKeyUtil {
}
}
+ public static synchronized @NonNull byte[] rotateProfileKey(@NonNull Context context) {
+ TextSecurePreferences.setProfileKey(context, null);
+ return getProfileKey(context);
+ }
}
diff --git a/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java b/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java
index 4de44cd02..407fc7475 100644
--- a/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java
+++ b/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java
@@ -10,6 +10,7 @@ import android.util.Log;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
import org.thoughtcrime.securesms.BuildConfig;
+import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -40,6 +41,11 @@ public class UnidentifiedAccessUtil {
public static Optional getAccessFor(@NonNull Context context,
@NonNull Recipient recipient)
{
+ if (!TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
+ Log.i(TAG, "Unidentified delivery is disabled. [other]");
+ return Optional.absent();
+ }
+
try {
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
@@ -49,9 +55,9 @@ public class UnidentifiedAccessUtil {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
- Log.w(TAG, "Their access key: " + (theirUnidentifiedAccessKey == null));
- Log.w(TAG, "Our access key: " + (ourUnidentifiedAccessKey == null));
- Log.w(TAG, "Our certificatE: " + (ourUnidentifiedAccessCertificate == null));
+ Log.i(TAG, "Their access key present? " + (theirUnidentifiedAccessKey != null));
+ Log.i(TAG, "Our access key present? " + (ourUnidentifiedAccessKey != null));
+ Log.i(TAG, "Our certificate present? " + (ourUnidentifiedAccessCertificate != null));
if (theirUnidentifiedAccessKey != null &&
ourUnidentifiedAccessKey != null &&
@@ -71,6 +77,11 @@ public class UnidentifiedAccessUtil {
}
public static Optional getAccessForSync(@NonNull Context context) {
+ if (!TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
+ Log.i(TAG, "Unidentified delivery is disabled. [self]");
+ return Optional.absent();
+ }
+
try {
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context);
diff --git a/src/org/thoughtcrime/securesms/database/MessagingDatabase.java b/src/org/thoughtcrime/securesms/database/MessagingDatabase.java
index 660c5584c..349ad4825 100644
--- a/src/org/thoughtcrime/securesms/database/MessagingDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MessagingDatabase.java
@@ -34,6 +34,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
public abstract void markExpireStarted(long messageId, long startTime);
public abstract void markAsSent(long messageId, boolean secure);
+ public abstract void markUnidentified(long messageId, boolean unidentified);
public void setMismatchedIdentity(long messageId, final Address address, final IdentityKey identityKey) {
List items = new ArrayList() {{
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index 73097c937..a43ebb670 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -343,11 +343,11 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
-// public void markAsSending(long messageId) {
-// long threadId = getThreadIdForMessage(messageId);
-// updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE, Optional.of(threadId));
-// notifyConversationListeners(threadId);
-// }
+ public void markAsSending(long messageId) {
+ long threadId = getThreadIdForMessage(messageId);
+ updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE, Optional.of(threadId));
+ notifyConversationListeners(threadId);
+ }
public void markAsSentFailed(long messageId) {
long threadId = getThreadIdForMessage(messageId);
@@ -403,6 +403,7 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
+ @Override
public void markUnidentified(long messageId, boolean unidentified) {
ContentValues contentValues = new ContentValues();
contentValues.put(UNIDENTIFIED, unidentified ? 1 : 0);
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index 4725659c0..7f72a0551 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -270,8 +270,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.QUOTE_BODY,
MmsDatabase.QUOTE_MISSING,
MmsDatabase.QUOTE_ATTACHMENT,
- MmsDatabase.SHARED_CONTACTS,
- MmsDatabase.UNIDENTIFIED};
+ MmsDatabase.SHARED_CONTACTS};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
index ee7dcd3cf..0abe0a50f 100644
--- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -239,10 +239,15 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_TYPE | (isSecure ? Types.PUSH_MESSAGE_BIT | Types.SECURE_MESSAGE_BIT : 0));
}
+ public void markAsSending(long id) {
+ updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
+ }
+
public void markAsMissedCall(long id) {
updateTypeBitmask(id, Types.TOTAL_MASK, Types.MISSED_CALL_TYPE);
}
+ @Override
public void markUnidentified(long id, boolean unidentified) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(UNIDENTIFIED, unidentified ? 1 : 0);
@@ -695,7 +700,9 @@ public class SmsDatabase extends MessagingDatabase {
public Cursor getMessageCursor(long messageId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
- return db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
+ Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
+ setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
+ return cursor;
}
public boolean deleteMessage(long messageId) {
diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
index 5da18fd0e..7acb168c1 100644
--- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
+++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
@@ -289,6 +289,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN unidentified_access_mode INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE push ADD COLUMN server_timestamp INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE push ADD COLUMN server_guid TEXT DEFAULT NULL");
+ db.execSQL("ALTER TABLE group_receipts ADD COLUMN unidentified INTEGER DEFAULT 0");
+ db.execSQL("ALTER TABLE mms ADD COLUMN unidentified INTEGER DEFAULT 0");
+ db.execSQL("ALTER TABLE sms ADD COLUMN unidentified INTEGER DEFAULT 0");
}
db.setTransactionSuccessful();
diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
index bfe448f37..ae542fed2 100644
--- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
@@ -3,6 +3,9 @@ package org.thoughtcrime.securesms.dependencies;
import android.content.Context;
import org.thoughtcrime.securesms.gcm.GcmBroadcastReceiver;
+import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
+import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
+import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
import org.thoughtcrime.securesms.logging.Log;
import org.greenrobot.eventbus.EventBus;
@@ -87,7 +90,10 @@ import dagger.Provides;
AppProtectionPreferenceFragment.class,
GcmBroadcastReceiver.class,
RotateCertificateJob.class,
- SendDeliveryReceiptJob.class})
+ SendDeliveryReceiptJob.class,
+ RotateProfileKeyJob.class,
+ MultiDeviceConfigurationUpdateJob.class,
+ RefreshUnidentifiedDeliveryAbilityJob.class})
public class SignalCommunicationModule {
private static final String TAG = SignalCommunicationModule.class.getSimpleName();
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java
index 11cda46ec..e641c053c 100644
--- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
+import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
@@ -13,6 +14,8 @@ import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.GroupUtil;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
@@ -61,6 +64,11 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje
public void onRun(MasterSecret masterSecret)
throws IOException, UntrustedIdentityException
{
+ if (!TextSecurePreferences.isMultiDevice(context)) {
+ Log.i(TAG, "Not multi device, aborting...");
+ return;
+ }
+
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
try (RecipientReader reader = database.readerForBlocked(database.getBlocked())) {
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceConfigurationUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceConfigurationUpdateJob.java
new file mode 100644
index 000000000..3f53f31e5
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceConfigurationUpdateJob.java
@@ -0,0 +1,87 @@
+package org.thoughtcrime.securesms.jobs;
+
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.jobmanager.JobParameters;
+import org.thoughtcrime.securesms.jobmanager.SafeData;
+import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.SignalServiceMessageSender;
+import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
+import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
+import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
+import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import androidx.work.Data;
+
+public class MultiDeviceConfigurationUpdateJob extends ContextJob implements InjectableType {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String TAG = MultiDeviceConfigurationUpdateJob.class.getSimpleName();
+
+ private static final String KEY_READ_RECEIPTS_ENABLED = "read_receipts_enabled";
+ private static final String KEY_UNIDENTIFIED_DELIVERY_INDICATORS_ENABLED = "unidentified_delivery_indicators_enabled";
+
+ @Inject transient SignalServiceMessageSender messageSender;
+
+ private boolean readReceiptsEnabled;
+ private boolean unidentifiedDeliveryIndicatorsEnabled;
+
+ public MultiDeviceConfigurationUpdateJob() {
+ super(null, null);
+ }
+
+ public MultiDeviceConfigurationUpdateJob(Context context, boolean readReceiptsEnabled, boolean unidentifiedDeliveryIndicatorsEnabled) {
+ super(context, JobParameters.newBuilder()
+ .withGroupId("__MULTI_DEVICE_CONFIGURATION_UPDATE_JOB__")
+ .withNetworkRequirement()
+ .create());
+
+ this.readReceiptsEnabled = readReceiptsEnabled;
+ this.unidentifiedDeliveryIndicatorsEnabled = unidentifiedDeliveryIndicatorsEnabled;
+ }
+
+ @Override
+ protected void initialize(@NonNull SafeData data) {
+ readReceiptsEnabled = data.getBoolean(KEY_READ_RECEIPTS_ENABLED);
+ unidentifiedDeliveryIndicatorsEnabled = data.getBoolean(KEY_UNIDENTIFIED_DELIVERY_INDICATORS_ENABLED);
+ }
+
+ @Override
+ protected @NonNull Data serialize(@NonNull Data.Builder dataBuilder) {
+ return dataBuilder.putBoolean(KEY_READ_RECEIPTS_ENABLED, readReceiptsEnabled)
+ .putBoolean(KEY_UNIDENTIFIED_DELIVERY_INDICATORS_ENABLED, unidentifiedDeliveryIndicatorsEnabled)
+ .build();
+ }
+
+ @Override
+ public void onRun() throws IOException, UntrustedIdentityException {
+ if (!TextSecurePreferences.isMultiDevice(context)) {
+ Log.i(TAG, "Not multi device, aborting...");
+ return;
+ }
+
+ messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(readReceiptsEnabled), Optional.of(unidentifiedDeliveryIndicatorsEnabled))),
+ UnidentifiedAccessUtil.getAccessForSync(context));
+ }
+
+ @Override
+ public boolean onShouldRetry(Exception e) {
+ return e instanceof PushNetworkException;
+ }
+
+ @Override
+ public void onCanceled() {
+ Log.w(TAG, "**** Failed to synchronize read receipts state!");
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
index 0f5735375..661f091f2 100644
--- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java
@@ -116,7 +116,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
throws IOException, UntrustedIdentityException, NetworkException
{
if (!TextSecurePreferences.isMultiDevice(context)) {
- Log.w(TAG, "Not multi device, aborting...");
+ Log.i(TAG, "Not multi device, aborting...");
return;
}
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java
index 32442e289..958c8903d 100644
--- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -67,6 +68,11 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
@Override
public void onRun(MasterSecret masterSecret) throws Exception {
+ if (!TextSecurePreferences.isMultiDevice(context)) {
+ Log.i(TAG, "Not multi device, aborting...");
+ return;
+ }
+
File contactDataFile = createTempFile("multidevice-contact-update");
GroupDatabase.Reader reader = null;
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java
index 39873b6ae..d0b104f85 100644
--- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java
@@ -62,7 +62,7 @@ public class MultiDeviceProfileKeyUpdateJob extends MasterSecretJob implements I
@Override
public void onRun(MasterSecret masterSecret) throws IOException, UntrustedIdentityException {
if (!TextSecurePreferences.isMultiDevice(getContext())) {
- Log.w(TAG, "Not multi device...");
+ Log.i(TAG, "Not multi device...");
return;
}
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadReceiptUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadReceiptUpdateJob.java
index cb0cddb2e..5a3f372ec 100644
--- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadReceiptUpdateJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadReceiptUpdateJob.java
@@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -22,6 +23,10 @@ import javax.inject.Inject;
import androidx.work.Data;
+/**
+ * Use {@link MultiDeviceConfigurationUpdateJob}.
+ */
+@Deprecated
public class MultiDeviceReadReceiptUpdateJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 1L;
@@ -59,7 +64,12 @@ public class MultiDeviceReadReceiptUpdateJob extends ContextJob implements Injec
@Override
public void onRun() throws IOException, UntrustedIdentityException {
- messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(enabled))),
+ if (!TextSecurePreferences.isMultiDevice(context)) {
+ Log.i(TAG, "Not multi device, aborting...");
+ return;
+ }
+
+ messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(enabled), Optional.absent())),
UnidentifiedAccessUtil.getAccessForSync(context));
}
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java
index a54fa393a..5bffe4ae9 100644
--- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
@@ -91,7 +92,7 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta
@Override
public void onRun(MasterSecret masterSecret) throws IOException, UntrustedIdentityException {
if (!TextSecurePreferences.isMultiDevice(context)) {
- Log.w(TAG, "Not multi device...");
+ Log.i(TAG, "Not multi device...");
return;
}
@@ -101,8 +102,7 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta
readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp));
}
- messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages),
- UnidentifiedAccessUtil.getAccessForSync(context));
+ messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages), UnidentifiedAccessUtil.getAccessForSync(context));
}
@Override
diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java
index e14b76e3c..c7a9f873f 100644
--- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java
@@ -91,7 +91,7 @@ public class MultiDeviceVerifiedUpdateJob extends ContextJob implements Injectab
public void onRun() throws IOException, UntrustedIdentityException {
try {
if (!TextSecurePreferences.isMultiDevice(context)) {
- Log.w(TAG, "Not multi device...");
+ Log.i(TAG, "Not multi device...");
return;
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 30ff8a290..0fcf6175d 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -20,6 +20,7 @@ import org.signal.libsignal.metadata.ProtocolInvalidVersionException;
import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
import org.signal.libsignal.metadata.ProtocolNoSessionException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
+import org.signal.libsignal.metadata.SelfSendException;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
@@ -36,6 +37,7 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
+import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
@@ -242,6 +244,8 @@ public class PushDecryptJob extends ContextJob {
handleNeedsDeliveryReceipt(content, message);
}
} else if (content.getSyncMessage().isPresent()) {
+ TextSecurePreferences.setMultiDevice(context, true);
+
SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get());
@@ -273,7 +277,11 @@ public class PushDecryptJob extends ContextJob {
} catch (ProtocolInvalidVersionException e) {
Log.w(TAG, e);
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
- } catch (ProtocolInvalidMessageException | ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
+ } catch (ProtocolInvalidMessageException e) {
+ Log.w(TAG, e);
+ handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
+ }
+ catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
Log.w(TAG, e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (StorageFailedException e) {
@@ -290,6 +298,8 @@ public class PushDecryptJob extends ContextJob {
handleDuplicateMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (InvalidMetadataVersionException | InvalidMetadataMessageException e) {
Log.w(TAG, e);
+ } catch (SelfSendException e) {
+ Log.i(TAG, "Dropping UD message from self.");
}
}
@@ -542,6 +552,10 @@ public class PushDecryptJob extends ContextJob {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceContactUpdateJob(getContext(), true));
+
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new RefreshUnidentifiedDeliveryAbilityJob(context));
}
if (message.isGroupsRequest()) {
@@ -559,7 +573,9 @@ public class PushDecryptJob extends ContextJob {
if (message.isConfigurationRequest()) {
ApplicationContext.getInstance(context)
.getJobManager()
- .add(new MultiDeviceReadReceiptUpdateJob(getContext(), TextSecurePreferences.isReadReceiptsEnabled(getContext())));
+ .add(new MultiDeviceConfigurationUpdateJob(getContext(),
+ TextSecurePreferences.isReadReceiptsEnabled(getContext()),
+ TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext())));
}
}
@@ -670,7 +686,17 @@ public class PushDecryptJob extends ContextJob {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, null);
+ if (recipients.getAddress().isGroup()) {
+ GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
+ List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipients.getAddress().toGroupString(), false);
+
+ for (Recipient member : members) {
+ receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize()));
+ }
+ }
+
database.markAsSent(messageId, true);
+ database.markUnidentified(messageId, message.isUnidentified(recipients.getAddress().serialize()));
for (DatabaseAttachment attachment : DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId)) {
ApplicationContext.getInstance(context)
@@ -753,11 +779,19 @@ public class PushDecryptJob extends ContextJob {
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null);
database = DatabaseFactory.getMmsDatabase(context);
+
+ GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
+ List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), false);
+
+ for (Recipient member : members) {
+ receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize()));
+ }
} else {
OutgoingTextMessage outgoingTextMessage = new OutgoingEncryptedMessage(recipient, body, expiresInMillis);
messageId = DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoingTextMessage, false, message.getTimestamp(), null);
database = DatabaseFactory.getSmsDatabase(context);
+ database.markUnidentified(messageId, message.isUnidentified(recipient.getAddress().serialize()));
}
database.markAsSent(messageId, true);
diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
index e6d94415c..3d6723777 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
@@ -45,6 +45,7 @@ import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -99,6 +100,11 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.build();
}
+ @Override
+ protected void onAdded() {
+ DatabaseFactory.getMmsDatabase(context).markAsSending(messageId);
+ }
+
@Override
public void onPushSend()
throws IOException, MmsException, NoSuchMessageException, RetryLaterException
@@ -110,6 +116,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
try {
Log.i(TAG, "Sending message: " + messageId);
+
List target;
if (filterAddress != null) target = Collections.singletonList(Address.fromSerialized(filterAddress));
diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
index f237a801c..31fb26deb 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
@@ -10,16 +10,19 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
+import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
+import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -68,6 +71,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
return dataBuilder.putLong(KEY_MESSAGE_ID, messageId).build();
}
+ @Override
+ protected void onAdded() {
+ DatabaseFactory.getMmsDatabase(context).markAsSending(messageId);
+ }
+
@Override
public void onPushSend()
throws RetryLaterException, MmsException, NoSuchMessageException,
@@ -85,6 +93,19 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
markAttachmentsUploaded(messageId, message.getAttachments());
database.markUnidentified(messageId, unidentified);
+ if (TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
+ Recipient recipient = message.getRecipient().resolve();
+ UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
+
+ if (unidentified && (accessMode == UnidentifiedAccessMode.UNKNOWN || accessMode == UnidentifiedAccessMode.DISABLED)) {
+ Log.i(TAG, "Marking recipient as UD-enabled following a UD send.");
+ DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
+ } else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
+ Log.i(TAG, "Marking recipient as UD-disabled following a non-UD send.");
+ DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
+ }
+ }
+
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
database.markExpireStarted(messageId);
expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn());
diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
index 054447946..df84f1157 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
+import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
@@ -19,11 +20,11 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
-import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
@@ -68,6 +69,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
@Override
public void onAdded() {
Log.i(TAG, "onAdded() messageId: " + messageId);
+ DatabaseFactory.getSmsDatabase(context).markAsSending(messageId);
}
@Override
@@ -83,6 +85,19 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
database.markAsSent(messageId, true);
database.markUnidentified(messageId, unidentified);
+ if (TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
+ Recipient recipient = record.getRecipient().resolve();
+ UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
+
+ if (unidentified && (accessMode == UnidentifiedAccessMode.UNKNOWN || accessMode == UnidentifiedAccessMode.DISABLED)) {
+ Log.i(TAG, "Marking recipient as UD-enabled following a UD send.");
+ DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
+ } else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
+ Log.i(TAG, "Marking recipient as UD-disabled following a non-UD send.");
+ DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
+ }
+ }
+
if (record.getExpiresIn() > 0) {
database.markExpireStarted(messageId);
expirationManager.scheduleDeletion(record.getId(), record.isMms(), record.getExpiresIn());
diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java
index c5a41fa82..bae0adb20 100644
--- a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
@@ -10,10 +11,6 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
-import org.thoughtcrime.securesms.dependencies.InjectableType;
-import org.thoughtcrime.securesms.jobmanager.JobParameters;
-import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
-import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
@@ -63,6 +60,10 @@ public class RefreshAttributesJob extends ContextJob implements InjectableType {
signalAccountManager.setAccountAttributes(signalingKey, registrationId, fetchesMessages, pin,
unidentifiedAccessKey, universalUnidentifiedAccess);
+
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new RefreshUnidentifiedDeliveryAbilityJob(context));
}
@Override
diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshUnidentifiedDeliveryAbilityJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshUnidentifiedDeliveryAbilityJob.java
new file mode 100644
index 000000000..f7440c508
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/RefreshUnidentifiedDeliveryAbilityJob.java
@@ -0,0 +1,96 @@
+package org.thoughtcrime.securesms.jobs;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.jobmanager.JobParameters;
+import org.thoughtcrime.securesms.jobmanager.SafeData;
+import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.service.IncomingMessageObserver;
+import org.thoughtcrime.securesms.util.Base64;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
+import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
+import org.whispersystems.signalservice.api.crypto.ProfileCipher;
+import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import androidx.work.Data;
+
+public class RefreshUnidentifiedDeliveryAbilityJob extends ContextJob implements InjectableType {
+
+ private static final String TAG = RefreshUnidentifiedDeliveryAbilityJob.class.getSimpleName();
+
+ @Inject transient SignalServiceMessageReceiver receiver;
+
+ public RefreshUnidentifiedDeliveryAbilityJob() {
+ super(null, null);
+ }
+
+ public RefreshUnidentifiedDeliveryAbilityJob(Context context) {
+ super(context, new JobParameters.Builder()
+ .withNetworkRequirement()
+ .create());
+ }
+
+ @Override
+ protected void initialize(@NonNull SafeData data) { }
+
+ @Override
+ protected @NonNull Data serialize(@NonNull Data.Builder dataBuilder) {
+ return dataBuilder.build();
+ }
+
+ @Override
+ public void onRun() throws Exception {
+ byte[] profileKey = ProfileKeyUtil.getProfileKey(context);
+ SignalServiceProfile profile = retrieveProfile(TextSecurePreferences.getLocalNumber(context));
+
+ boolean enabled = profile.getUnidentifiedAccess() != null && isValidVerifier(profileKey, profile.getUnidentifiedAccess());
+
+ TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, enabled);
+ Log.i(TAG, "Set UD status to: " + enabled);
+ }
+
+ @Override
+ protected void onCanceled() {
+
+ }
+
+ @Override
+ protected boolean onShouldRetry(Exception exception) {
+ return exception instanceof PushNetworkException;
+ }
+
+ private SignalServiceProfile retrieveProfile(@NonNull String number) throws IOException {
+ SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe();
+
+ if (pipe != null) {
+ try {
+ return pipe.getProfile(new SignalServiceAddress(number), Optional.absent());
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ return receiver.retrieveProfile(new SignalServiceAddress(number), Optional.absent());
+ }
+
+ private boolean isValidVerifier(@NonNull byte[] profileKey, @NonNull String verifier) {
+ ProfileCipher profileCipher = new ProfileCipher(profileKey);
+ try {
+ return profileCipher.verifyUnidentifiedAccess(Base64.decode(verifier));
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return false;
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java
index e79a12aa9..eb416a6dd 100644
--- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java
@@ -30,7 +30,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
+import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException;
@@ -101,9 +101,8 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
try {
profile = retrieveProfile(number, unidentifiedAccess);
- } catch (AuthorizationFailedException e) {
+ } catch (NonSuccessfulResponseCodeException e) {
if (unidentifiedAccess.isPresent()) {
- // XXX Update UI
profile = retrieveProfile(number, Optional.absent());
} else {
throw e;
@@ -129,7 +128,10 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
private SignalServiceProfile retrieveProfile(@NonNull String number, Optional unidentifiedAccess)
throws IOException
{
- SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe();
+ SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
+ SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
+ SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe
+ : authPipe;
if (pipe != null) {
try {
@@ -169,10 +171,11 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
byte[] profileKey = recipient.getProfileKey();
- // XXX Update UI
if (unrestrictedUnidentifiedAccess) {
+ Log.i(TAG, "Marking recipient UD status as unrestricted.");
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
+ Log.i(TAG, "Marking recipient UD status as disabled.");
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
} else {
ProfileCipher profileCipher = new ProfileCipher(profileKey);
@@ -185,7 +188,9 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
verifiedUnidentifiedAccess = false;
}
- recipientDatabase.setUnidentifiedAccessMode(recipient, verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED);
+ UnidentifiedAccessMode mode = verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED;
+ Log.i(TAG, "Marking recipient UD status as " + mode.name() + " after verification.");
+ recipientDatabase.setUnidentifiedAccessMode(recipient, mode);
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java b/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java
new file mode 100644
index 000000000..d6f4b0013
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java
@@ -0,0 +1,88 @@
+package org.thoughtcrime.securesms.jobs;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
+import org.thoughtcrime.securesms.database.Address;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.jobmanager.JobParameters;
+import org.thoughtcrime.securesms.jobmanager.SafeData;
+import org.thoughtcrime.securesms.profiles.AvatarHelper;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.signalservice.api.SignalServiceAccountManager;
+import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
+import org.whispersystems.signalservice.api.util.StreamDetails;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import androidx.work.Data;
+
+public class RotateProfileKeyJob extends ContextJob implements InjectableType {
+
+ @Inject SignalServiceAccountManager accountManager;
+
+ public RotateProfileKeyJob() {
+ super(null, null);
+ }
+
+ public RotateProfileKeyJob(Context context) {
+ super(context, new JobParameters.Builder()
+ .withGroupId("__ROTATE_PROFILE_KEY__")
+ .withDuplicatesIgnored(true)
+ .withNetworkRequirement()
+ .create());
+ }
+
+ @NonNull
+ @Override
+ protected Data serialize(@NonNull Data.Builder dataBuilder) {
+ return dataBuilder.build();
+ }
+
+ @Override
+ protected void initialize(@NonNull SafeData data) {
+ }
+
+ @Override
+ public void onRun() throws Exception {
+ byte[] profileKey = ProfileKeyUtil.rotateProfileKey(context);
+
+ accountManager.setProfileName(profileKey, TextSecurePreferences.getProfileName(context));
+ accountManager.setProfileAvatar(profileKey, getProfileAvatar());
+
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new RefreshAttributesJob(context));
+ }
+
+ @Override
+ protected void onCanceled() {
+
+ }
+
+ @Override
+ protected boolean onShouldRetry(Exception exception) {
+ return exception instanceof PushNetworkException;
+ }
+
+ private @Nullable StreamDetails getProfileAvatar() {
+ try {
+ Address localAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(context));
+ File avatarFile = AvatarHelper.getAvatarFile(context, localAddress);
+
+ if (avatarFile.exists()) {
+ return new StreamDetails(new FileInputStream(avatarFile), "image/jpeg", avatarFile.length());
+ }
+ } catch (IOException e) {
+ return null;
+ }
+ return null;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
index 2f3703825..8f18f56da 100644
--- a/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
+++ b/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
@@ -10,6 +10,7 @@ import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.Preference;
+import android.view.View;
import android.widget.Toast;
import org.thoughtcrime.securesms.ApplicationContext;
@@ -20,9 +21,12 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob;
+import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
import org.thoughtcrime.securesms.service.KeyCachingService;
+import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
@@ -34,7 +38,8 @@ import mobi.upod.timedurationpicker.TimeDurationPickerDialog;
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment implements InjectableType {
- private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
+ private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
+ private static final String PREFERENCE_UNIDENTIFIED_LEARN_MORE = "pref_unidentified_learn_more";
private CheckBoxPreference disablePassphrase;
@@ -61,6 +66,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setOnPreferenceClickListener(new PassphraseIntervalClickListener());
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
this.findPreference(PREFERENCE_CATEGORY_BLOCKED).setOnPreferenceClickListener(new BlockedContactsClickListener());
+ this.findPreference(TextSecurePreferences.SHOW_UNIDENTIFIED_DELIVERY_INDICATORS).setOnPreferenceChangeListener(new ShowUnidentifiedDeliveryIndicatorsChangedListener());
+ this.findPreference(TextSecurePreferences.UNIVERSAL_UNIDENTIFIED_ACCESS).setOnPreferenceChangeListener(new UniversalUnidentifiedAccessChangedListener());
+ this.findPreference(PREFERENCE_UNIDENTIFIED_LEARN_MORE).setOnPreferenceClickListener(new UnidentifiedLearnMoreClickListener());
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
initializeVisibility();
@@ -177,7 +185,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
boolean enabled = (boolean)newValue;
ApplicationContext.getInstance(getContext())
.getJobManager()
- .add(new MultiDeviceReadReceiptUpdateJob(getContext(), enabled));
+ .add(new MultiDeviceConfigurationUpdateJob(getContext(),
+ enabled,
+ TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext())));
return true;
}
@@ -271,4 +281,35 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
}
}
+ private class ShowUnidentifiedDeliveryIndicatorsChangedListener implements Preference.OnPreferenceChangeListener {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ boolean enabled = (boolean) newValue;
+ ApplicationContext.getInstance(getContext())
+ .getJobManager()
+ .add(new MultiDeviceConfigurationUpdateJob(getContext(),
+ TextSecurePreferences.isReadReceiptsEnabled(getContext()),
+ enabled));
+
+ return true;
+ }
+ }
+
+ private class UniversalUnidentifiedAccessChangedListener implements Preference.OnPreferenceChangeListener {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ ApplicationContext.getInstance(getContext())
+ .getJobManager()
+ .add(new RefreshAttributesJob(getContext()));
+ return true;
+ }
+ }
+
+ private class UnidentifiedLearnMoreClickListener implements Preference.OnPreferenceClickListener {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ CommunicationActions.openBrowserLink(preference.getContext(), "https://signal.org/blog/secret-sender/");
+ return true;
+ }
+ }
}
diff --git a/src/org/thoughtcrime/securesms/push/SecurityEventListener.java b/src/org/thoughtcrime/securesms/push/SecurityEventListener.java
index ea33cb3c3..f876a6a91 100644
--- a/src/org/thoughtcrime/securesms/push/SecurityEventListener.java
+++ b/src/org/thoughtcrime/securesms/push/SecurityEventListener.java
@@ -24,12 +24,4 @@ public class SecurityEventListener implements SignalServiceMessageSender.EventLi
public void onSecurityEvent(SignalServiceAddress textSecureAddress) {
SecurityEvent.broadcastSecurityUpdateEvent(context);
}
-
- @Override
- public void onUnidentifiedAccessFailed(SignalServiceAddress address) {
- // XXX Update UI
- DatabaseFactory.getRecipientDatabase(context)
- .setUnidentifiedAccessMode(Recipient.from(context, Address.fromSerialized(address.getNumber()), false),
- RecipientDatabase.UnidentifiedAccessMode.DISABLED);
- }
}
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java
index 62a712986..b963acd01 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipient.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java
@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
diff --git a/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java b/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java
index 0f0b8c11a..de098d252 100644
--- a/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java
+++ b/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java
@@ -41,7 +41,7 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private static SignalServiceMessagePipe pipe = null;
- public static SignalServiceMessagePipe unidentifiedPipe = null;
+ private static SignalServiceMessagePipe unidentifiedPipe = null;
private final Context context;
private final NetworkRequirement networkRequirement;
@@ -158,7 +158,7 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
Log.i(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
envelope -> {
- Log.i(TAG, "Retrieved envelope! " + envelope.getSource());
+ Log.i(TAG, "Retrieved envelope! " + String.valueOf(envelope.getSource()));
new PushContentReceiveJob(context).processEnvelope(envelope);
});
} catch (TimeoutException e) {
diff --git a/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java
deleted file mode 100644
index f78b81995..000000000
--- a/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package org.thoughtcrime.securesms.service;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-import android.support.annotation.Nullable;
-import android.support.v4.app.NotificationCompat;
-
-import org.thoughtcrime.securesms.ApplicationContext;
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.dependencies.InjectableType;
-import org.thoughtcrime.securesms.gcm.GcmBroadcastReceiver;
-import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
-import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirementProvider;
-import org.thoughtcrime.securesms.jobmanager.requirements.RequirementListener;
-import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
-import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.notifications.NotificationChannels;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.whispersystems.libsignal.InvalidVersionException;
-import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
-import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.inject.Inject;
-
-public class MessageRetrievalService extends Service implements InjectableType, RequirementListener {
-
- private static final String TAG = MessageRetrievalService.class.getSimpleName();
-
- public static final String ACTION_ACTIVITY_STARTED = "ACTIVITY_STARTED";
- public static final String ACTION_ACTIVITY_FINISHED = "ACTIVITY_FINISHED";
- public static final String ACTION_PUSH_RECEIVED = "PUSH_RECEIVED";
- public static final String ACTION_INITIALIZE = "INITIALIZE";
- public static final int FOREGROUND_ID = 313399;
-
- private static final long REQUEST_TIMEOUT_MINUTES = 1;
-
- private NetworkRequirement networkRequirement;
- private NetworkRequirementProvider networkRequirementProvider;
-
- @Inject
- public SignalServiceMessageReceiver receiver;
-
- private int activeActivities = 0;
- private List pushPending = new LinkedList<>();
- private MessageRetrievalThread retrievalThread = null;
-
- public static SignalServiceMessagePipe pipe = null;
- public static SignalServiceMessagePipe unidentifiedPipe = null;
-
- @Override
- public void onCreate() {
- super.onCreate();
- ApplicationContext.getInstance(this).injectDependencies(this);
-
- networkRequirement = new NetworkRequirement(this);
- networkRequirementProvider = new NetworkRequirementProvider(this);
-
- networkRequirementProvider.setListener(this);
-
- retrievalThread = new MessageRetrievalThread();
- retrievalThread.start();
-
- setForegroundIfNecessary();
- }
-
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent == null) return START_STICKY;
-
- if (ACTION_ACTIVITY_STARTED.equals(intent.getAction())) incrementActive();
- else if (ACTION_ACTIVITY_FINISHED.equals(intent.getAction())) decrementActive();
- else if (ACTION_PUSH_RECEIVED.equals(intent.getAction())) incrementPushReceived(intent);
-
- return START_STICKY;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (retrievalThread != null) {
- retrievalThread.stopThread();
- }
-
- sendBroadcast(new Intent("org.thoughtcrime.securesms.RESTART"));
- }
-
- @Override
- public void onRequirementStatusChanged() {
- synchronized (this) {
- notifyAll();
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- private void setForegroundIfNecessary() {
- if (TextSecurePreferences.isGcmDisabled(this)) {
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationChannels.OTHER);
- builder.setContentTitle(getString(R.string.MessageRetrievalService_signal));
- builder.setContentText(getString(R.string.MessageRetrievalService_background_connection_enabled));
- builder.setPriority(NotificationCompat.PRIORITY_MIN);
- builder.setWhen(0);
- builder.setSmallIcon(R.drawable.ic_signal_grey_24dp);
- startForeground(FOREGROUND_ID, builder.build());
- }
- }
-
- private synchronized void incrementActive() {
- activeActivities++;
- Log.d(TAG, "Active Count: " + activeActivities);
- notifyAll();
- }
-
- private synchronized void decrementActive() {
- activeActivities--;
- Log.d(TAG, "Active Count: " + activeActivities);
- notifyAll();
- }
-
- private synchronized void incrementPushReceived(Intent intent) {
- pushPending.add(intent);
- notifyAll();
- }
-
- private synchronized void decrementPushReceived() {
- if (!pushPending.isEmpty()) {
- Intent intent = pushPending.remove(0);
- GcmBroadcastReceiver.completeWakefulIntent(intent);
- notifyAll();
- }
- }
-
- private synchronized boolean isConnectionNecessary() {
- boolean isGcmDisabled = TextSecurePreferences.isGcmDisabled(this);
-
- Log.d(TAG, String.format("Network requirement: %s, active activities: %s, push pending: %s, gcm disabled: %b",
- networkRequirement.isPresent(), activeActivities, pushPending.size(), isGcmDisabled));
-
- return TextSecurePreferences.isPushRegistered(this) &&
- TextSecurePreferences.isWebsocketRegistered(this) &&
- (activeActivities > 0 || !pushPending.isEmpty() || isGcmDisabled) &&
- networkRequirement.isPresent();
- }
-
- private synchronized void waitForConnectionNecessary() {
- try {
- while (!isConnectionNecessary()) wait();
- } catch (InterruptedException e) {
- throw new AssertionError(e);
- }
- }
-
- private void shutdown(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
- try {
- pipe.shutdown();
- unidentifiedPipe.shutdown();
- } catch (Throwable t) {
- Log.w(TAG, t);
- }
- }
-
- public static void registerActivityStarted(Context activity) {
- Intent intent = new Intent(activity, MessageRetrievalService.class);
- intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_STARTED);
- activity.startService(intent);
- }
-
- public static void registerActivityStopped(Context activity) {
- Intent intent = new Intent(activity, MessageRetrievalService.class);
- intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_FINISHED);
- activity.startService(intent);
- }
-
- public static @Nullable SignalServiceMessagePipe getPipe() {
- return pipe;
- }
-
- public static @Nullable SignalServiceMessagePipe getUnidentifiedPipe() {
- return unidentifiedPipe;
- }
-
- private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
-
- private AtomicBoolean stopThread = new AtomicBoolean(false);
-
- MessageRetrievalThread() {
- super("MessageRetrievalService");
- setUncaughtExceptionHandler(this);
- }
-
- @Override
- public void run() {
- while (!stopThread.get()) {
- Log.i(TAG, "Waiting for websocket state change....");
- waitForConnectionNecessary();
-
- Log.i(TAG, "Making websocket connection....");
- pipe = receiver.createMessagePipe();
- unidentifiedPipe = receiver.createUnidentifiedMessagePipe();
-
- SignalServiceMessagePipe localPipe = pipe;
- SignalServiceMessagePipe unidentifiedLocalPipe = unidentifiedPipe;
-
- try {
- while (isConnectionNecessary() && !stopThread.get()) {
- try {
- Log.i(TAG, "Reading message...");
- localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
- envelope -> {
- Log.i(TAG, "Retrieved envelope! " + envelope.getSource());
- new PushContentReceiveJob(getApplicationContext()).processEnvelope(envelope);
- decrementPushReceived();
- });
- } catch (TimeoutException e) {
- Log.w(TAG, "Application level read timeout...");
- } catch (InvalidVersionException e) {
- Log.w(TAG, e);
- }
- }
- } catch (Throwable e) {
- Log.w(TAG, e);
- } finally {
- Log.w(TAG, "Shutting down pipe...");
- shutdown(localPipe, unidentifiedLocalPipe);
- }
-
- Log.i(TAG, "Looping...");
- }
-
- Log.i(TAG, "Exiting...");
- }
-
- private void stopThread() {
- stopThread.set(true);
- }
-
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- Log.w(TAG, "*** Uncaught exception!");
- Log.w(TAG, e);
- }
- }
-}
diff --git a/src/org/thoughtcrime/securesms/util/CommunicationActions.java b/src/org/thoughtcrime/securesms/util/CommunicationActions.java
index a8c0182d8..46b2f9192 100644
--- a/src/org/thoughtcrime/securesms/util/CommunicationActions.java
+++ b/src/org/thoughtcrime/securesms/util/CommunicationActions.java
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util;
import android.Manifest;
import android.app.Activity;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -10,6 +11,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
+import android.widget.Toast;
import org.thoughtcrime.securesms.ConversationActivity;
import org.thoughtcrime.securesms.R;
@@ -87,4 +89,13 @@ public class CommunicationActions {
}
context.startActivity(intent);
}
+
+ public static void openBrowserLink(@NonNull Context context, @NonNull String link) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
+ context.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(context, R.string.CommunicationActions_no_browser_found, Toast.LENGTH_SHORT).show();
+ }
+ }
}
diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
index 33a0fa8b9..63eb9b87d 100644
--- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
+++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
@@ -164,10 +164,12 @@ public class TextSecurePreferences {
private static final String NOTIFICATION_MESSAGES_CHANNEL_VERSION = "pref_notification_messages_channel_version";
private static final String NEEDS_MESSAGE_PULL = "pref_needs_message_pull";
-
+
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF = "pref_unidentified_access_certificate_rotation_time";
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE = "pref_unidentified_access_certificate";
- private static final String UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access";
+ public static final String UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access";
+ public static final String SHOW_UNIDENTIFIED_DELIVERY_INDICATORS = "pref_show_unidentifed_delivery_indicators";
+ private static final String UNIDENTIFIED_DELIVERY_ENABLED = "pref_unidentified_delivery_enabled";
public static boolean isScreenLockEnabled(@NonNull Context context) {
return getBooleanPreference(context, SCREEN_LOCK, false);
@@ -540,8 +542,16 @@ public class TextSecurePreferences {
return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false);
}
- public static void setUniversalUnidentifiedAccess(Context context, boolean value) {
- setBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, value);
+ public static boolean isShowUnidentifiedDeliveryIndicatorsEnabled(Context context) {
+ return getBooleanPreference(context, SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, false);
+ }
+
+ public static void setIsUnidentifiedDeliveryEnabled(Context context, boolean enabled) {
+ setBooleanPreference(context, UNIDENTIFIED_DELIVERY_ENABLED, enabled);
+ }
+
+ public static boolean isUnidentifiedDeliveryEnabled(Context context) {
+ return getBooleanPreference(context, UNIDENTIFIED_DELIVERY_ENABLED, true);
}
public static long getSignedPreKeyRotationTime(Context context) {