diff --git a/build.gradle b/build.gradle index 028a4f1df9..f09e16b5ad 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.9.0' + compile 'org.whispersystems:signal-service-android:2.10.0-RC1' compile 'org.whispersystems:webrtc-android:M69' compile "me.leolin:ShortcutBadger:1.1.16" @@ -147,124 +147,6 @@ 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', - '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:bf469abcdcd2b2ba429024aca30713eaa3b83a77af34030a818b1c9fb4780044', - '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.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1', - 'org.whispersystems:signal-service-java:4db9adf763071756cfd93fe48a40850f684ca02f2dea59601841abba7715c5c1', - '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:curve25519-android:82595394422b957d4a5b5f1b27b75ba25cf6dc4db4d312418ca38cd6fff279ca', - 'org.whispersystems:signal-protocol-java:5152c2b01a25147967d6bf82e540f947901bdfa79260be3eb3e96b03f787d6b5', - 'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74', - 'com.googlecode.libphonenumber:libphonenumber:183392c0565be16d3f6f86680b4106bbde6fe31a402ad21bf9823d938c0c8706', - 'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d', - 'com.squareup.okhttp3:okhttp:7265adbd6f028aade307f58569d814835cd02bc9beffb70c25f72c9de50d61c4', - 'com.madgag.spongycastle:pkix:0d9cca6991f68eb373cfad309d5268c9fc38db5efb5fe00dcccf5c973af1eca1', - 'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a', - 'org.threeten:threetenbp:f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7', - 'org.whispersystems:curve25519-java:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e', - 'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94', - 'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0', - 'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850', - 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f', - ] -} - android { flavorDimensions "none" compileSdkVersion 28 @@ -287,7 +169,7 @@ android { project.ext.set("archivesBaseName", "Signal"); buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" - buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\"" + buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.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\"" @@ -296,6 +178,7 @@ android { buildConfigField "String", "USER_AGENT", "\"OWA\"" buildConfigField "boolean", "DEV_BUILD", "false" buildConfigField "String", "MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\"" + buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"" ndk { abiFilters "armeabi", "armeabi-v7a", "x86" diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 35ea6c30cd..9a044582ac 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.LocalBackupListener; +import org.thoughtcrime.securesms.service.RotateSenderCertificateListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -207,6 +208,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc RotateSignedPreKeyListener.schedule(this); DirectoryRefreshListener.schedule(this); LocalBackupListener.schedule(this); + RotateSenderCertificateListener.schedule(this); if (BuildConfig.PLAY_STORE_DISABLED) { UpdateApkRefreshListener.schedule(this); diff --git a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java index 855310525a..e8d2bc0f86 100644 --- a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java +++ b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java @@ -167,10 +167,11 @@ public class ConfirmIdentityDialog extends AlertDialog { SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE, messageRecord.getIndividualRecipient().getAddress().toPhoneString(), - messageRecord.getRecipientDeviceId(), "", + messageRecord.getRecipientDeviceId(), messageRecord.getDateSent(), legacy ? Base64.decode(messageRecord.getBody()) : null, - !legacy ? Base64.decode(messageRecord.getBody()) : null); + !legacy ? Base64.decode(messageRecord.getBody()) : null, + 0, null); long pushId = pushDatabase.insert(envelope); diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 7fc4128fbf..ae64e2b415 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -51,10 +51,6 @@ import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; - -import org.thoughtcrime.securesms.camera.CameraActivity; -import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.logging.Log; import android.util.Pair; import android.view.KeyEvent; import android.view.Menu; @@ -79,6 +75,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.thoughtcrime.securesms.audio.AudioRecorder; import org.thoughtcrime.securesms.audio.AudioSlidePlayer; +import org.thoughtcrime.securesms.camera.CameraActivity; import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.components.AnimatingToggle; import org.thoughtcrime.securesms.components.AttachmentTypeSelector; @@ -114,6 +111,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; import org.thoughtcrime.securesms.database.DraftDatabase.Drafts; +import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; @@ -129,6 +127,7 @@ import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType; import org.thoughtcrime.securesms.mms.AudioSlide; diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java index db6b389fec..20cc4eeb5d 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java @@ -303,13 +303,13 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity } @Override - public Loader onCreateLoader(int id, Bundle args) { + public @NonNull Loader onCreateLoader(int id, Bundle args) { return new MessageDetailsLoader(this, getIntent().getStringExtra(TYPE_EXTRA), getIntent().getLongExtra(MESSAGE_ID_EXTRA, -1)); } @Override - public void onLoadFinished(Loader loader, Cursor cursor) { + public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA)); if (messageRecord == null) { @@ -320,7 +320,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity } @Override - public void onLoaderReset(Loader loader) { + public void onLoaderReset(@NonNull Loader loader) { recipientsList.setAdapter(null); } diff --git a/src/org/thoughtcrime/securesms/RegistrationActivity.java b/src/org/thoughtcrime/securesms/RegistrationActivity.java index 3bfa50f761..85d8395974 100644 --- a/src/org/thoughtcrime/securesms/RegistrationActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationActivity.java @@ -25,7 +25,6 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; -import org.thoughtcrime.securesms.logging.Log; import android.util.Pair; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -62,13 +61,16 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.PreKeyUtil; import org.thoughtcrime.securesms.crypto.SessionUtil; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.GcmRefreshJob; +import org.thoughtcrime.securesms.jobs.RotateCertificateJob; import org.thoughtcrime.securesms.lock.RegistrationLockReminders; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.push.AccountManagerFactory; @@ -688,13 +690,17 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif } private void verifyAccount(@NonNull String code, @Nullable String pin) throws IOException { - int registrationId = KeyHelper.generateRegistrationId(false); + int registrationId = KeyHelper.generateRegistrationId(false); + byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(RegistrationActivity.this); + boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(RegistrationActivity.this); + TextSecurePreferences.setLocalRegistrationId(RegistrationActivity.this, registrationId); SessionUtil.archiveAllSessions(RegistrationActivity.this); String signalingKey = Util.getSecret(52); - accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent(), pin); + accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent(), pin, + unidentifiedAccessKey, universalUnidentifiedAccess); IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(RegistrationActivity.this); List records = PreKeyUtil.generatePreKeys(RegistrationActivity.this); @@ -727,6 +733,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif private void handleSuccessfulRegistration() { ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new DirectoryRefreshJob(RegistrationActivity.this, false)); + ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new RotateCertificateJob(RegistrationActivity.this)); DirectoryRefreshListener.schedule(RegistrationActivity.this); RotateSignedPreKeyListener.schedule(RegistrationActivity.this); diff --git a/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java b/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java index 5c8b40de6b..19427c5862 100644 --- a/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java @@ -83,7 +83,7 @@ public class PointerAttachment extends Attachment { pointer.get().asPointer().getSize().or(0), pointer.get().asPointer().getFileName().orNull(), String.valueOf(pointer.get().asPointer().getId()), - encodedKey, pointer.get().asPointer().getRelay().orNull(), + encodedKey, null, pointer.get().asPointer().getDigest().orNull(), pointer.get().asPointer().getVoiceNote(), pointer.get().asPointer().getWidth(), diff --git a/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java b/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java new file mode 100644 index 0000000000..4de44cd020 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java @@ -0,0 +1,118 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +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.recipients.Recipient; +import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.ecc.Curve; +import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; + +import java.io.IOException; + +public class UnidentifiedAccessUtil { + + private static final String TAG = UnidentifiedAccessUtil.class.getSimpleName(); + + public static CertificateValidator getCertificateValidator() { + try { + ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BuildConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0); + return new CertificateValidator(unidentifiedSenderTrustRoot); + } catch (InvalidKeyException | IOException e) { + throw new AssertionError(e); + } + } + + @WorkerThread + public static Optional getAccessFor(@NonNull Context context, + @NonNull Recipient recipient) + { + try { + byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient); + byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context); + byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context); + + if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) { + 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)); + + if (theirUnidentifiedAccessKey != null && + ourUnidentifiedAccessKey != null && + ourUnidentifiedAccessCertificate != null) + { + return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(theirUnidentifiedAccessKey, + ourUnidentifiedAccessCertificate), + new UnidentifiedAccess(ourUnidentifiedAccessKey, + ourUnidentifiedAccessCertificate))); + } + + return Optional.absent(); + } catch (InvalidCertificateException e) { + Log.w(TAG, e); + return Optional.absent(); + } + } + + public static Optional getAccessForSync(@NonNull Context context) { + try { + byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context); + byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context); + + if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) { + ourUnidentifiedAccessKey = Util.getSecretBytes(16); + } + + if (ourUnidentifiedAccessKey != null && ourUnidentifiedAccessCertificate != null) { + return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey, + ourUnidentifiedAccessCertificate), + new UnidentifiedAccess(ourUnidentifiedAccessKey, + ourUnidentifiedAccessCertificate))); + } + + return Optional.absent(); + } catch (InvalidCertificateException e) { + Log.w(TAG, e); + return Optional.absent(); + } + } + + public static @NonNull byte[] getSelfUnidentifiedAccessKey(@NonNull Context context) { + return UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getProfileKey(context)); + } + + private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient) { + byte[] theirProfileKey = recipient.resolve().getProfileKey(); + + switch (recipient.resolve().getUnidentifiedAccessMode()) { + case UNKNOWN: + if (theirProfileKey == null) return Util.getSecretBytes(16); + else return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); + case DISABLED: + return null; + case ENABLED: + if (theirProfileKey == null) return null; + else return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); + case UNRESTRICTED: + return Util.getSecretBytes(16); + default: + throw new AssertionError("Unknown mode: " + recipient.getUnidentifiedAccessMode().getMode()); + } + } +} diff --git a/src/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java b/src/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java index 6d1ee8a96b..40fef31c9f 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java @@ -51,6 +51,11 @@ public class SignalProtocolStoreImpl implements SignalProtocolStore { return identityKeyStore.isTrustedIdentity(address, identityKey, direction); } + @Override + public IdentityKey getIdentity(SignalProtocolAddress address) { + return identityKeyStore.getIdentity(address); + } + @Override public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { return preKeyStore.loadPreKey(preKeyId); diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java index 679ab27efb..13b1becf8f 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java @@ -108,6 +108,17 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { } } + @Override + public IdentityKey getIdentity(SignalProtocolAddress address) { + Optional record = DatabaseFactory.getIdentityDatabase(context).getIdentity(Address.fromSerialized(address.getName())); + + if (record.isPresent()) { + return record.get().getIdentityKey(); + } else { + return null; + } + } + private boolean isTrustedForSending(IdentityKey identityKey, Optional identityRecord) { if (!identityRecord.isPresent()) { Log.w(TAG, "Nothing here, returning true..."); diff --git a/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java b/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java index e4fae7292c..58a6fdad9a 100644 --- a/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java @@ -17,11 +17,12 @@ public class GroupReceiptDatabase extends Database { public static final String TABLE_NAME = "group_receipts"; - private static final String ID = "_id"; - public static final String MMS_ID = "mms_id"; - private static final String ADDRESS = "address"; - private static final String STATUS = "status"; - private static final String TIMESTAMP = "timestamp"; + private static final String ID = "_id"; + public static final String MMS_ID = "mms_id"; + private static final String ADDRESS = "address"; + private static final String STATUS = "status"; + private static final String TIMESTAMP = "timestamp"; + private static final String UNIDENTIFIED = "unidentified"; public static final int STATUS_UNKNOWN = -1; public static final int STATUS_UNDELIVERED = 0; @@ -29,7 +30,7 @@ public class GroupReceiptDatabase extends Database { public static final int STATUS_READ = 2; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + - MMS_ID + " INTEGER, " + ADDRESS + " TEXT, " + STATUS + " INTEGER, " + TIMESTAMP + " INTEGER);"; + MMS_ID + " INTEGER, " + ADDRESS + " TEXT, " + STATUS + " INTEGER, " + TIMESTAMP + " INTEGER, " + UNIDENTIFIED + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXES = { "CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", @@ -63,6 +64,16 @@ public class GroupReceiptDatabase extends Database { new String[] {String.valueOf(mmsId), address.serialize(), String.valueOf(status)}); } + public void setUnidentified(Address address, long mmsId, boolean unidentified) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + ContentValues values = new ContentValues(1); + values.put(UNIDENTIFIED, unidentified ? 1 : 0); + + db.update(TABLE_NAME, values, MMS_ID + " = ? AND " + ADDRESS + " = ?", + new String[] {String.valueOf(mmsId), address.serialize()}); + + } + public @NonNull List getGroupReceiptInfo(long mmsId) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); List results = new LinkedList<>(); @@ -71,7 +82,8 @@ public class GroupReceiptDatabase extends Database { while (cursor != null && cursor.moveToNext()) { results.add(new GroupReceiptInfo(Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))), cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)), - cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)))); + cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)), + cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED)) == 1)); } } @@ -92,11 +104,13 @@ public class GroupReceiptDatabase extends Database { private final Address address; private final int status; private final long timestamp; + private final boolean unidentified; - public GroupReceiptInfo(Address address, int status, long timestamp) { - this.address = address; - this.status = status; - this.timestamp = timestamp; + GroupReceiptInfo(Address address, int status, long timestamp, boolean unidentified) { + this.address = address; + this.status = status; + this.timestamp = timestamp; + this.unidentified = unidentified; } public Address getAddress() { @@ -110,5 +124,9 @@ public class GroupReceiptDatabase extends Database { public long getTimestamp() { return timestamp; } + + public boolean isUnidentified() { + return unidentified; + } } } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 031c934089..73097c9375 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -23,7 +23,6 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; -import org.thoughtcrime.securesms.logging.Log; import android.util.Pair; import com.annimon.stream.Stream; @@ -52,6 +51,7 @@ import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.database.model.Quote; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.TrimThreadJob; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; @@ -125,7 +125,7 @@ public class MmsDatabase extends MessagingDatabase { EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " INTEGER DEFAULT 0, " + READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + QUOTE_ID + " INTEGER DEFAULT 0, " + QUOTE_AUTHOR + " TEXT, " + QUOTE_BODY + " TEXT, " + QUOTE_ATTACHMENT + " INTEGER DEFAULT -1, " + - QUOTE_MISSING + " INTEGER DEFAULT 0, " + SHARED_CONTACTS + " TEXT);"; + QUOTE_MISSING + " INTEGER DEFAULT 0, " + SHARED_CONTACTS + " TEXT, " + UNIDENTIFIED + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", @@ -145,7 +145,7 @@ public class MmsDatabase extends MessagingDatabase { MESSAGE_SIZE, STATUS, TRANSACTION_ID, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID, DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID, - EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING, SHARED_CONTACTS, + EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING, SHARED_CONTACTS, UNIDENTIFIED, "json_group_array(json_object(" + "'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " + "'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " + @@ -403,6 +403,14 @@ public class MmsDatabase extends MessagingDatabase { notifyConversationListeners(threadId); } + public void markUnidentified(long messageId, boolean unidentified) { + ContentValues contentValues = new ContentValues(); + contentValues.put(UNIDENTIFIED, unidentified ? 1 : 0); + + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); + } + @Override public void markExpireStarted(long messageId) { markExpireStarted(messageId, System.currentTimeMillis()); @@ -575,6 +583,8 @@ public class MmsDatabase extends MessagingDatabase { String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId); + String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES)); + String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE)); long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID)); String quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR)); @@ -585,20 +595,38 @@ public class MmsDatabase extends MessagingDatabase { Set contactAttachments = new HashSet<>(Stream.of(contacts).map(Contact::getAvatarAttachment).filter(a -> a != null).toList()); List attachments = Stream.of(associatedAttachments).filterNot(Attachment::isQuote).filterNot(contactAttachments::contains).map(a -> (Attachment)a).toList(); - Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false); - QuoteModel quote = null; + Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false); + List networkFailures = new LinkedList<>(); + List mismatches = new LinkedList<>(); + QuoteModel quote = null; if (quoteId > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) { quote = new QuoteModel(quoteId, Address.fromSerialized(quoteAuthor), quoteText, quoteMissing, quoteAttachments); } + if (!TextUtils.isEmpty(mismatchDocument)) { + try { + mismatches = JsonUtils.fromJson(mismatchDocument, IdentityKeyMismatchList.class).getList(); + } catch (IOException e) { + Log.w(TAG, e); + } + } + + if (!TextUtils.isEmpty(networkDocument)) { + try { + networkFailures = JsonUtils.fromJson(networkDocument, NetworkFailureList.class).getList(); + } catch (IOException e) { + Log.w(TAG, e); + } + } + if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) { return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, quote, contacts); } else if (Types.isExpirationTimerUpdate(outboxType)) { return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn); } - OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts); + OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts, networkFailures, mismatches); if (Types.isSecureType(outboxType)) { return new OutgoingSecureMediaMessage(message); @@ -730,6 +758,7 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId()); contentValues.put(EXPIRES_IN, retrieved.getExpiresIn()); contentValues.put(READ, retrieved.isExpirationUpdate() ? 1 : 0); + contentValues.put(UNIDENTIFIED, retrieved.isUnidentified()); if (!contentValues.containsKey(DATE_SENT)) { contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED)); @@ -1181,7 +1210,7 @@ public class MmsDatabase extends MessagingDatabase { message.getOutgoingQuote().isOriginalMissing(), new SlideDeck(context, message.getOutgoingQuote().getAttachments())) : null, - message.getSharedContacts()); + message.getSharedContacts(), false); } } @@ -1269,6 +1298,7 @@ public class MmsDatabase extends MessagingDatabase { int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)); long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED)); + boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.UNIDENTIFIED)) == 1; if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { readReceiptCount = 0; @@ -1287,7 +1317,7 @@ public class MmsDatabase extends MessagingDatabase { addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId, body, slideDeck, partCount, box, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, - readReceiptCount, quote, contacts); + readReceiptCount, quote, contacts, unidentified); } private Recipient getRecipientFor(String serialized) { diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 39b1eafafb..81fe7f50fa 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -19,6 +19,7 @@ public interface MmsSmsColumns { public static final String EXPIRES_IN = "expires_in"; public static final String EXPIRE_STARTED = "expire_started"; public static final String NOTIFIED = "notified"; + public static final String UNIDENTIFIED = "unidentified"; public static class Types { protected static final long TOTAL_MASK = 0xFFFFFFFF; diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 803389f1e0..4725659c01 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -49,7 +49,9 @@ public class MmsSmsDatabase extends Database { MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, - SmsDatabase.STATUS, MmsDatabase.PART_COUNT, + SmsDatabase.STATUS, + MmsSmsColumns.UNIDENTIFIED, + MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, @@ -232,6 +234,7 @@ public class MmsSmsDatabase extends Database { MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, + MmsDatabase.UNIDENTIFIED, MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, @@ -256,6 +259,7 @@ public class MmsSmsDatabase extends Database { MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, + MmsDatabase.UNIDENTIFIED, MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, @@ -266,7 +270,8 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_BODY, MmsDatabase.QUOTE_MISSING, MmsDatabase.QUOTE_ATTACHMENT, - MmsDatabase.SHARED_CONTACTS}; + MmsDatabase.SHARED_CONTACTS, + MmsDatabase.UNIDENTIFIED}; SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); @@ -304,6 +309,7 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(MmsDatabase.EXPIRY); mmsColumnsPresent.add(MmsDatabase.NOTIFIED); mmsColumnsPresent.add(MmsDatabase.STATUS); + mmsColumnsPresent.add(MmsDatabase.UNIDENTIFIED); mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE); mmsColumnsPresent.add(AttachmentDatabase.ROW_ID); @@ -351,6 +357,7 @@ public class MmsSmsDatabase extends Database { smsColumnsPresent.add(SmsDatabase.DATE_SENT); smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED); smsColumnsPresent.add(SmsDatabase.STATUS); + smsColumnsPresent.add(SmsDatabase.UNIDENTIFIED); @SuppressWarnings("deprecation") String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 4, MMS_TRANSPORT, selection, null, MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID, null); diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java index 8bc17c7510..3219db08d8 100644 --- a/src/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -20,17 +20,20 @@ public class PushDatabase extends Database { private static final String TAG = PushDatabase.class.getSimpleName(); - private static final String TABLE_NAME = "push"; - public static final String ID = "_id"; - public static final String TYPE = "type"; - public static final String SOURCE = "source"; - public static final String DEVICE_ID = "device_id"; - public static final String LEGACY_MSG = "body"; - public static final String CONTENT = "content"; - public static final String TIMESTAMP = "timestamp"; + private static final String TABLE_NAME = "push"; + public static final String ID = "_id"; + public static final String TYPE = "type"; + public static final String SOURCE = "source"; + public static final String DEVICE_ID = "device_id"; + public static final String LEGACY_MSG = "body"; + public static final String CONTENT = "content"; + public static final String TIMESTAMP = "timestamp"; + public static final String SERVER_TIMESTAMP = "server_timestamp"; + public static final String SERVER_GUID = "server_guid"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + - TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + LEGACY_MSG + " TEXT, " + CONTENT + " TEXT, " + TIMESTAMP + " INTEGER);"; + TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + LEGACY_MSG + " TEXT, " + CONTENT + " TEXT, " + TIMESTAMP + " INTEGER, " + + SERVER_TIMESTAMP + " INTEGER DEFAULT 0, " + SERVER_GUID + " TEXT DEFAULT NULL);"; public PushDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); @@ -49,6 +52,8 @@ public class PushDatabase extends Database { values.put(LEGACY_MSG, envelope.hasLegacyMessage() ? Base64.encodeBytes(envelope.getLegacyMessage()) : ""); values.put(CONTENT, envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : ""); values.put(TIMESTAMP, envelope.getTimestamp()); + values.put(SERVER_TIMESTAMP, envelope.getServerTimestamp()); + values.put(SERVER_GUID, envelope.getUuid()); return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); } @@ -69,10 +74,11 @@ public class PushDatabase extends Database { return new SignalServiceEnvelope(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)), cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)), cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)), - "", cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)), Util.isEmpty(legacyMessage) ? null : Base64.decode(legacyMessage), - Util.isEmpty(content) ? null : Base64.decode(content)); + Util.isEmpty(content) ? null : Base64.decode(content), + cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_TIMESTAMP)), + cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID))); } } catch (IOException e) { Log.w(TAG, e); @@ -135,16 +141,19 @@ public class PushDatabase extends Database { if (cursor == null || !cursor.moveToNext()) return null; - int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)); - String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)); - int deviceId = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)); - String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG)); - String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT)); - long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)); + int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)); + String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)); + int deviceId = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)); + String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG)); + String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT)); + long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)); + long serverTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_TIMESTAMP)); + String serverGuid = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID)); - return new SignalServiceEnvelope(type, source, deviceId, "", timestamp, + return new SignalServiceEnvelope(type, source, deviceId, timestamp, legacyMessage != null ? Base64.decode(legacyMessage) : null, - content != null ? Base64.decode(content) : null); + content != null ? Base64.decode(content) : null, + serverTimestamp, serverGuid); } catch (IOException e) { throw new AssertionError(e); } diff --git a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java index 4cca0b7f42..d77976bd75 100644 --- a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -6,7 +6,6 @@ import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import org.thoughtcrime.securesms.logging.Log; import com.annimon.stream.Stream; @@ -14,6 +13,7 @@ import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Util; @@ -33,34 +33,36 @@ public class RecipientDatabase extends Database { private static final String TAG = RecipientDatabase.class.getSimpleName(); - static final String TABLE_NAME = "recipient_preferences"; - private static final String ID = "_id"; - static final String ADDRESS = "recipient_ids"; - private static final String BLOCK = "block"; - private static final String NOTIFICATION = "notification"; - private static final String VIBRATE = "vibrate"; - private static final String MUTE_UNTIL = "mute_until"; - private static final String COLOR = "color"; - private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder"; - private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id"; - private static final String EXPIRE_MESSAGES = "expire_messages"; - private static final String REGISTERED = "registered"; - private static final String PROFILE_KEY = "profile_key"; - private static final String SYSTEM_DISPLAY_NAME = "system_display_name"; - private static final String SYSTEM_PHOTO_URI = "system_contact_photo"; - private static final String SYSTEM_PHONE_LABEL = "system_phone_label"; - private static final String SYSTEM_CONTACT_URI = "system_contact_uri"; - private static final String SIGNAL_PROFILE_NAME = "signal_profile_name"; - private static final String SIGNAL_PROFILE_AVATAR = "signal_profile_avatar"; - private static final String PROFILE_SHARING = "profile_sharing_approval"; - private static final String CALL_RINGTONE = "call_ringtone"; - private static final String CALL_VIBRATE = "call_vibrate"; - private static final String NOTIFICATION_CHANNEL = "notification_channel"; + static final String TABLE_NAME = "recipient_preferences"; + private static final String ID = "_id"; + static final String ADDRESS = "recipient_ids"; + private static final String BLOCK = "block"; + private static final String NOTIFICATION = "notification"; + private static final String VIBRATE = "vibrate"; + private static final String MUTE_UNTIL = "mute_until"; + private static final String COLOR = "color"; + private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder"; + private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id"; + private static final String EXPIRE_MESSAGES = "expire_messages"; + private static final String REGISTERED = "registered"; + private static final String PROFILE_KEY = "profile_key"; + private static final String SYSTEM_DISPLAY_NAME = "system_display_name"; + private static final String SYSTEM_PHOTO_URI = "system_contact_photo"; + private static final String SYSTEM_PHONE_LABEL = "system_phone_label"; + private static final String SYSTEM_CONTACT_URI = "system_contact_uri"; + private static final String SIGNAL_PROFILE_NAME = "signal_profile_name"; + private static final String SIGNAL_PROFILE_AVATAR = "signal_profile_avatar"; + private static final String PROFILE_SHARING = "profile_sharing_approval"; + private static final String CALL_RINGTONE = "call_ringtone"; + private static final String CALL_VIBRATE = "call_vibrate"; + private static final String NOTIFICATION_CHANNEL = "notification_channel"; + private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode"; private static final String[] RECIPIENT_PROJECTION = new String[] { BLOCK, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED, PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI, - SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL + SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL, + UNIDENTIFIED_ACCESS_MODE }; static final List TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) @@ -103,6 +105,24 @@ public class RecipientDatabase extends Database { } } + public enum UnidentifiedAccessMode { + UNKNOWN(0), DISABLED(1), ENABLED(2), UNRESTRICTED(3); + + private final int mode; + + UnidentifiedAccessMode(int mode) { + this.mode = mode; + } + + public int getMode() { + return mode; + } + + public static UnidentifiedAccessMode fromMode(int mode) { + return values()[mode]; + } + } + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + @@ -126,7 +146,8 @@ public class RecipientDatabase extends Database { PROFILE_SHARING + " INTEGER DEFAULT 0, " + CALL_RINGTONE + " TEXT DEFAULT NULL, " + CALL_VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " + - NOTIFICATION_CHANNEL + " TEXT DEFAULT NULL);"; + NOTIFICATION_CHANNEL + " TEXT DEFAULT NULL, " + + UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0);"; public RecipientDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); @@ -169,29 +190,30 @@ public class RecipientDatabase extends Database { } Optional getRecipientSettings(@NonNull Cursor cursor) { - boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1; - String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION)); - String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE)); - int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE)); - int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE)); - long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL)); - String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR)); - boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1; - int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID)); - int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRE_MESSAGES)); - int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED)); - String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY)); - String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)); - String systemContactPhoto = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHOTO_URI)); - String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL)); - String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI)); - String signalProfileName = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_NAME)); - String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR)); - boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1; - String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); + boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1; + String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION)); + String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE)); + int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE)); + int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE)); + long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL)); + String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR)); + boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1; + int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID)); + int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRE_MESSAGES)); + int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED)); + String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY)); + String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)); + String systemContactPhoto = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHOTO_URI)); + String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL)); + String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI)); + String signalProfileName = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_NAME)); + String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR)); + boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1; + String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); + int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE)); MaterialColor color; - byte[] profileKey = null; + byte[] profileKey = null; try { color = serializedColor == null ? null : MaterialColor.fromSerialized(serializedColor); @@ -219,7 +241,7 @@ public class RecipientDatabase extends Database { profileKey, systemDisplayName, systemContactPhoto, systemPhoneLabel, systemContactUri, signalProfileName, signalProfileAvatar, profileSharing, - notificationChannel)); + notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode))); } public BulkOperationsHandle resetAllSystemContactInfo() { @@ -309,6 +331,13 @@ public class RecipientDatabase extends Database { recipient.resolve().setExpireMessages(expiration); } + public void setUnidentifiedAccessMode(@NonNull Recipient recipient, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) { + ContentValues values = new ContentValues(1); + values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode()); + updateOrInsert(recipient.getAddress(), values); + recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode); + } + public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) { ContentValues values = new ContentValues(1); values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey)); @@ -506,26 +535,27 @@ public class RecipientDatabase extends Database { } public static class RecipientSettings { - private final boolean blocked; - private final long muteUntil; - private final VibrateState messageVibrateState; - private final VibrateState callVibrateState; - private final Uri messageRingtone; - private final Uri callRingtone; - private final MaterialColor color; - private final boolean seenInviteReminder; - private final int defaultSubscriptionId; - private final int expireMessages; - private final RegisteredState registered; - private final byte[] profileKey; - private final String systemDisplayName; - private final String systemContactPhoto; - private final String systemPhoneLabel; - private final String systemContactUri; - private final String signalProfileName; - private final String signalProfileAvatar; - private final boolean profileSharing; - private final String notificationChannel; + private final boolean blocked; + private final long muteUntil; + private final VibrateState messageVibrateState; + private final VibrateState callVibrateState; + private final Uri messageRingtone; + private final Uri callRingtone; + private final MaterialColor color; + private final boolean seenInviteReminder; + private final int defaultSubscriptionId; + private final int expireMessages; + private final RegisteredState registered; + private final byte[] profileKey; + private final String systemDisplayName; + private final String systemContactPhoto; + private final String systemPhoneLabel; + private final String systemContactUri; + private final String signalProfileName; + private final String signalProfileAvatar; + private final boolean profileSharing; + private final String notificationChannel; + private final UnidentifiedAccessMode unidentifiedAccessMode; RecipientSettings(boolean blocked, long muteUntil, @NonNull VibrateState messageVibrateState, @@ -545,28 +575,30 @@ public class RecipientDatabase extends Database { @Nullable String signalProfileName, @Nullable String signalProfileAvatar, boolean profileSharing, - @Nullable String notificationChannel) + @Nullable String notificationChannel, + @NonNull UnidentifiedAccessMode unidentifiedAccessMode) { - this.blocked = blocked; - this.muteUntil = muteUntil; - this.messageVibrateState = messageVibrateState; - this.callVibrateState = callVibrateState; - this.messageRingtone = messageRingtone; - this.callRingtone = callRingtone; - this.color = color; - this.seenInviteReminder = seenInviteReminder; - this.defaultSubscriptionId = defaultSubscriptionId; - this.expireMessages = expireMessages; - this.registered = registered; - this.profileKey = profileKey; - this.systemDisplayName = systemDisplayName; - this.systemContactPhoto = systemContactPhoto; - this.systemPhoneLabel = systemPhoneLabel; - this.systemContactUri = systemContactUri; - this.signalProfileName = signalProfileName; - this.signalProfileAvatar = signalProfileAvatar; - this.profileSharing = profileSharing; - this.notificationChannel = notificationChannel; + this.blocked = blocked; + this.muteUntil = muteUntil; + this.messageVibrateState = messageVibrateState; + this.callVibrateState = callVibrateState; + this.messageRingtone = messageRingtone; + this.callRingtone = callRingtone; + this.color = color; + this.seenInviteReminder = seenInviteReminder; + this.defaultSubscriptionId = defaultSubscriptionId; + this.expireMessages = expireMessages; + this.registered = registered; + this.profileKey = profileKey; + this.systemDisplayName = systemDisplayName; + this.systemContactPhoto = systemContactPhoto; + this.systemPhoneLabel = systemPhoneLabel; + this.systemContactUri = systemContactUri; + this.signalProfileName = signalProfileName; + this.signalProfileAvatar = signalProfileAvatar; + this.profileSharing = profileSharing; + this.notificationChannel = notificationChannel; + this.unidentifiedAccessMode = unidentifiedAccessMode; } public @Nullable MaterialColor getColor() { @@ -613,7 +645,7 @@ public class RecipientDatabase extends Database { return registered; } - public byte[] getProfileKey() { + public @Nullable byte[] getProfileKey() { return profileKey; } @@ -648,6 +680,10 @@ public class RecipientDatabase extends Database { public @Nullable String getNotificationChannel() { return notificationChannel; } + + public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() { + return unidentifiedAccessMode; + } } public static class RecipientReader implements Closeable { diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index acac8c500c..ee7dcd3cfd 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -22,7 +22,6 @@ import android.content.Context; import android.database.Cursor; import android.support.annotation.NonNull; import android.text.TextUtils; -import org.thoughtcrime.securesms.logging.Log; import android.util.Pair; import com.annimon.stream.Stream; @@ -38,6 +37,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.TrimThreadJob; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; @@ -81,7 +81,7 @@ public class SmsDatabase extends MessagingDatabase { DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " DEFAULT 0, " + - READ_RECEIPT_COUNT + " INTEGER DEFAULT 0);"; + READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + UNIDENTIFIED + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", @@ -99,7 +99,7 @@ public class SmsDatabase extends MessagingDatabase { PROTOCOL, READ, STATUS, TYPE, REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, DELIVERY_RECEIPT_COUNT, MISMATCHED_IDENTITIES, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED, - NOTIFIED, READ_RECEIPT_COUNT + NOTIFIED, READ_RECEIPT_COUNT, UNIDENTIFIED }; private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); @@ -243,6 +243,14 @@ public class SmsDatabase extends MessagingDatabase { updateTypeBitmask(id, Types.TOTAL_MASK, Types.MISSED_CALL_TYPE); } + public void markUnidentified(long id, boolean unidentified) { + ContentValues contentValues = new ContentValues(1); + contentValues.put(UNIDENTIFIED, unidentified ? 1 : 0); + + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)}); + } + @Override public void markExpireStarted(long id) { markExpireStarted(id, System.currentTimeMillis()); @@ -559,6 +567,7 @@ public class SmsDatabase extends MessagingDatabase { values.put(READ, unread ? 0 : 1); values.put(SUBSCRIPTION_ID, message.getSubscriptionId()); values.put(EXPIRES_IN, message.getExpiresIn()); + values.put(UNIDENTIFIED, message.isUnidentified()); if (!TextUtils.isEmpty(message.getPseudoSubject())) values.put(SUBJECT, message.getPseudoSubject()); @@ -818,7 +827,7 @@ public class SmsDatabase extends MessagingDatabase { 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), threadId, 0, new LinkedList(), message.getSubscriptionId(), message.getExpiresIn(), - System.currentTimeMillis(), 0); + System.currentTimeMillis(), 0, false); } } @@ -858,6 +867,7 @@ public class SmsDatabase extends MessagingDatabase { long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN)); long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED)); String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY)); + boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.UNIDENTIFIED)) == 1; if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { readReceiptCount = 0; @@ -871,7 +881,7 @@ public class SmsDatabase extends MessagingDatabase { addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, type, threadId, status, mismatches, subscriptionId, - expiresIn, expireStarted, readReceiptCount); + expiresIn, expireStarted, readReceiptCount, unidentified); } private List getMismatches(String document) { diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 991539c2a1..5da18fd0ed 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -56,8 +56,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int BAD_IMPORT_CLEANUP = 10; private static final int QUOTE_MISSING = 11; private static final int NOTIFICATION_CHANNELS = 12; + private static final int SECRET_SENDER = 13; - private static final int DATABASE_VERSION = 12; + private static final int DATABASE_VERSION = 13; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -284,6 +285,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } } + if (oldVersion < SECRET_SENDER) { + 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.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index 4eedda8dad..9ed8e54fee 100644 --- a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -55,11 +55,12 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { List mismatches, List failures, int subscriptionId, long expiresIn, long expireStarted, int readReceiptCount, - @Nullable Quote quote, @Nullable List contacts) + @Nullable Quote quote, @Nullable List contacts, + boolean unidentified) { super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, - subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts); + subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts, unidentified); this.context = context.getApplicationContext(); this.partCount = partCount; diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index 436dbe4cea..1a1e146368 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -53,6 +53,7 @@ public abstract class MessageRecord extends DisplayRecord { private final int subscriptionId; private final long expiresIn; private final long expireStarted; + private final boolean unidentified; MessageRecord(Context context, long id, String body, Recipient conversationRecipient, Recipient individualRecipient, int recipientDeviceId, @@ -61,7 +62,7 @@ public abstract class MessageRecord extends DisplayRecord { List mismatches, List networkFailures, int subscriptionId, long expiresIn, long expireStarted, - int readReceiptCount) + int readReceiptCount, boolean unidentified) { super(context, body, conversationRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount); @@ -73,6 +74,7 @@ public abstract class MessageRecord extends DisplayRecord { this.subscriptionId = subscriptionId; this.expiresIn = expiresIn; this.expireStarted = expireStarted; + this.unidentified = unidentified; } public abstract boolean isMms(); @@ -242,4 +244,8 @@ public abstract class MessageRecord extends DisplayRecord { public long getExpireStarted() { return expireStarted; } + + public boolean isUnidentified() { + return unidentified; + } } diff --git a/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index fdf72a4768..c33af25ca0 100644 --- a/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -27,9 +27,9 @@ public abstract class MmsMessageRecord extends MessageRecord { long type, List mismatches, List networkFailures, int subscriptionId, long expiresIn, long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, - @Nullable Quote quote, @NonNull List contacts) + @Nullable Quote quote, @NonNull List contacts, boolean unidentified) { - super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount); + super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount, unidentified); this.slideDeck = slideDeck; this.quote = quote; diff --git a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java index c028f6480c..3a0b20e530 100644 --- a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java @@ -56,7 +56,7 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord { super(context, id, "", conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, new LinkedList(), new LinkedList(), subscriptionId, - 0, 0, slideDeck, readReceiptCount, null, Collections.emptyList()); + 0, 0, slideDeck, readReceiptCount, null, Collections.emptyList(), false); this.contentLocation = contentLocation; this.messageSize = messageSize; diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 00c092521c..1f3c501496 100644 --- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -47,12 +47,12 @@ public class SmsMessageRecord extends MessageRecord { long type, long threadId, int status, List mismatches, int subscriptionId, long expiresIn, long expireStarted, - int readReceiptCount) + int readReceiptCount, boolean unidentified) { super(context, id, body, recipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, mismatches, new LinkedList<>(), subscriptionId, - expiresIn, expireStarted, readReceiptCount); + expiresIn, expireStarted, readReceiptCount, unidentified); } public long getType() { diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java index 7045604091..bfe448f373 100644 --- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java @@ -33,7 +33,9 @@ import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; +import org.thoughtcrime.securesms.jobs.RotateCertificateJob; import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob; +import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob; import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.push.SecurityEventListener; @@ -83,7 +85,9 @@ import dagger.Provides; SendReadReceiptJob.class, MultiDeviceReadReceiptUpdateJob.class, AppProtectionPreferenceFragment.class, - GcmBroadcastReceiver.class}) + GcmBroadcastReceiver.class, + RotateCertificateJob.class, + SendDeliveryReceiptJob.class}) public class SignalCommunicationModule { private static final String TAG = SignalCommunicationModule.class.getSimpleName(); @@ -118,10 +122,13 @@ public class SignalCommunicationModule { new DynamicCredentialsProvider(context), new SignalProtocolStoreImpl(context), BuildConfig.USER_AGENT, + TextSecurePreferences.isMultiDevice(context), Optional.fromNullable(IncomingMessageObserver.getPipe()), + Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()), Optional.of(new SecurityEventListener(context))); } else { - this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe()); + this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe(), IncomingMessageObserver.getUnidentifiedPipe()); + this.messageSender.setIsMultiDevice(TextSecurePreferences.isMultiDevice(context)); } return this.messageSender; diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index bcf2129b6f..e9e35d54dd 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -4,7 +4,6 @@ package org.thoughtcrime.securesms.groups; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import org.thoughtcrime.securesms.logging.Log; import com.google.protobuf.ByteString; @@ -17,6 +16,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.notifications.MessageNotifier; @@ -27,8 +27,8 @@ import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; +import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type; @@ -47,7 +47,7 @@ public class GroupMessageProcessor { private static final String TAG = GroupMessageProcessor.class.getSimpleName(); public static @Nullable Long process(@NonNull Context context, - @NonNull SignalServiceEnvelope envelope, + @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, boolean outgoing) { @@ -62,13 +62,13 @@ public class GroupMessageProcessor { Optional record = database.getGroup(id); if (record.isPresent() && group.getType() == Type.UPDATE) { - return handleGroupUpdate(context, envelope, group, record.get(), outgoing); + return handleGroupUpdate(context, content, group, record.get(), outgoing); } else if (!record.isPresent() && group.getType() == Type.UPDATE) { - return handleGroupCreate(context, envelope, group, outgoing); + return handleGroupCreate(context, content, group, outgoing); } else if (record.isPresent() && group.getType() == Type.QUIT) { - return handleGroupLeave(context, envelope, group, record.get(), outgoing); + return handleGroupLeave(context, content, group, record.get(), outgoing); } else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) { - return handleGroupInfoRequest(context, envelope, group, record.get()); + return handleGroupInfoRequest(context, content, group, record.get()); } else { Log.w(TAG, "Received unknown type, ignoring..."); return null; @@ -76,7 +76,7 @@ public class GroupMessageProcessor { } private static @Nullable Long handleGroupCreate(@NonNull Context context, - @NonNull SignalServiceEnvelope envelope, + @NonNull SignalServiceContent content, @NonNull SignalServiceGroup group, boolean outgoing) { @@ -95,14 +95,13 @@ public class GroupMessageProcessor { } database.create(id, group.getName().orNull(), members, - avatar != null && avatar.isPointer() ? avatar.asPointer() : null, - envelope.getRelay()); + avatar != null && avatar.isPointer() ? avatar.asPointer() : null, null); - return storeMessage(context, envelope, group, builder.build(), outgoing); + return storeMessage(context, content, group, builder.build(), outgoing); } private static @Nullable Long handleGroupUpdate(@NonNull Context context, - @NonNull SignalServiceEnvelope envelope, + @NonNull SignalServiceContent content, @NonNull SignalServiceGroup group, @NonNull GroupRecord groupRecord, boolean outgoing) @@ -156,25 +155,25 @@ public class GroupMessageProcessor { if (!groupRecord.isActive()) database.setActive(id, true); - return storeMessage(context, envelope, group, builder.build(), outgoing); + return storeMessage(context, content, group, builder.build(), outgoing); } private static Long handleGroupInfoRequest(@NonNull Context context, - @NonNull SignalServiceEnvelope envelope, + @NonNull SignalServiceContent content, @NonNull SignalServiceGroup group, @NonNull GroupRecord record) { - if (record.getMembers().contains(Address.fromExternal(context, envelope.getSource()))) { + if (record.getMembers().contains(Address.fromExternal(context, content.getSender()))) { ApplicationContext.getInstance(context) .getJobManager() - .add(new PushGroupUpdateJob(context, envelope.getSource(), group.getGroupId())); + .add(new PushGroupUpdateJob(context, content.getSender(), group.getGroupId())); } return null; } private static Long handleGroupLeave(@NonNull Context context, - @NonNull SignalServiceEnvelope envelope, + @NonNull SignalServiceContent content, @NonNull SignalServiceGroup group, @NonNull GroupRecord record, boolean outgoing) @@ -186,11 +185,11 @@ public class GroupMessageProcessor { GroupContext.Builder builder = createGroupContext(group); builder.setType(GroupContext.Type.QUIT); - if (members.contains(Address.fromExternal(context, envelope.getSource()))) { - database.remove(id, Address.fromExternal(context, envelope.getSource())); + if (members.contains(Address.fromExternal(context, content.getSender()))) { + database.remove(id, Address.fromExternal(context, content.getSender())); if (outgoing) database.setActive(id, false); - return storeMessage(context, envelope, group, builder.build(), outgoing); + return storeMessage(context, content, group, builder.build(), outgoing); } return null; @@ -198,7 +197,7 @@ public class GroupMessageProcessor { private static @Nullable Long storeMessage(@NonNull Context context, - @NonNull SignalServiceEnvelope envelope, + @NonNull SignalServiceContent content, @NonNull SignalServiceGroup group, @NonNull GroupContext storage, boolean outgoing) @@ -213,7 +212,7 @@ public class GroupMessageProcessor { MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false)); Recipient recipient = Recipient.from(context, addres, false); - OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, envelope.getTimestamp(), 0, null, Collections.emptyList()); + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, null, Collections.emptyList()); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null); @@ -223,7 +222,7 @@ public class GroupMessageProcessor { } else { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); String body = Base64.encodeBytes(storage.toByteArray()); - IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group), 0); + IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt()); IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); Optional insertResult = smsDatabase.insertMessageInbox(groupMessage); diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index 463bcd822b..f35e78ac92 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -193,7 +193,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable Log.i(TAG, "Downloading attachment with no digest..."); } - return new SignalServiceAttachmentPointer(id, null, key, relay, + return new SignalServiceAttachmentPointer(id, null, key, Optional.of(Util.toIntExact(attachment.getSize())), Optional.absent(), 0, 0, diff --git a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java index 3a07f1f079..3d38cb7c1d 100644 --- a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -98,7 +98,7 @@ public class AvatarDownloadJob extends MasterSecretJob implements InjectableType attachment = File.createTempFile("avatar", "tmp", context.getCacheDir()); attachment.deleteOnExit(); - SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, relay, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false); + SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false); InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE); Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500); diff --git a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index 6b4b48a821..358dcd2d9a 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -252,7 +252,7 @@ public class MmsDownloadJob extends MasterSecretJob { group = Optional.of(Address.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), true))); } - IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false); + IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false, false); Optional insertResult = database.insertMessageInbox(message, contentLocation, threadId); if (insertResult.isPresent()) { diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java index 25c7f70ab2..11cda46ec5 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java @@ -4,6 +4,7 @@ import android.content.Context; import android.support.annotation.NonNull; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientReader; @@ -30,6 +31,7 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje private static final long serialVersionUID = 1L; + @SuppressWarnings("unused") private static final String TAG = MultiDeviceBlockedUpdateJob.class.getSimpleName(); @Inject transient SignalServiceMessageSender messageSender; @@ -75,7 +77,8 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje } } - messageSender.sendMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups))); + messageSender.sendMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups)), + UnidentifiedAccessUtil.getAccessForSync(context)); } } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 7b56daa856..0f5735375a 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; @@ -238,7 +239,8 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje .build(); try { - messageSender.sendMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, complete))); + messageSender.sendMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, complete)), + UnidentifiedAccessUtil.getAccessForSync(context)); } catch (IOException ioe) { throw new NetworkException(ioe); } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java index ca7de5f49d..32442e289d 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java @@ -5,6 +5,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -131,7 +132,8 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject .withLength(contactsFile.length()) .build(); - messageSender.sendMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); + messageSender.sendMessage(SignalServiceSyncMessage.forGroups(attachmentStream), + UnidentifiedAccessUtil.getAccessForSync(context)); } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java index 270e22653d..39873b6aed 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java @@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -86,7 +87,7 @@ public class MultiDeviceProfileKeyUpdateJob extends MasterSecretJob implements I SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false)); - messageSender.sendMessage(syncMessage); + messageSender.sendMessage(syncMessage, UnidentifiedAccessUtil.getAccessForSync(context)); } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadReceiptUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadReceiptUpdateJob.java index 9aafd3a1b3..cb0cddb2ec 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadReceiptUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadReceiptUpdateJob.java @@ -4,6 +4,7 @@ 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; @@ -58,7 +59,8 @@ public class MultiDeviceReadReceiptUpdateJob extends ContextJob implements Injec @Override public void onRun() throws IOException, UntrustedIdentityException { - messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(enabled)))); + messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(enabled))), + UnidentifiedAccessUtil.getAccessForSync(context)); } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java index a2cf7fafd0..a54fa393a3 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java @@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.JobParameters; @@ -100,7 +101,8 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp)); } - messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages)); + 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 0a3c7ed709..e14b76e3c8 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java @@ -7,11 +7,14 @@ import android.support.annotation.NonNull; import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; @@ -101,7 +104,8 @@ public class MultiDeviceVerifiedUpdateJob extends ContextJob implements Injectab VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus); VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination.toPhoneString(), new IdentityKey(identityKey, 0), verifiedState, timestamp); - messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); + messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage), + UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(destination), false))); } catch (InvalidKeyException e) { throw new IOException(e); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 11d4606bd0..30ff8a290a 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.jobs; +import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -7,11 +8,18 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; - -import org.thoughtcrime.securesms.jobmanager.SafeData; -import org.thoughtcrime.securesms.logging.Log; import android.util.Pair; +import org.signal.libsignal.metadata.InvalidMetadataMessageException; +import org.signal.libsignal.metadata.InvalidMetadataVersionException; +import org.signal.libsignal.metadata.ProtocolDuplicateMessageException; +import org.signal.libsignal.metadata.ProtocolInvalidKeyException; +import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException; +import org.signal.libsignal.metadata.ProtocolInvalidMessageException; +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.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.R; @@ -22,6 +30,7 @@ import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.ContactModelMapper; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.SecurityEvent; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.Address; @@ -40,6 +49,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.groups.GroupMessageProcessor; import org.thoughtcrime.securesms.jobmanager.JobParameters; +import org.thoughtcrime.securesms.jobmanager.SafeData; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; @@ -53,25 +64,13 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; -import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage; import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; -import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.libsignal.DuplicateMessageException; -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.InvalidKeyIdException; -import org.whispersystems.libsignal.InvalidMessageException; -import org.whispersystems.libsignal.InvalidVersionException; -import org.whispersystems.libsignal.LegacyMessageException; -import org.whispersystems.libsignal.NoSessionException; -import org.whispersystems.libsignal.UntrustedIdentityException; -import org.whispersystems.libsignal.protocol.PreKeySignalMessage; import org.whispersystems.libsignal.state.SessionStore; import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.guava.Optional; @@ -212,11 +211,11 @@ public class PushDecryptJob extends ContextJob { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context); SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context)); - SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore); + SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, UnidentifiedAccessUtil.getCertificateValidator()); SignalServiceContent content = cipher.decrypt(envelope); - if (shouldIgnore(envelope, content)) { + if (shouldIgnore(content)) { Log.i(TAG, "Ignoring message."); return; } @@ -225,41 +224,45 @@ public class PushDecryptJob extends ContextJob { SignalServiceDataMessage message = content.getDataMessage().get(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent(); - if (message.isEndSession()) handleEndSessionMessage(envelope, message, smsMessageId); - else if (message.isGroupUpdate()) handleGroupMessage(envelope, message, smsMessageId); - else if (message.isExpirationUpdate()) handleExpirationUpdate(envelope, message, smsMessageId); - else if (isMediaMessage) handleMediaMessage(envelope, message, smsMessageId); - else if (message.getBody().isPresent()) handleTextMessage(envelope, message, smsMessageId); + if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId); + else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId); + else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId); + else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId); + else if (message.getBody().isPresent()) handleTextMessage(content, message, smsMessageId); if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) { - handleUnknownGroupMessage(envelope, message.getGroupInfo().get()); + handleUnknownGroupMessage(content, message.getGroupInfo().get()); } if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { - handleProfileKey(envelope, message); + handleProfileKey(content, message); + } + + if (content.isNeedsReceipt()) { + handleNeedsDeliveryReceipt(content, message); } } else if (content.getSyncMessage().isPresent()) { SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); - if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(envelope, syncMessage.getSent().get()); + if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get()); else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get()); - else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), envelope.getTimestamp()); + else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp()); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else Log.w(TAG, "Contains no known sync types..."); } else if (content.getCallMessage().isPresent()) { Log.i(TAG, "Got call message..."); SignalServiceCallMessage message = content.getCallMessage().get(); - if (message.getOfferMessage().isPresent()) handleCallOfferMessage(envelope, message.getOfferMessage().get(), smsMessageId); - else if (message.getAnswerMessage().isPresent()) handleCallAnswerMessage(envelope, message.getAnswerMessage().get()); - else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(envelope, message.getIceUpdateMessages().get()); - else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(envelope, message.getHangupMessage().get(), smsMessageId); - else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(envelope, message.getBusyMessage().get()); + if (message.getOfferMessage().isPresent()) handleCallOfferMessage(content, message.getOfferMessage().get(), smsMessageId); + else if (message.getAnswerMessage().isPresent()) handleCallAnswerMessage(content, message.getAnswerMessage().get()); + else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(content, message.getIceUpdateMessages().get()); + else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(content, message.getHangupMessage().get(), smsMessageId); + else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(content, message.getBusyMessage().get()); } else if (content.getReceiptMessage().isPresent()) { SignalServiceReceiptMessage message = content.getReceiptMessage().get(); - if (message.isReadReceipt()) handleReadReceipt(envelope, message); - else if (message.isDeliveryReceipt()) handleDeliveryReceipt(envelope, message); + if (message.isReadReceipt()) handleReadReceipt(content, message); + else if (message.isDeliveryReceipt()) handleDeliveryReceipt(content, message); } else { Log.w(TAG, "Got unrecognized message..."); } @@ -267,28 +270,30 @@ public class PushDecryptJob extends ContextJob { if (envelope.isPreKeySignalMessage()) { ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context)); } - } catch (InvalidVersionException e) { + } catch (ProtocolInvalidVersionException e) { Log.w(TAG, e); - handleInvalidVersionMessage(envelope, smsMessageId); - } catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException e) { + handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); + } catch (ProtocolInvalidMessageException | ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) { Log.w(TAG, e); - handleCorruptMessage(envelope, smsMessageId); - } catch (NoSessionException e) { + handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); + } catch (StorageFailedException e) { Log.w(TAG, e); - handleNoSessionMessage(envelope, smsMessageId); - } catch (LegacyMessageException e) { + handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); + } catch (ProtocolNoSessionException e) { Log.w(TAG, e); - handleLegacyMessage(envelope, smsMessageId); - } catch (DuplicateMessageException e) { + handleNoSessionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); + } catch (ProtocolLegacyMessageException e) { Log.w(TAG, e); - handleDuplicateMessage(envelope, smsMessageId); - } catch (UntrustedIdentityException e) { + handleLegacyMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); + } catch (ProtocolDuplicateMessageException e) { + Log.w(TAG, e); + handleDuplicateMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); + } catch (InvalidMetadataVersionException | InvalidMetadataMessageException e) { Log.w(TAG, e); - handleUntrustedIdentityMessage(envelope, smsMessageId); } } - private void handleCallOfferMessage(@NonNull SignalServiceEnvelope envelope, + private void handleCallOfferMessage(@NonNull SignalServiceContent content, @NonNull OfferMessage message, @NonNull Optional smsMessageId) { @@ -301,29 +306,29 @@ public class PushDecryptJob extends ContextJob { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_INCOMING_CALL); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription()); - intent.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, envelope.getTimestamp()); + intent.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, content.getTimestamp()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(intent); else context.startService(intent); } } - private void handleCallAnswerMessage(@NonNull SignalServiceEnvelope envelope, + private void handleCallAnswerMessage(@NonNull SignalServiceContent content, @NonNull AnswerMessage message) { Log.i(TAG, "handleCallAnswerMessage..."); Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_RESPONSE_MESSAGE); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription()); context.startService(intent); } - private void handleCallIceUpdateMessage(@NonNull SignalServiceEnvelope envelope, + private void handleCallIceUpdateMessage(@NonNull SignalServiceContent content, @NonNull List messages) { Log.w(TAG, "handleCallIceUpdateMessage... " + messages.size()); @@ -331,7 +336,7 @@ public class PushDecryptJob extends ContextJob { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_ICE_MESSAGE); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP, message.getSdp()); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_MID, message.getSdpMid()); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_LINE_INDEX, message.getSdpMLineIndex()); @@ -340,7 +345,7 @@ public class PushDecryptJob extends ContextJob { } } - private void handleCallHangupMessage(@NonNull SignalServiceEnvelope envelope, + private void handleCallHangupMessage(@NonNull SignalServiceContent content, @NonNull HangupMessage message, @NonNull Optional smsMessageId) { @@ -351,32 +356,32 @@ public class PushDecryptJob extends ContextJob { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_REMOTE_HANGUP); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); context.startService(intent); } } - private void handleCallBusyMessage(@NonNull SignalServiceEnvelope envelope, + private void handleCallBusyMessage(@NonNull SignalServiceContent content, @NonNull BusyMessage message) { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(WebRtcCallService.ACTION_REMOTE_BUSY); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); - intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); + intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender())); context.startService(intent); } - private void handleEndSessionMessage(@NonNull SignalServiceEnvelope envelope, - @NonNull SignalServiceDataMessage message, - @NonNull Optional smsMessageId) + private void handleEndSessionMessage(@NonNull SignalServiceContent content, + @NonNull Optional smsMessageId) { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); - IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), - envelope.getSourceDevice(), - message.getTimestamp(), - "", Optional.absent(), 0); + IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), + content.getSenderDevice(), + content.getTimestamp(), + "", Optional.absent(), 0, + content.isNeedsReceipt()); Long threadId; @@ -393,7 +398,7 @@ public class PushDecryptJob extends ContextJob { if (threadId != null) { SessionStore sessionStore = new TextSecureSessionStore(context); - sessionStore.deleteAllSessions(envelope.getSource()); + sessionStore.deleteAllSessions(content.getSender()); SecurityEvent.broadcastSecurityUpdateEvent(context); MessageNotifier.updateNotification(context, threadId); @@ -424,15 +429,15 @@ public class PushDecryptJob extends ContextJob { return threadId; } - private void handleGroupMessage(@NonNull SignalServiceEnvelope envelope, + private void handleGroupMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) - throws MmsException + throws StorageFailedException { - GroupMessageProcessor.process(context, envelope, message, false); + GroupMessageProcessor.process(context, content, message, false); - if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(envelope, message).getExpireMessages()) { - handleExpirationUpdate(envelope, message, Optional.absent()); + if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) { + handleExpirationUpdate(content, message, Optional.absent()); } if (smsMessageId.isPresent()) { @@ -440,36 +445,41 @@ public class PushDecryptJob extends ContextJob { } } - private void handleUnknownGroupMessage(@NonNull SignalServiceEnvelope envelope, + private void handleUnknownGroupMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceGroup group) { ApplicationContext.getInstance(context) .getJobManager() - .add(new RequestGroupInfoJob(context, envelope.getSource(), group.getGroupId())); + .add(new RequestGroupInfoJob(context, content.getSender(), group.getGroupId())); } - private void handleExpirationUpdate(@NonNull SignalServiceEnvelope envelope, + private void handleExpirationUpdate(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) - throws MmsException + throws StorageFailedException { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Recipient recipient = getMessageDestination(envelope, message); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()), - message.getTimestamp(), -1, - message.getExpiresInSeconds() * 1000L, true, - Optional.fromNullable(envelope.getRelay()), - Optional.absent(), message.getGroupInfo(), - Optional.absent(), Optional.absent(), Optional.absent()); + try { + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + Recipient recipient = getMessageDestination(content, message); + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), + message.getTimestamp(), -1, + message.getExpiresInSeconds() * 1000L, true, + content.isNeedsReceipt(), + Optional.absent(), + message.getGroupInfo(), + Optional.absent(), + Optional.absent(), + Optional.absent()); + database.insertSecureDecryptedMessageInbox(mediaMessage, -1); + DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, message.getExpiresInSeconds()); - database.insertSecureDecryptedMessageInbox(mediaMessage, -1); - - DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, message.getExpiresInSeconds()); - - if (smsMessageId.isPresent()) { - DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); + if (smsMessageId.isPresent()) { + DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); + } + } catch (MmsException e) { + throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); } } @@ -477,48 +487,53 @@ public class PushDecryptJob extends ContextJob { IdentityUtil.processVerifiedMessage(context, verifiedMessage); } - private void handleSynchronizeSentMessage(@NonNull SignalServiceEnvelope envelope, + private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, @NonNull SentTranscriptMessage message) - throws MmsException + throws StorageFailedException + { - GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); + try { + GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - Long threadId; + Long threadId; - if (message.getMessage().isEndSession()) { - threadId = handleSynchronizeSentEndSessionMessage(message); - } else if (message.getMessage().isGroupUpdate()) { - threadId = GroupMessageProcessor.process(context, envelope, message.getMessage(), true); - } else if (message.getMessage().isExpirationUpdate()) { - threadId = handleSynchronizeSentExpirationUpdate(message); - } else if (message.getMessage().getAttachments().isPresent() || message.getMessage().getQuote().isPresent()) { - threadId = handleSynchronizeSentMediaMessage(message); - } else { - threadId = handleSynchronizeSentTextMessage(message); - } - - if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false))) { - handleUnknownGroupMessage(envelope, message.getMessage().getGroupInfo().get()); - } - - if (message.getMessage().getProfileKey().isPresent()) { - Recipient recipient = null; - - if (message.getDestination().isPresent()) recipient = Recipient.from(context, Address.fromExternal(context, message.getDestination().get()), false); - else if (message.getMessage().getGroupInfo().isPresent()) recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)), false); - - - if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); + if (message.getMessage().isEndSession()) { + threadId = handleSynchronizeSentEndSessionMessage(message); + } else if (message.getMessage().isGroupUpdate()) { + threadId = GroupMessageProcessor.process(context, content, message.getMessage(), true); + } else if (message.getMessage().isExpirationUpdate()) { + threadId = handleSynchronizeSentExpirationUpdate(message); + } else if (message.getMessage().getAttachments().isPresent() || message.getMessage().getQuote().isPresent()) { + threadId = handleSynchronizeSentMediaMessage(message); + } else { + threadId = handleSynchronizeSentTextMessage(message); } - } - if (threadId != null) { - DatabaseFactory.getThreadDatabase(getContext()).setRead(threadId, true); - MessageNotifier.updateNotification(getContext()); - } + if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false))) { + handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get()); + } - MessageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp()); + if (message.getMessage().getProfileKey().isPresent()) { + Recipient recipient = null; + + if (message.getDestination().isPresent()) recipient = Recipient.from(context, Address.fromExternal(context, message.getDestination().get()), false); + else if (message.getMessage().getGroupInfo().isPresent()) recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)), false); + + + if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) { + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); + } + } + + if (threadId != null) { + DatabaseFactory.getThreadDatabase(getContext()).setRead(threadId, true); + MessageNotifier.updateNotification(getContext()); + } + + MessageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp()); + } catch (MmsException e) { + throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); + } } private void handleSynchronizeRequestMessage(@NonNull RequestMessage message) @@ -572,51 +587,48 @@ public class PushDecryptJob extends ContextJob { MessageNotifier.updateNotification(context); } - private void handleMediaMessage(@NonNull SignalServiceEnvelope envelope, + private void handleMediaMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) - throws MmsException + throws StorageFailedException { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Recipient recipient = getMessageDestination(envelope, message); - Optional quote = getValidatedQuote(message.getQuote()); - Optional> sharedContacts = getContacts(message.getSharedContacts()); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()), - message.getTimestamp(), -1, - message.getExpiresInSeconds() * 1000L, false, - Optional.fromNullable(envelope.getRelay()), - message.getBody(), - message.getGroupInfo(), - message.getAttachments(), - quote, - sharedContacts); + try { + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + Optional quote = getValidatedQuote(message.getQuote()); + Optional> sharedContacts = getContacts(message.getSharedContacts()); + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), + message.getTimestamp(), -1, + message.getExpiresInSeconds() * 1000L, false, + content.isNeedsReceipt(), + message.getBody(), + message.getGroupInfo(), + message.getAttachments(), + quote, + sharedContacts); - if (message.getExpiresInSeconds() != recipient.getExpireMessages()) { - handleExpirationUpdate(envelope, message, Optional.absent()); - } + Optional insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1); - Optional insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1); + if (insertResult.isPresent()) { + List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId()); - if (insertResult.isPresent()) { - List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId()); + for (DatabaseAttachment attachment : attachments) { + ApplicationContext.getInstance(context) + .getJobManager() + .add(new AttachmentDownloadJob(context, insertResult.get().getMessageId(), attachment.getAttachmentId(), false)); + } - for (DatabaseAttachment attachment : attachments) { - ApplicationContext.getInstance(context) - .getJobManager() - .add(new AttachmentDownloadJob(context, insertResult.get().getMessageId(), attachment.getAttachmentId(), false)); + if (smsMessageId.isPresent()) { + DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); + } + + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } - - if (smsMessageId.isPresent()) { - DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); - } - - MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); + } catch (MmsException e) { + throw new StorageFailedException(e, content.getSender(), content.getSenderDevice()); } } - private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) - throws MmsException - { + private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); Recipient recipient = getSyncMessageDestination(message); @@ -646,7 +658,8 @@ public class PushDecryptJob extends ContextJob { message.getTimestamp(), -1, message.getMessage().getExpiresInSeconds() * 1000, ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(), - sharedContacts.or(Collections.emptyList())); + sharedContacts.or(Collections.emptyList()), + Collections.emptyList(), Collections.emptyList()); mediaMessage = new OutgoingSecureMediaMessage(mediaMessage); @@ -677,17 +690,17 @@ public class PushDecryptJob extends ContextJob { return threadId; } - private void handleTextMessage(@NonNull SignalServiceEnvelope envelope, + private void handleTextMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) - throws MmsException + throws StorageFailedException { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); String body = message.getBody().isPresent() ? message.getBody().get() : ""; - Recipient recipient = getMessageDestination(envelope, message); + Recipient recipient = getMessageDestination(content, message); if (message.getExpiresInSeconds() != recipient.getExpireMessages()) { - handleExpirationUpdate(envelope, message, Optional.absent()); + handleExpirationUpdate(content, message, Optional.absent()); } Long threadId; @@ -695,11 +708,12 @@ public class PushDecryptJob extends ContextJob { if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) { threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second; } else { - IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), - envelope.getSourceDevice(), + IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), + content.getSenderDevice(), message.getTimestamp(), body, message.getGroupInfo(), - message.getExpiresInSeconds() * 1000L); + message.getExpiresInSeconds() * 1000L, + content.isNeedsReceipt()); textMessage = new IncomingEncryptedMessage(textMessage, body); Optional insertResult = database.insertMessageInbox(textMessage); @@ -758,13 +772,13 @@ public class PushDecryptJob extends ContextJob { return threadId; } - private void handleInvalidVersionMessage(@NonNull SignalServiceEnvelope envelope, + private void handleInvalidVersionMessage(@NonNull String sender, int senderDevice, long timestamp, @NonNull Optional smsMessageId) { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { - Optional insertResult = insertPlaceholder(envelope); + Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp); if (insertResult.isPresent()) { smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId()); @@ -775,13 +789,13 @@ public class PushDecryptJob extends ContextJob { } } - private void handleCorruptMessage(@NonNull SignalServiceEnvelope envelope, + private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp, @NonNull Optional smsMessageId) { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { - Optional insertResult = insertPlaceholder(envelope); + Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp); if (insertResult.isPresent()) { smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId()); @@ -792,13 +806,13 @@ public class PushDecryptJob extends ContextJob { } } - private void handleNoSessionMessage(@NonNull SignalServiceEnvelope envelope, + private void handleNoSessionMessage(@NonNull String sender, int senderDevice, long timestamp, @NonNull Optional smsMessageId) { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { - Optional insertResult = insertPlaceholder(envelope); + Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp); if (insertResult.isPresent()) { smsDatabase.markAsNoSession(insertResult.get().getMessageId()); @@ -809,13 +823,13 @@ public class PushDecryptJob extends ContextJob { } } - private void handleLegacyMessage(@NonNull SignalServiceEnvelope envelope, + private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp, @NonNull Optional smsMessageId) { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { - Optional insertResult = insertPlaceholder(envelope); + Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp); if (insertResult.isPresent()) { smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId()); @@ -827,7 +841,7 @@ public class PushDecryptJob extends ContextJob { } @SuppressWarnings("unused") - private void handleDuplicateMessage(@NonNull SignalServiceEnvelope envelope, + private void handleDuplicateMessage(@NonNull String sender, int senderDeviceId, long timestamp, @NonNull Optional smsMessageId) { // Let's start ignoring these now @@ -842,45 +856,11 @@ public class PushDecryptJob extends ContextJob { // } } - private void handleUntrustedIdentityMessage(@NonNull SignalServiceEnvelope envelope, - @NonNull Optional smsMessageId) - { - try { - SmsDatabase database = DatabaseFactory.getSmsDatabase(context); - Address sourceAddress = Address.fromExternal(context, envelope.getSource()); - byte[] serialized = envelope.hasLegacyMessage() ? envelope.getLegacyMessage() : envelope.getContent(); - PreKeySignalMessage whisperMessage = new PreKeySignalMessage(serialized); - IdentityKey identityKey = whisperMessage.getIdentityKey(); - String encoded = Base64.encodeBytes(serialized); - - IncomingTextMessage textMessage = new IncomingTextMessage(sourceAddress, - envelope.getSourceDevice(), - envelope.getTimestamp(), encoded, - Optional.absent(), 0); - - if (!smsMessageId.isPresent()) { - IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded, envelope.hasLegacyMessage()); - Optional insertResult = database.insertMessageInbox(bundleMessage); - - if (insertResult.isPresent()) { - database.setMismatchedIdentity(insertResult.get().getMessageId(), sourceAddress, identityKey); - MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); - } - } else { - database.updateMessageBody(smsMessageId.get(), encoded); - database.markAsPreKeyBundle(smsMessageId.get()); - database.setMismatchedIdentity(smsMessageId.get(), sourceAddress, identityKey); - } - } catch (InvalidMessageException | InvalidVersionException e) { - throw new AssertionError(e); - } - } - - private void handleProfileKey(@NonNull SignalServiceEnvelope envelope, + private void handleProfileKey(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); - Address sourceAddress = Address.fromExternal(context, envelope.getSource()); + Address sourceAddress = Address.fromExternal(context, content.getSender()); Recipient recipient = Recipient.from(context, sourceAddress, false); if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) { @@ -889,17 +869,27 @@ public class PushDecryptJob extends ContextJob { } } - private void handleDeliveryReceipt(@NonNull SignalServiceEnvelope envelope, + private void handleNeedsDeliveryReceipt(@NonNull SignalServiceContent content, + @NonNull SignalServiceDataMessage message) + { + ApplicationContext.getInstance(context) + .getJobManager() + .add(new SendDeliveryReceiptJob(context, Address.fromExternal(context, content.getSender()), message.getTimestamp())); + } + + @SuppressLint("DefaultLocale") + private void handleDeliveryReceipt(@NonNull SignalServiceContent content, @NonNull SignalServiceReceiptMessage message) { for (long timestamp : message.getTimestamps()) { Log.i(TAG, String.format("Received encrypted delivery receipt: (XXXXX, %d)", timestamp)); DatabaseFactory.getMmsSmsDatabase(context) - .incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp), System.currentTimeMillis()); + .incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, content.getSender()), timestamp), System.currentTimeMillis()); } } - private void handleReadReceipt(@NonNull SignalServiceEnvelope envelope, + @SuppressLint("DefaultLocale") + private void handleReadReceipt(@NonNull SignalServiceContent content, @NonNull SignalServiceReceiptMessage message) { if (TextSecurePreferences.isReadReceiptsEnabled(context)) { @@ -907,7 +897,7 @@ public class PushDecryptJob extends ContextJob { Log.i(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp)); DatabaseFactory.getMmsSmsDatabase(context) - .incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp), envelope.getTimestamp()); + .incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, content.getSender()), timestamp), content.getTimestamp()); } } } @@ -960,12 +950,11 @@ public class PushDecryptJob extends ContextJob { return Optional.of(contacts); } - private Optional insertPlaceholder(@NonNull SignalServiceEnvelope envelope) { + private Optional insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); - IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), - envelope.getSourceDevice(), - envelope.getTimestamp(), "", - Optional.absent(), 0); + IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, sender), + senderDevice, timestamp, "", + Optional.absent(), 0, false); textMessage = new IncomingEncryptedMessage(textMessage, ""); return database.insertMessageInbox(textMessage); @@ -979,21 +968,20 @@ public class PushDecryptJob extends ContextJob { } } - - private Recipient getMessageDestination(SignalServiceEnvelope envelope, SignalServiceDataMessage message) { + private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) { if (message.getGroupInfo().isPresent()) { return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false); } else { - return Recipient.from(context, Address.fromExternal(context, envelope.getSource()), false); + return Recipient.from(context, Address.fromExternal(context, content.getSender()), false); } } - private boolean shouldIgnore(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) { - Recipient sender = Recipient.from(context, Address.fromExternal(context, envelope.getSource()), false); + private boolean shouldIgnore(@NonNull SignalServiceContent content) { + Recipient sender = Recipient.from(context, Address.fromExternal(context, content.getSender()), false); if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); - Recipient conversation = getMessageDestination(envelope, message); + Recipient conversation = getMessageDestination(content, message); if (conversation.isGroupRecipient() && conversation.isBlocked()) { return true; @@ -1023,4 +1011,25 @@ public class PushDecryptJob extends ContextJob { return false; } + + @SuppressWarnings("WeakerAccess") + private static class StorageFailedException extends Exception { + private final String sender; + private final int senderDevice; + + private StorageFailedException(Exception e, String sender, int senderDevice) { + super(e); + this.sender = sender; + this.senderDevice = senderDevice; + } + + public String getSender() { + return sender; + } + + public int getSenderDevice() { + return senderDevice; + } + } + } diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index d4c97f2d21..e6d94415c5 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -4,15 +4,18 @@ import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; +import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.JobParameters; @@ -24,25 +27,27 @@ import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.util.GroupUtil; 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.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Quote; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; -import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import java.io.IOException; -import java.util.LinkedList; +import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -96,47 +101,56 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { @Override public void onPushSend() - throws MmsException, IOException, NoSuchMessageException + throws IOException, MmsException, NoSuchMessageException, RetryLaterException { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - OutgoingMediaMessage message = database.getOutgoingMessage(messageId); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + OutgoingMediaMessage message = database.getOutgoingMessage(messageId); + List existingNetworkFailures = message.getNetworkFailures(); + List existingIdentityMismatches = message.getIdentityKeyMismatches(); try { Log.i(TAG, "Sending message: " + messageId); + List
target; - deliver(message, filterAddress == null ? null : Address.fromSerialized(filterAddress)); + if (filterAddress != null) target = Collections.singletonList(Address.fromSerialized(filterAddress)); + else if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(NetworkFailure::getAddress).toList(); + else target = getGroupMessageRecipients(message.getRecipient().getAddress().toGroupString(), messageId); - database.markAsSent(messageId, true); - markAttachmentsUploaded(messageId, message.getAttachments()); + List results = deliver(message, target); + List networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Address.fromSerialized(result.getAddress().getNumber()))).toList(); + List identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Address.fromSerialized(result.getAddress().getNumber()), result.getIdentityFailure().getIdentityKey())).toList(); + Set
successAddresses = Stream.of(results).filter(result -> result.getSuccess() != null).map(result -> Address.fromSerialized(result.getAddress().getNumber())).collect(Collectors.toSet()); + List resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successAddresses.contains(failure.getAddress())).toList(); + List resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successAddresses.contains(failure.getAddress())).toList(); + List successes = Stream.of(results).filter(result -> result.getSuccess() != null).toList(); - if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { - database.markExpireStarted(messageId); - ApplicationContext.getInstance(context) - .getExpiringMessageManager() - .scheduleDeletion(messageId, true, message.getExpiresIn()); + for (NetworkFailure resolvedFailure : resolvedNetworkFailures) { + database.removeFailure(messageId, resolvedFailure); + existingNetworkFailures.remove(resolvedFailure); } - Log.i(TAG, "Sent message: " + messageId); - } catch (InvalidNumberException | RecipientFormattingException | UndeliverableMessageException e) { - Log.w(TAG, e); - database.markAsSentFailed(messageId); - notifyMediaMessageDeliveryFailed(context, messageId); - } catch (EncapsulatedExceptions e) { - Log.w(TAG, e); - List failures = new LinkedList<>(); - - for (NetworkFailureException nfe : e.getNetworkExceptions()) { - failures.add(new NetworkFailure(Address.fromSerialized(nfe.getE164number()))); + for (IdentityKeyMismatch resolvedIdentity : resolvedIdentityFailures) { + database.removeMismatchedIdentity(messageId, resolvedIdentity.getAddress(), resolvedIdentity.getIdentityKey()); + existingIdentityMismatches.remove(resolvedIdentity); } - for (UntrustedIdentityException uie : e.getUntrustedIdentityExceptions()) { - database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey()); + if (!networkFailures.isEmpty()) { + database.addFailures(messageId, networkFailures); } - database.addFailures(messageId, failures); + for (IdentityKeyMismatch mismatch : identityMismatches) { + database.addMismatchedIdentity(messageId, mismatch.getAddress(), mismatch.getIdentityKey()); + } - if (e.getNetworkExceptions().isEmpty() && e.getUntrustedIdentityExceptions().isEmpty()) { + for (SendMessageResult success : successes) { + DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Address.fromSerialized(success.getAddress().getNumber()), + messageId, + success.getSuccess().isUnidentified()); + } + + if (existingNetworkFailures.isEmpty() && networkFailures.isEmpty() && identityMismatches.isEmpty() && existingIdentityMismatches.isEmpty()) { database.markAsSent(messageId, true); + markAttachmentsUploaded(messageId, message.getAttachments()); if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { @@ -145,16 +159,28 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { .getExpiringMessageManager() .scheduleDeletion(messageId, true, message.getExpiresIn()); } - } else { + } else if (!networkFailures.isEmpty()) { + throw new RetryLaterException(); + } else if (!identityMismatches.isEmpty()) { database.markAsSentFailed(messageId); notifyMediaMessageDeliveryFailed(context, messageId); } + + } catch (InvalidNumberException | RecipientFormattingException | UndeliverableMessageException e) { + Log.w(TAG, e); + database.markAsSentFailed(messageId); + notifyMediaMessageDeliveryFailed(context, messageId); + } catch (UntrustedIdentityException e) { + Log.w(TAG, e); + database.markAsSentFailed(messageId); + notifyMediaMessageDeliveryFailed(context, messageId); } } @Override public boolean onShouldRetryThrowable(Exception exception) { - if (exception instanceof IOException) return true; + if (exception instanceof IOException) return true; + if (exception instanceof RetryLaterException) return true; return false; } @@ -163,23 +189,24 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); } - private void deliver(OutgoingMediaMessage message, @Nullable Address filterAddress) + private List deliver(OutgoingMediaMessage message, @NonNull List
destinations) throws IOException, RecipientFormattingException, InvalidNumberException, - EncapsulatedExceptions, UndeliverableMessageException + UndeliverableMessageException, UntrustedIdentityException { String groupId = message.getRecipient().getAddress().toGroupString(); Optional profileKey = getProfileKey(message.getRecipient()); - List
recipients = getGroupMessageRecipients(groupId, messageId); MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); List scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments()); List attachmentStreams = getAttachmentsFor(scaledAttachments); Optional quote = getQuoteFor(message); List sharedContacts = getSharedContactsFor(message); + List addresses = Stream.of(destinations).map(this::getPushAddress).toList(); - List addresses; - - if (filterAddress != null) addresses = getPushAddresses(filterAddress); - else addresses = getPushAddresses(recipients); + List> unidentifiedAccess = Stream.of(addresses) + .map(address -> Address.fromSerialized(address.getNumber())) + .map(address -> Recipient.from(context, address, false)) + .map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient)) + .toList(); if (message.isGroup()) { OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message; @@ -193,7 +220,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { .asGroupMessage(group) .build(); - messageSender.sendMessage(addresses, groupDataMessage); + return messageSender.sendMessage(addresses, unidentifiedAccess, groupDataMessage); } else { SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedId(groupId)); SignalServiceDataMessage groupMessage = SignalServiceDataMessage.newBuilder() @@ -208,20 +235,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { .withSharedContacts(sharedContacts) .build(); - messageSender.sendMessage(addresses, groupMessage); + return messageSender.sendMessage(addresses, unidentifiedAccess, groupMessage); } } - private List getPushAddresses(Address address) { - List addresses = new LinkedList<>(); - addresses.add(getPushAddress(address)); - return addresses; - } - - private List getPushAddresses(List
addresses) { - return Stream.of(addresses).map(this::getPushAddress).toList(); - } - private @NonNull List
getGroupMessageRecipients(String groupId, long messageId) { List destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId); if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getAddress).toList(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java index c9cf823031..12afebc2c2 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java @@ -4,15 +4,15 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.support.annotation.NonNull; -import org.thoughtcrime.securesms.jobmanager.SafeData; -import org.thoughtcrime.securesms.logging.Log; - +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; 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.recipients.Recipient; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; @@ -122,7 +122,9 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType { .withExpiration(groupRecipient.getExpireMessages()) .build(); - messageSender.sendMessage(new SignalServiceAddress(source), message); + messageSender.sendMessage(new SignalServiceAddress(source), + UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(source), false)), + message); } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 940d8dfb3e..f237a801cd 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -3,16 +3,16 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.support.annotation.NonNull; -import org.thoughtcrime.securesms.jobmanager.SafeData; -import org.thoughtcrime.securesms.logging.Log; - import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; 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.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; @@ -80,9 +80,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { try { Log.i(TAG, "Sending message: " + messageId); - deliver(message); + boolean unidentified = deliver(message); database.markAsSent(messageId, true); markAttachmentsUploaded(messageId, message.getAttachments()); + database.markUnidentified(messageId, unidentified); if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { database.markExpireStarted(messageId); @@ -117,7 +118,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { notifyMediaMessageDeliveryFailed(context, messageId); } - private void deliver(OutgoingMediaMessage message) + private boolean deliver(OutgoingMediaMessage message) throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException, UndeliverableMessageException { @@ -144,7 +145,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { .asExpirationUpdate(message.isExpirationUpdate()) .build(); - messageSender.sendMessage(address, mediaMessage); + return messageSender.sendMessage(address, UnidentifiedAccessUtil.getAccessFor(context, message.getRecipient()), mediaMessage).getSuccess().isUnidentified(); } catch (UnregisteredUserException e) { Log.w(TAG, e); throw new InsecureFallbackApprovalException(e); diff --git a/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java index da7014550b..6c5141608e 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java @@ -4,15 +4,12 @@ import android.content.Context; import android.support.annotation.NonNull; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.database.NoSuchMessageException; 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.SignalServiceMessageReceiver; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java index 3bb7366780..787edf487e 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.jobs; +import android.annotation.SuppressLint; import android.content.Context; import android.support.annotation.NonNull; @@ -25,17 +26,19 @@ public abstract class PushReceivedJob extends ContextJob { public void processEnvelope(@NonNull SignalServiceEnvelope envelope) { synchronized (RECEIVE_LOCK) { - Address source = Address.fromExternal(context, envelope.getSource()); - Recipient recipient = Recipient.from(context, source, false); + if (envelope.hasSource()) { + Address source = Address.fromExternal(context, envelope.getSource()); + Recipient recipient = Recipient.from(context, source, false); - if (!isActiveNumber(recipient)) { - DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED); - ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, recipient, false)); + if (!isActiveNumber(recipient)) { + DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED); + ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, recipient, false)); + } } if (envelope.isReceipt()) { handleReceipt(envelope); - } else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage()) { + } else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender()) { handleMessage(envelope); } else { Log.w(TAG, "Received envelope of unknown type: " + envelope.getType()); @@ -47,6 +50,7 @@ public abstract class PushReceivedJob extends ContextJob { new PushDecryptJob(context).processMessage(envelope); } + @SuppressLint("DefaultLocale") private void handleReceipt(SignalServiceEnvelope envelope) { Log.i(TAG, String.format("Received receipt: (XXXXX, %d)", envelope.getTimestamp())); DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index a74a77d970..c1a179b387 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -92,7 +92,6 @@ public abstract class PushSendJob extends SendJob { } protected SignalServiceAddress getPushAddress(Address address) { -// String relay = TextSecureDirectory.getInstance(context).getRelay(address.toPhoneString()); String relay = null; return new SignalServiceAddress(address.toPhoneString(), Optional.fromNullable(relay)); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 1076376674..0544479466 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.NoSuchMessageException; @@ -20,7 +21,9 @@ import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.RetryLaterException; 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; @@ -76,8 +79,9 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { try { Log.i(TAG, "Sending message: " + messageId); - deliver(record); + boolean unidentified = deliver(record); database.markAsSent(messageId, true); + database.markUnidentified(messageId, unidentified); if (record.getExpiresIn() > 0) { database.markExpireStarted(messageId); @@ -118,22 +122,25 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { } } - private void deliver(SmsMessageRecord message) + private boolean deliver(SmsMessageRecord message) throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException { try { - SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress()); - Optional profileKey = getProfileKey(message.getIndividualRecipient()); - SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder() - .withTimestamp(message.getDateSent()) - .withBody(message.getBody()) - .withExpiration((int)(message.getExpiresIn() / 1000)) - .withProfileKey(profileKey.orNull()) - .asEndSessionMessage(message.isEndSession()) - .build(); + SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress()); + Optional profileKey = getProfileKey(message.getIndividualRecipient()); + Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, message.getIndividualRecipient()); + Log.w(TAG, "Have access key to use: " + unidentifiedAccess.isPresent()); - messageSender.sendMessage(address, textSecureMessage); + SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder() + .withTimestamp(message.getDateSent()) + .withBody(message.getBody()) + .withExpiration((int)(message.getExpiresIn() / 1000)) + .withProfileKey(profileKey.orNull()) + .asEndSessionMessage(message.isEndSession()) + .build(); + + return messageSender.sendMessage(address, unidentifiedAccess, textSecureMessage).getSuccess().isUnidentified(); } catch (UnregisteredUserException e) { Log.w(TAG, e); throw new InsecureFallbackApprovalException(e); diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java index 70952e881b..c5a41fa82b 100644 --- a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java @@ -8,6 +8,12 @@ import org.thoughtcrime.securesms.logging.Log; 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; @@ -48,12 +54,15 @@ public class RefreshAttributesJob extends ContextJob implements InjectableType { @Override public void onRun() throws IOException { - String signalingKey = TextSecurePreferences.getSignalingKey(context); - int registrationId = TextSecurePreferences.getLocalRegistrationId(context); - boolean fetchesMessages = TextSecurePreferences.isGcmDisabled(context); - String pin = TextSecurePreferences.getRegistrationLockPin(context); + String signalingKey = TextSecurePreferences.getSignalingKey(context); + int registrationId = TextSecurePreferences.getLocalRegistrationId(context); + boolean fetchesMessages = TextSecurePreferences.isGcmDisabled(context); + String pin = TextSecurePreferences.getRegistrationLockPin(context); + byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context); + boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context); - signalAccountManager.setAccountAttributes(signalingKey, registrationId, fetchesMessages, pin); + signalAccountManager.setAccountAttributes(signalingKey, registrationId, fetchesMessages, pin, + unidentifiedAccessKey, universalUnidentifiedAccess); } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java b/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java index 7846d971f7..7bf46f8bc0 100644 --- a/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java @@ -3,10 +3,14 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.support.annotation.NonNull; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; +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.util.GroupUtil; +import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement; +import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; @@ -23,6 +27,7 @@ import androidx.work.Data; public class RequestGroupInfoJob extends ContextJob implements InjectableType { + @SuppressWarnings("unused") private static final String TAG = RequestGroupInfoJob.class.getSimpleName(); private static final long serialVersionUID = 0L; @@ -77,7 +82,9 @@ public class RequestGroupInfoJob extends ContextJob implements InjectableType { .withTimestamp(System.currentTimeMillis()) .build(); - messageSender.sendMessage(new SignalServiceAddress(source), message); + messageSender.sendMessage(new SignalServiceAddress(source), + UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromExternal(context, source), false)), + message); } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java index 37a878986e..e79a12aa9c 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java @@ -5,14 +5,16 @@ import android.content.Context; import android.support.annotation.NonNull; import android.text.TextUtils; -import org.thoughtcrime.securesms.database.Address; -import org.thoughtcrime.securesms.jobmanager.SafeData; -import org.thoughtcrime.securesms.logging.Log; - import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; +import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; 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.recipients.Recipient; import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.util.Base64; @@ -20,11 +22,15 @@ import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; +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.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.util.InvalidNumberException; import java.io.IOException; @@ -88,12 +94,26 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType { private void handleIndividualRecipient(Recipient recipient) throws IOException, InvalidKeyException, InvalidNumberException { - String number = recipient.getAddress().toPhoneString(); - SignalServiceProfile profile = retrieveProfile(number); + String number = recipient.getAddress().toPhoneString(); + Optional unidentifiedAccess = getUnidentifiedAccess(recipient); + + SignalServiceProfile profile; + + try { + profile = retrieveProfile(number, unidentifiedAccess); + } catch (AuthorizationFailedException e) { + if (unidentifiedAccess.isPresent()) { + // XXX Update UI + profile = retrieveProfile(number, Optional.absent()); + } else { + throw e; + } + } setIdentityKey(recipient, profile.getIdentityKey()); setProfileName(recipient, profile.getName()); setProfileAvatar(recipient, profile.getAvatar()); + setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess()); } private void handleGroupRecipient(Recipient group) @@ -106,18 +126,20 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType { } } - private SignalServiceProfile retrieveProfile(@NonNull String number) throws IOException { + private SignalServiceProfile retrieveProfile(@NonNull String number, Optional unidentifiedAccess) + throws IOException + { SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe(); if (pipe != null) { try { - return pipe.getProfile(new SignalServiceAddress(number)); + return pipe.getProfile(new SignalServiceAddress(number), unidentifiedAccess); } catch (IOException e) { Log.w(TAG, e); } } - return receiver.retrieveProfile(new SignalServiceAddress(number)); + return receiver.retrieveProfile(new SignalServiceAddress(number), unidentifiedAccess); } private void setIdentityKey(Recipient recipient, String identityKeyValue) { @@ -143,6 +165,30 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType { } } + private void setUnidentifiedAccessMode(Recipient recipient, String unidentifiedAccessVerifier, boolean unrestrictedUnidentifiedAccess) { + RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + byte[] profileKey = recipient.getProfileKey(); + + // XXX Update UI + if (unrestrictedUnidentifiedAccess) { + recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED); + } else if (profileKey == null || unidentifiedAccessVerifier == null) { + recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED); + } else { + ProfileCipher profileCipher = new ProfileCipher(profileKey); + boolean verifiedUnidentifiedAccess; + + try { + verifiedUnidentifiedAccess = profileCipher.verifyUnidentifiedAccess(Base64.decode(unidentifiedAccessVerifier)); + } catch (IOException e) { + Log.w(TAG, e); + verifiedUnidentifiedAccess = false; + } + + recipientDatabase.setUnidentifiedAccessMode(recipient, verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED); + } + } + private void setProfileName(Recipient recipient, String profileName) { try { byte[] profileKey = recipient.getProfileKey(); @@ -172,4 +218,14 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType { .add(new RetrieveProfileAvatarJob(context, recipient, profileAvatar)); } } + + private Optional getUnidentifiedAccess(@NonNull Recipient recipient) { + Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient); + + if (unidentifiedAccess.isPresent()) { + return unidentifiedAccess.get().getTargetUnidentifiedAccess(); + } + + return Optional.absent(); + } } diff --git a/src/org/thoughtcrime/securesms/jobs/RotateCertificateJob.java b/src/org/thoughtcrime/securesms/jobs/RotateCertificateJob.java new file mode 100644 index 0000000000..e680c00967 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/RotateCertificateJob.java @@ -0,0 +1,71 @@ +package org.thoughtcrime.securesms.jobs; + + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.Log; + +import org.thoughtcrime.securesms.dependencies.InjectableType; +import org.thoughtcrime.securesms.jobmanager.JobParameters; +import org.thoughtcrime.securesms.jobmanager.SafeData; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; + +import java.io.IOException; + +import javax.inject.Inject; + +import androidx.work.Data; + +@SuppressWarnings("WeakerAccess") +public class RotateCertificateJob extends ContextJob implements InjectableType { + + private static final long serialVersionUID = 1L; + + private static final String TAG = RotateCertificateJob.class.getName(); + + @Inject transient SignalServiceAccountManager accountManager; + + public RotateCertificateJob() { + super(null, null); + } + + public RotateCertificateJob(Context context) { + super(context, JobParameters.newBuilder() + .withGroupId("__ROTATE_SENDER_CERTIFICATE__") + .withNetworkRequirement() + .create()); + } + + @NonNull + @Override + protected Data serialize(@NonNull Data.Builder dataBuilder) { + return dataBuilder.build(); + } + + @Override + protected void initialize(@NonNull SafeData data) { + + } + + @Override + public void onAdded() {} + + + @Override + public void onRun() throws IOException { + byte[] certificate = accountManager.getSenderCertificate(); + TextSecurePreferences.setUnidentifiedAccessCertificate(context, certificate); + } + + @Override + public boolean onShouldRetry(Exception e) { + return e instanceof PushNetworkException; + } + + @Override + public void onCanceled() { + Log.w(TAG, "Failed to rotate sender certificate!"); + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java new file mode 100644 index 0000000000..493db58589 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/SendDeliveryReceiptJob.java @@ -0,0 +1,100 @@ +package org.thoughtcrime.securesms.jobs; + + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; +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.recipients.Recipient; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; + +import java.io.IOException; +import java.util.Collections; + +import javax.inject.Inject; + +import androidx.work.Data; + +public class SendDeliveryReceiptJob extends ContextJob implements InjectableType { + + private static final long serialVersionUID = 1L; + + private static final String KEY_ADDRESS = "address"; + private static final String KEY_MESSAGE_ID = "message_id"; + private static final String KEY_TIMESTAMP = "timestamp"; + + private static final String TAG = SendReadReceiptJob.class.getSimpleName(); + + @Inject + transient SignalServiceMessageSender messageSender; + + private String address; + private long messageId; + private long timestamp; + + public SendDeliveryReceiptJob() { + super(null, null); + } + + public SendDeliveryReceiptJob(Context context, Address address, long messageId) { + super(context, JobParameters.newBuilder() + .withNetworkRequirement() + .create()); + + this.address = address.serialize(); + this.messageId = messageId; + this.timestamp = System.currentTimeMillis(); + } + + @Override + public void onAdded() {} + + @NonNull + @Override + protected Data serialize(@NonNull Data.Builder dataBuilder) { + return dataBuilder.putString(KEY_ADDRESS, address) + .putLong(KEY_MESSAGE_ID, messageId) + .putLong(KEY_TIMESTAMP, timestamp) + .build(); + } + + @Override + protected void initialize(@NonNull SafeData data) { + this.address = data.getString(KEY_ADDRESS); + this.messageId = data.getLong(KEY_MESSAGE_ID); + this.timestamp = data.getLong(KEY_TIMESTAMP); + } + + @Override + public void onRun() throws IOException, UntrustedIdentityException { + SignalServiceAddress remoteAddress = new SignalServiceAddress(address); + SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY, + Collections.singletonList(messageId), + timestamp); + + messageSender.sendReceipt(remoteAddress, + UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)), + receiptMessage); + } + + @Override + public boolean onShouldRetry(Exception e) { + if (e instanceof PushNetworkException) return true; + return false; + } + + @Override + public void onCanceled() { + Log.w(TAG, "Failed to send delivery receipt to: " + address); + } + +} diff --git a/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java index bed3982340..b5af6f666b 100644 --- a/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java @@ -4,11 +4,13 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.support.annotation.NonNull; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; 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.logging.Log; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; @@ -86,7 +88,9 @@ public class SendReadReceiptJob extends ContextJob implements InjectableType { SignalServiceAddress remoteAddress = new SignalServiceAddress(address); SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp); - messageSender.sendReceipt(remoteAddress, receiptMessage); + messageSender.sendReceipt(remoteAddress, + UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)), + receiptMessage); } @Override diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 2eca2fb524..35f6f08585 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -24,6 +24,7 @@ public class IncomingMediaMessage { private final long expiresIn; private final boolean expirationUpdate; private final QuoteModel quote; + private final boolean unidentified; private final List attachments = new LinkedList<>(); private final List sharedContacts = new LinkedList<>(); @@ -35,7 +36,8 @@ public class IncomingMediaMessage { List attachments, int subscriptionId, long expiresIn, - boolean expirationUpdate) + boolean expirationUpdate, + boolean unidentified) { this.from = from; this.groupId = groupId.orNull(); @@ -46,6 +48,7 @@ public class IncomingMediaMessage { this.expiresIn = expiresIn; this.expirationUpdate = expirationUpdate; this.quote = null; + this.unidentified = unidentified; this.attachments.addAll(attachments); } @@ -55,7 +58,7 @@ public class IncomingMediaMessage { int subscriptionId, long expiresIn, boolean expirationUpdate, - Optional relay, + boolean unidentified, Optional body, Optional group, Optional> attachments, @@ -70,6 +73,7 @@ public class IncomingMediaMessage { this.expiresIn = expiresIn; this.expirationUpdate = expirationUpdate; this.quote = quote.orNull(); + this.unidentified = unidentified; if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false)); else this.groupId = null; @@ -125,4 +129,8 @@ public class IncomingMediaMessage { public List getSharedContacts() { return sharedContacts; } + + public boolean isUnidentified() { + return unidentified; + } } diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java index b7e2420fa6..1036f11da1 100644 --- a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java @@ -6,6 +6,8 @@ import android.text.TextUtils; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.contactshare.Contact; +import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; +import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.recipients.Recipient; import java.util.LinkedList; @@ -13,32 +15,40 @@ import java.util.List; public class OutgoingMediaMessage { - private final Recipient recipient; - protected final String body; - protected final List attachments; - private final long sentTimeMillis; - private final int distributionType; - private final int subscriptionId; - private final long expiresIn; - private final QuoteModel outgoingQuote; - private final List contacts = new LinkedList<>(); + private final Recipient recipient; + protected final String body; + protected final List attachments; + private final long sentTimeMillis; + private final int distributionType; + private final int subscriptionId; + private final long expiresIn; + private final QuoteModel outgoingQuote; + + private final List networkFailures = new LinkedList<>(); + private final List identityKeyMismatches = new LinkedList<>(); + private final List contacts = new LinkedList<>(); public OutgoingMediaMessage(Recipient recipient, String message, List attachments, long sentTimeMillis, int subscriptionId, long expiresIn, - int distributionType, @Nullable QuoteModel outgoingQuote, - @NonNull List contacts) + int distributionType, + @Nullable QuoteModel outgoingQuote, + @NonNull List contacts, + @NonNull List networkFailures, + @NonNull List identityKeyMismatches) { - this.recipient = recipient; - this.body = message; - this.sentTimeMillis = sentTimeMillis; - this.distributionType = distributionType; - this.attachments = attachments; - this.subscriptionId = subscriptionId; - this.expiresIn = expiresIn; - this.outgoingQuote = outgoingQuote; + this.recipient = recipient; + this.body = message; + this.sentTimeMillis = sentTimeMillis; + this.distributionType = distributionType; + this.attachments = attachments; + this.subscriptionId = subscriptionId; + this.expiresIn = expiresIn; + this.outgoingQuote = outgoingQuote; this.contacts.addAll(contacts); + this.networkFailures.addAll(networkFailures); + this.identityKeyMismatches.addAll(identityKeyMismatches); } public OutgoingMediaMessage(Recipient recipient, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, long expiresIn, int distributionType, @Nullable QuoteModel outgoingQuote, @NonNull List contacts) @@ -47,7 +57,8 @@ public class OutgoingMediaMessage { buildMessage(slideDeck, message), slideDeck.asAttachments(), sentTimeMillis, subscriptionId, - expiresIn, distributionType, outgoingQuote, contacts); + expiresIn, distributionType, outgoingQuote, + contacts, new LinkedList<>(), new LinkedList<>()); } public OutgoingMediaMessage(OutgoingMediaMessage that) { @@ -60,6 +71,8 @@ public class OutgoingMediaMessage { this.expiresIn = that.expiresIn; this.outgoingQuote = that.outgoingQuote; + this.identityKeyMismatches.addAll(that.identityKeyMismatches); + this.networkFailures.addAll(that.networkFailures); this.contacts.addAll(that.contacts); } @@ -111,6 +124,14 @@ public class OutgoingMediaMessage { return contacts; } + public @NonNull List getNetworkFailures() { + return networkFailures; + } + + public @NonNull List getIdentityKeyMismatches() { + return identityKeyMismatches; + } + private static String buildMessage(SlideDeck slideDeck, String message) { if (!TextUtils.isEmpty(message) && !TextUtils.isEmpty(slideDeck.getBody())) { return slideDeck.getBody() + "\n\n" + message; diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingSecureMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingSecureMediaMessage.java index 79773b5989..cbed4891e4 100644 --- a/src/org/thoughtcrime/securesms/mms/OutgoingSecureMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/OutgoingSecureMediaMessage.java @@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.recipients.Recipient; +import java.util.Collections; import java.util.List; public class OutgoingSecureMediaMessage extends OutgoingMediaMessage { @@ -19,7 +20,7 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage { @Nullable QuoteModel quote, @NonNull List contacts) { - super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts); + super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts, Collections.emptyList(), Collections.emptyList()); } public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java index 44f1f97084..84406c4eac 100644 --- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java @@ -76,7 +76,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver { if (recipient.isGroupRecipient()) { Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); - OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList()); + OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); replyThreadId = MessageSender.send(context, reply, threadId, false, null); } else { Log.w("AndroidAutoReplyReceiver", "Sending regular message "); diff --git a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index e82e2d177c..5b45b5e3b8 100644 --- a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -72,7 +72,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver { long expiresIn = recipient.getExpireMessages() * 1000L; if (recipient.isGroupRecipient()) { - OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList()); + OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); threadId = MessageSender.send(context, reply, -1, false, null); } else if (TextSecurePreferences.isPushRegistered(context) && recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { OutgoingEncryptedMessage reply = new OutgoingEncryptedMessage(recipient, responseText.toString(), expiresIn); diff --git a/src/org/thoughtcrime/securesms/push/SecurityEventListener.java b/src/org/thoughtcrime/securesms/push/SecurityEventListener.java index cbb7bd0fe0..ea33cb3c3e 100644 --- a/src/org/thoughtcrime/securesms/push/SecurityEventListener.java +++ b/src/org/thoughtcrime/securesms/push/SecurityEventListener.java @@ -3,6 +3,10 @@ package org.thoughtcrime.securesms.push; import android.content.Context; import org.thoughtcrime.securesms.crypto.SecurityEvent; +import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -20,4 +24,12 @@ 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/push/SignalServiceNetworkAccess.java b/src/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.java index 9c319fae46..4755ae0faf 100644 --- a/src/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.java +++ b/src/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.java @@ -21,6 +21,7 @@ import okhttp3.TlsVersion; public class SignalServiceNetworkAccess { + @SuppressWarnings("unused") private static final String TAG = SignalServiceNetworkAccess.class.getName(); private static final String COUNTRY_CODE_EGYPT = "+20"; diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index b9c980aa9d..62a712986a 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; +import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; @@ -93,6 +94,7 @@ public class Recipient implements RecipientModifiedListener { private boolean profileSharing; private String notificationChannel; + private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; @SuppressWarnings("ConstantConditions") public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) { @@ -121,51 +123,55 @@ public class Recipient implements RecipientModifiedListener { this.resolving = true; if (stale != null) { - this.name = stale.name; - this.contactUri = stale.contactUri; - this.systemContactPhoto = stale.systemContactPhoto; - this.groupAvatarId = stale.groupAvatarId; - this.color = stale.color; - this.customLabel = stale.customLabel; - this.messageRingtone = stale.messageRingtone; - this.callRingtone = stale.callRingtone; - this.mutedUntil = stale.mutedUntil; - this.blocked = stale.blocked; - this.messageVibrate = stale.messageVibrate; - this.callVibrate = stale.callVibrate; - this.expireMessages = stale.expireMessages; - this.seenInviteReminder = stale.seenInviteReminder; - this.defaultSubscriptionId = stale.defaultSubscriptionId; - this.registered = stale.registered; - this.notificationChannel = stale.notificationChannel; - this.profileKey = stale.profileKey; - this.profileName = stale.profileName; - this.profileAvatar = stale.profileAvatar; - this.profileSharing = stale.profileSharing; + this.name = stale.name; + this.contactUri = stale.contactUri; + this.systemContactPhoto = stale.systemContactPhoto; + this.groupAvatarId = stale.groupAvatarId; + this.color = stale.color; + this.customLabel = stale.customLabel; + this.messageRingtone = stale.messageRingtone; + this.callRingtone = stale.callRingtone; + this.mutedUntil = stale.mutedUntil; + this.blocked = stale.blocked; + this.messageVibrate = stale.messageVibrate; + this.callVibrate = stale.callVibrate; + this.expireMessages = stale.expireMessages; + this.seenInviteReminder = stale.seenInviteReminder; + this.defaultSubscriptionId = stale.defaultSubscriptionId; + this.registered = stale.registered; + this.notificationChannel = stale.notificationChannel; + this.profileKey = stale.profileKey; + this.profileName = stale.profileName; + this.profileAvatar = stale.profileAvatar; + this.profileSharing = stale.profileSharing; + this.unidentifiedAccessMode = stale.unidentifiedAccessMode; + this.participants.clear(); this.participants.addAll(stale.participants); } if (details.isPresent()) { - this.name = details.get().name; - this.systemContactPhoto = details.get().systemContactPhoto; - this.groupAvatarId = details.get().groupAvatarId; - this.color = details.get().color; - this.messageRingtone = details.get().messageRingtone; - this.callRingtone = details.get().callRingtone; - this.mutedUntil = details.get().mutedUntil; - this.blocked = details.get().blocked; - this.messageVibrate = details.get().messageVibrateState; - this.callVibrate = details.get().callVibrateState; - this.expireMessages = details.get().expireMessages; - this.seenInviteReminder = details.get().seenInviteReminder; - this.defaultSubscriptionId = details.get().defaultSubscriptionId; - this.registered = details.get().registered; - this.notificationChannel = details.get().notificationChannel; - this.profileKey = details.get().profileKey; - this.profileName = details.get().profileName; - this.profileAvatar = details.get().profileAvatar; - this.profileSharing = details.get().profileSharing; + this.name = details.get().name; + this.systemContactPhoto = details.get().systemContactPhoto; + this.groupAvatarId = details.get().groupAvatarId; + this.color = details.get().color; + this.messageRingtone = details.get().messageRingtone; + this.callRingtone = details.get().callRingtone; + this.mutedUntil = details.get().mutedUntil; + this.blocked = details.get().blocked; + this.messageVibrate = details.get().messageVibrateState; + this.callVibrate = details.get().callVibrateState; + this.expireMessages = details.get().expireMessages; + this.seenInviteReminder = details.get().seenInviteReminder; + this.defaultSubscriptionId = details.get().defaultSubscriptionId; + this.registered = details.get().registered; + this.notificationChannel = details.get().notificationChannel; + this.profileKey = details.get().profileKey; + this.profileName = details.get().profileName; + this.profileAvatar = details.get().profileAvatar; + this.profileSharing = details.get().profileSharing; + this.unidentifiedAccessMode = details.get().unidentifiedAccessMode; + this.participants.clear(); this.participants.addAll(details.get().participants); } @@ -175,28 +181,29 @@ public class Recipient implements RecipientModifiedListener { public void onSuccess(RecipientDetails result) { if (result != null) { synchronized (Recipient.this) { - Recipient.this.name = result.name; - Recipient.this.contactUri = result.contactUri; - Recipient.this.systemContactPhoto = result.systemContactPhoto; - Recipient.this.groupAvatarId = result.groupAvatarId; - Recipient.this.color = result.color; - Recipient.this.customLabel = result.customLabel; - Recipient.this.messageRingtone = result.messageRingtone; - Recipient.this.callRingtone = result.callRingtone; - Recipient.this.mutedUntil = result.mutedUntil; - Recipient.this.blocked = result.blocked; - Recipient.this.messageVibrate = result.messageVibrateState; - Recipient.this.callVibrate = result.callVibrateState; - Recipient.this.expireMessages = result.expireMessages; - Recipient.this.seenInviteReminder = result.seenInviteReminder; - Recipient.this.defaultSubscriptionId = result.defaultSubscriptionId; - Recipient.this.registered = result.registered; - Recipient.this.notificationChannel = result.notificationChannel; - Recipient.this.profileKey = result.profileKey; - Recipient.this.profileName = result.profileName; - Recipient.this.profileAvatar = result.profileAvatar; - Recipient.this.profileSharing = result.profileSharing; - Recipient.this.profileName = result.profileName; + Recipient.this.name = result.name; + Recipient.this.contactUri = result.contactUri; + Recipient.this.systemContactPhoto = result.systemContactPhoto; + Recipient.this.groupAvatarId = result.groupAvatarId; + Recipient.this.color = result.color; + Recipient.this.customLabel = result.customLabel; + Recipient.this.messageRingtone = result.messageRingtone; + Recipient.this.callRingtone = result.callRingtone; + Recipient.this.mutedUntil = result.mutedUntil; + Recipient.this.blocked = result.blocked; + Recipient.this.messageVibrate = result.messageVibrateState; + Recipient.this.callVibrate = result.callVibrateState; + Recipient.this.expireMessages = result.expireMessages; + Recipient.this.seenInviteReminder = result.seenInviteReminder; + Recipient.this.defaultSubscriptionId = result.defaultSubscriptionId; + Recipient.this.registered = result.registered; + Recipient.this.notificationChannel = result.notificationChannel; + Recipient.this.profileKey = result.profileKey; + Recipient.this.profileName = result.profileName; + Recipient.this.profileAvatar = result.profileAvatar; + Recipient.this.profileSharing = result.profileSharing; + Recipient.this.profileName = result.profileName; + Recipient.this.unidentifiedAccessMode = result.unidentifiedAccessMode; Recipient.this.participants.clear(); Recipient.this.participants.addAll(result.participants); @@ -221,28 +228,30 @@ public class Recipient implements RecipientModifiedListener { } Recipient(@NonNull Address address, @NonNull RecipientDetails details) { - this.address = address; - this.contactUri = details.contactUri; - this.name = details.name; - this.systemContactPhoto = details.systemContactPhoto; - this.groupAvatarId = details.groupAvatarId; - this.color = details.color; - this.customLabel = details.customLabel; - this.messageRingtone = details.messageRingtone; - this.callRingtone = details.callRingtone; - this.mutedUntil = details.mutedUntil; - this.blocked = details.blocked; - this.messageVibrate = details.messageVibrateState; - this.callVibrate = details.callVibrateState; - this.expireMessages = details.expireMessages; - this.seenInviteReminder = details.seenInviteReminder; - this.defaultSubscriptionId = details.defaultSubscriptionId; - this.registered = details.registered; - this.notificationChannel = details.notificationChannel; - this.profileKey = details.profileKey; - this.profileName = details.profileName; - this.profileAvatar = details.profileAvatar; - this.profileSharing = details.profileSharing; + this.address = address; + this.contactUri = details.contactUri; + this.name = details.name; + this.systemContactPhoto = details.systemContactPhoto; + this.groupAvatarId = details.groupAvatarId; + this.color = details.color; + this.customLabel = details.customLabel; + this.messageRingtone = details.messageRingtone; + this.callRingtone = details.callRingtone; + this.mutedUntil = details.mutedUntil; + this.blocked = details.blocked; + this.messageVibrate = details.messageVibrateState; + this.callVibrate = details.callVibrateState; + this.expireMessages = details.expireMessages; + this.seenInviteReminder = details.seenInviteReminder; + this.defaultSubscriptionId = details.defaultSubscriptionId; + this.registered = details.registered; + this.notificationChannel = details.notificationChannel; + this.profileKey = details.profileKey; + this.profileName = details.profileName; + this.profileAvatar = details.profileAvatar; + this.profileSharing = details.profileSharing; + this.unidentifiedAccessMode = details.unidentifiedAccessMode; + this.participants.addAll(details.participants); this.resolving = false; } @@ -616,6 +625,18 @@ public class Recipient implements RecipientModifiedListener { notifyListeners(); } + public synchronized UnidentifiedAccessMode getUnidentifiedAccessMode() { + return unidentifiedAccessMode; + } + + public void setUnidentifiedAccessMode(@NonNull UnidentifiedAccessMode unidentifiedAccessMode) { + synchronized (this) { + this.unidentifiedAccessMode = unidentifiedAccessMode; + } + + notifyListeners(); + } + public synchronized boolean isSystemContact() { return contactUri != null; } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java index 5398662ffb..bdccf32a56 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; +import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.SoftHashMap; @@ -152,56 +153,58 @@ class RecipientProvider { } static class RecipientDetails { - @Nullable final String name; - @Nullable final String customLabel; - @Nullable final Uri systemContactPhoto; - @Nullable final Uri contactUri; - @Nullable final Long groupAvatarId; - @Nullable final MaterialColor color; - @Nullable final Uri messageRingtone; - @Nullable final Uri callRingtone; - final long mutedUntil; - @Nullable final VibrateState messageVibrateState; - @Nullable final VibrateState callVibrateState; - final boolean blocked; - final int expireMessages; - @NonNull final List participants; - @Nullable final String profileName; - final boolean seenInviteReminder; - final Optional defaultSubscriptionId; - @NonNull final RegisteredState registered; - @Nullable final byte[] profileKey; - @Nullable final String profileAvatar; - final boolean profileSharing; - final boolean systemContact; - @Nullable final String notificationChannel; + @Nullable final String name; + @Nullable final String customLabel; + @Nullable final Uri systemContactPhoto; + @Nullable final Uri contactUri; + @Nullable final Long groupAvatarId; + @Nullable final MaterialColor color; + @Nullable final Uri messageRingtone; + @Nullable final Uri callRingtone; + final long mutedUntil; + @Nullable final VibrateState messageVibrateState; + @Nullable final VibrateState callVibrateState; + final boolean blocked; + final int expireMessages; + @NonNull final List participants; + @Nullable final String profileName; + final boolean seenInviteReminder; + final Optional defaultSubscriptionId; + @NonNull final RegisteredState registered; + @Nullable final byte[] profileKey; + @Nullable final String profileAvatar; + final boolean profileSharing; + final boolean systemContact; + @Nullable final String notificationChannel; + @NonNull final UnidentifiedAccessMode unidentifiedAccessMode; RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId, boolean systemContact, @Nullable RecipientSettings settings, @Nullable List participants) { - this.groupAvatarId = groupAvatarId; - this.systemContactPhoto = settings != null ? Util.uri(settings.getSystemContactPhotoUri()) : null; - this.customLabel = settings != null ? settings.getSystemPhoneLabel() : null; - this.contactUri = settings != null ? Util.uri(settings.getSystemContactUri()) : null; - this.color = settings != null ? settings.getColor() : null; - this.messageRingtone = settings != null ? settings.getMessageRingtone() : null; - this.callRingtone = settings != null ? settings.getCallRingtone() : null; - this.mutedUntil = settings != null ? settings.getMuteUntil() : 0; - this.messageVibrateState = settings != null ? settings.getMessageVibrateState() : null; - this.callVibrateState = settings != null ? settings.getCallVibrateState() : null; - this.blocked = settings != null && settings.isBlocked(); - this.expireMessages = settings != null ? settings.getExpireMessages() : 0; - this.participants = participants == null ? new LinkedList<>() : participants; - this.profileName = settings != null ? settings.getProfileName() : null; - this.seenInviteReminder = settings != null && settings.hasSeenInviteReminder(); - this.defaultSubscriptionId = settings != null ? settings.getDefaultSubscriptionId() : Optional.absent(); - this.registered = settings != null ? settings.getRegistered() : RegisteredState.UNKNOWN; - this.profileKey = settings != null ? settings.getProfileKey() : null; - this.profileAvatar = settings != null ? settings.getProfileAvatar() : null; - this.profileSharing = settings != null && settings.isProfileSharing(); - this.systemContact = systemContact; - this.notificationChannel = settings != null ? settings.getNotificationChannel() : null; + this.groupAvatarId = groupAvatarId; + this.systemContactPhoto = settings != null ? Util.uri(settings.getSystemContactPhotoUri()) : null; + this.customLabel = settings != null ? settings.getSystemPhoneLabel() : null; + this.contactUri = settings != null ? Util.uri(settings.getSystemContactUri()) : null; + this.color = settings != null ? settings.getColor() : null; + this.messageRingtone = settings != null ? settings.getMessageRingtone() : null; + this.callRingtone = settings != null ? settings.getCallRingtone() : null; + this.mutedUntil = settings != null ? settings.getMuteUntil() : 0; + this.messageVibrateState = settings != null ? settings.getMessageVibrateState() : null; + this.callVibrateState = settings != null ? settings.getCallVibrateState() : null; + this.blocked = settings != null && settings.isBlocked(); + this.expireMessages = settings != null ? settings.getExpireMessages() : 0; + this.participants = participants == null ? new LinkedList<>() : participants; + this.profileName = settings != null ? settings.getProfileName() : null; + this.seenInviteReminder = settings != null && settings.hasSeenInviteReminder(); + this.defaultSubscriptionId = settings != null ? settings.getDefaultSubscriptionId() : Optional.absent(); + this.registered = settings != null ? settings.getRegistered() : RegisteredState.UNKNOWN; + this.profileKey = settings != null ? settings.getProfileKey() : null; + this.profileAvatar = settings != null ? settings.getProfileAvatar() : null; + this.profileSharing = settings != null && settings.isProfileSharing(); + this.systemContact = systemContact; + this.notificationChannel = settings != null ? settings.getNotificationChannel() : null; + this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED; if (name == null && settings != null) this.name = settings.getSystemDisplayName(); else this.name = name; diff --git a/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java b/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java index fdb76851b3..0f0b8c11ab 100644 --- a/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java +++ b/src/org/thoughtcrime/securesms/service/IncomingMessageObserver.java @@ -40,7 +40,8 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe public static final int FOREGROUND_ID = 313399; private static final long REQUEST_TIMEOUT_MINUTES = 1; - private static SignalServiceMessagePipe pipe = null; + private static SignalServiceMessagePipe pipe = null; + public static SignalServiceMessagePipe unidentifiedPipe = null; private final Context context; private final NetworkRequirement networkRequirement; @@ -114,9 +115,10 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe } } - private void shutdown(SignalServiceMessagePipe pipe) { + private void shutdown(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) { try { pipe.shutdown(); + unidentifiedPipe.shutdown(); } catch (Throwable t) { Log.w(TAG, t); } @@ -126,6 +128,10 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe return pipe; } + public static @Nullable SignalServiceMessagePipe getUnidentifiedPipe() { + return unidentifiedPipe; + } + private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler { MessageRetrievalThread() { @@ -140,9 +146,11 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe waitForConnectionNecessary(); Log.i(TAG, "Making websocket connection...."); - pipe = receiver.createMessagePipe(); + pipe = receiver.createMessagePipe(); + unidentifiedPipe = receiver.createUnidentifiedMessagePipe(); - SignalServiceMessagePipe localPipe = pipe; + SignalServiceMessagePipe localPipe = pipe; + SignalServiceMessagePipe unidentifiedLocalPipe = unidentifiedPipe; try { while (isConnectionNecessary()) { @@ -163,7 +171,7 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe Log.w(TAG, e); } finally { Log.w(TAG, "Shutting down pipe..."); - shutdown(localPipe); + shutdown(localPipe, unidentifiedLocalPipe); } Log.i(TAG, "Looping..."); diff --git a/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java new file mode 100644 index 0000000000..f78b819958 --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java @@ -0,0 +1,254 @@ +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/service/RotateSenderCertificateListener.java b/src/org/thoughtcrime/securesms/service/RotateSenderCertificateListener.java new file mode 100644 index 0000000000..d4d0c560a3 --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/RotateSenderCertificateListener.java @@ -0,0 +1,38 @@ +package org.thoughtcrime.securesms.service; + + +import android.content.Context; +import android.content.Intent; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.jobs.RotateCertificateJob; +import org.thoughtcrime.securesms.util.TextSecurePreferences; + +import java.util.concurrent.TimeUnit; + +public class RotateSenderCertificateListener extends PersistentAlarmManagerListener { + + private static final long INTERVAL = TimeUnit.DAYS.toMillis(1); + + @Override + protected long getNextScheduledExecutionTime(Context context) { + return TextSecurePreferences.getUnidentifiedAccessCertificateRotationTime(context); + } + + @Override + protected long onAlarm(Context context, long scheduledTime) { + ApplicationContext.getInstance(context) + .getJobManager() + .add(new RotateCertificateJob(context)); + + long nextTime = System.currentTimeMillis() + INTERVAL; + TextSecurePreferences.setUnidentifiedAccessCertificateRotationTime(context, nextTime); + + return nextTime; + } + + public static void schedule(Context context) { + new RotateSenderCertificateListener().onReceive(context, new Intent()); + } + +} diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java index 85b9f20cae..7b9b7894c0 100644 --- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -28,6 +28,7 @@ import org.greenrobot.eventbus.EventBus; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.WebRtcCallActivity; import org.thoughtcrime.securesms.contacts.ContactAccessor; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; @@ -992,7 +993,9 @@ public class WebRtcCallService extends Service implements InjectableType, Callable callable = new Callable() { @Override public Boolean call() throws Exception { - messageSender.sendCallMessage(new SignalServiceAddress(recipient.getAddress().toPhoneString()), callMessage); + messageSender.sendCallMessage(new SignalServiceAddress(recipient.getAddress().toPhoneString()), + UnidentifiedAccessUtil.getAccessFor(WebRtcCallService.this, recipient), + callMessage); return true; } }; diff --git a/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java index 56cfc47f9f..8ce4bec8b4 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java @@ -7,7 +7,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup; public class IncomingJoinedMessage extends IncomingTextMessage { public IncomingJoinedMessage(Address sender) { - super(sender, 1, System.currentTimeMillis(), null, Optional.absent(), 0); + super(sender, 1, System.currentTimeMillis(), null, Optional.absent(), 0, false); } @Override diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 570a48aa7b..256a93b32a 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -42,6 +42,7 @@ public class IncomingTextMessage implements Parcelable { private final boolean push; private final int subscriptionId; private final long expiresInMillis; + private final boolean unidentified; public IncomingTextMessage(@NonNull Context context, @NonNull SmsMessage message, int subscriptionId) { this.message = message.getDisplayMessageBody(); @@ -56,11 +57,12 @@ public class IncomingTextMessage implements Parcelable { this.expiresInMillis = 0; this.groupId = null; this.push = false; + this.unidentified = false; } public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis) + long expiresInMillis, boolean unidentified) { this.message = encodedBody; this.sender = sender; @@ -73,6 +75,7 @@ public class IncomingTextMessage implements Parcelable { this.push = true; this.subscriptionId = -1; this.expiresInMillis = expiresInMillis; + this.unidentified = unidentified; if (group.isPresent()) { this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false)); @@ -94,6 +97,7 @@ public class IncomingTextMessage implements Parcelable { this.push = (in.readInt() == 1); this.subscriptionId = in.readInt(); this.expiresInMillis = in.readLong(); + this.unidentified = in.readInt() == 1; } public IncomingTextMessage(IncomingTextMessage base, String newBody) { @@ -109,6 +113,7 @@ public class IncomingTextMessage implements Parcelable { this.push = base.isPush(); this.subscriptionId = base.getSubscriptionId(); this.expiresInMillis = base.getExpiresIn(); + this.unidentified = base.isUnidentified(); } public IncomingTextMessage(List fragments) { @@ -130,6 +135,7 @@ public class IncomingTextMessage implements Parcelable { this.push = fragments.get(0).isPush(); this.subscriptionId = fragments.get(0).getSubscriptionId(); this.expiresInMillis = fragments.get(0).getExpiresIn(); + this.unidentified = fragments.get(0).isUnidentified(); } protected IncomingTextMessage(@NonNull Address sender, @Nullable Address groupId) @@ -146,6 +152,7 @@ public class IncomingTextMessage implements Parcelable { this.push = true; this.subscriptionId = -1; this.expiresInMillis = 0; + this.unidentified = false; } public int getSubscriptionId() { @@ -240,6 +247,10 @@ public class IncomingTextMessage implements Parcelable { return false; } + public boolean isUnidentified() { + return unidentified; + } + @Override public int describeContents() { return 0; @@ -258,5 +269,6 @@ public class IncomingTextMessage implements Parcelable { out.writeParcelable(groupId, flags); out.writeInt(push ? 1 : 0); out.writeInt(subscriptionId); + out.writeInt(unidentified ? 1 : 0); } } diff --git a/src/org/thoughtcrime/securesms/transport/RetryLaterException.java b/src/org/thoughtcrime/securesms/transport/RetryLaterException.java index 0642f7c691..5117b45b8e 100644 --- a/src/org/thoughtcrime/securesms/transport/RetryLaterException.java +++ b/src/org/thoughtcrime/securesms/transport/RetryLaterException.java @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.transport; import java.io.IOException; public class RetryLaterException extends Exception { + public RetryLaterException() {} + public RetryLaterException(Exception e) { super(e); } diff --git a/src/org/thoughtcrime/securesms/util/IdentityUtil.java b/src/org/thoughtcrime/securesms/util/IdentityUtil.java index 7c773f9f39..74aab01c1a 100644 --- a/src/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/src/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -7,7 +7,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.annotation.UiThread; -import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; @@ -19,6 +18,7 @@ import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage; @@ -82,7 +82,7 @@ public class IdentityUtil { SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId()); if (remote) { - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming); @@ -102,7 +102,7 @@ public class IdentityUtil { } if (remote) { - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming); @@ -132,14 +132,14 @@ public class IdentityUtil { while ((groupRecord = reader.getNext()) != null) { if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) { SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId()); - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false); IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming); smsDatabase.insertMessageInbox(groupUpdate); } } - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false); IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming); Optional insertResult = smsDatabase.insertMessageInbox(individualUpdate); diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 7e1dbf9bbc..33a0fa8b91 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -84,6 +84,7 @@ public class TextSecurePreferences { private static final String UPDATE_APK_DOWNLOAD_ID = "pref_update_apk_download_id"; private static final String UPDATE_APK_DIGEST = "pref_update_apk_digest"; private static final String SIGNED_PREKEY_ROTATION_TIME_PREF = "pref_signed_pre_key_rotation_time"; + private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications"; private static final String SHOW_INVITE_REMINDER_PREF = "pref_show_invite_reminder"; public static final String MESSAGE_BODY_TEXT_SIZE_PREF = "pref_message_body_text_size"; @@ -163,6 +164,10 @@ 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 boolean isScreenLockEnabled(@NonNull Context context) { return getBooleanPreference(context, SCREEN_LOCK, false); @@ -506,6 +511,39 @@ public class TextSecurePreferences { return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true); } + public static long getUnidentifiedAccessCertificateRotationTime(Context context) { + return getLongPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF, 0L); + } + + public static void setUnidentifiedAccessCertificateRotationTime(Context context, long value) { + setLongPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF, value); + } + + public static void setUnidentifiedAccessCertificate(Context context, byte[] value) { + setStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE, Base64.encodeBytes(value)); + } + + public static byte[] getUnidentifiedAccessCertificate(Context context) { + try { + String result = getStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE, null); + if (result != null) { + return Base64.decode(result); + } + } catch (IOException e) { + Log.w(TAG, e); + } + + return null; + } + + public static boolean isUniversalUnidentifiedAccess(Context context) { + return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false); + } + + public static void setUniversalUnidentifiedAccess(Context context, boolean value) { + setBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, value); + } + public static long getSignedPreKeyRotationTime(Context context) { return getLongPreference(context, SIGNED_PREKEY_ROTATION_TIME_PREF, 0L); }