Support for sealed sender - Part 1

This commit is contained in:
Moxie Marlinspike 2018-05-22 02:13:10 -07:00 committed by Greyson Parrelli
parent b7b9554364
commit 5f31762220
67 changed files with 1679 additions and 801 deletions

View File

@ -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"

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -303,13 +303,13 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new MessageDetailsLoader(this, getIntent().getStringExtra(TYPE_EXTRA),
getIntent().getLongExtra(MESSAGE_ID_EXTRA, -1));
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
public void onLoadFinished(@NonNull Loader<Cursor> 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<Cursor> loader) {
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
recipientsList.setAdapter(null);
}

View File

@ -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<PreKeyRecord> 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);

View File

@ -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(),

View File

@ -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<UnidentifiedAccessPair> 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<UnidentifiedAccessPair> 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());
}
}
}

View File

@ -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);

View File

@ -108,6 +108,17 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
}
}
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
Optional<IdentityRecord> 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> identityRecord) {
if (!identityRecord.isPresent()) {
Log.w(TAG, "Nothing here, returning true...");

View File

@ -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<GroupReceiptInfo> getGroupReceiptInfo(long mmsId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
List<GroupReceiptInfo> 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;
}
}
}

View File

@ -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<Attachment> contactAttachments = new HashSet<>(Stream.of(contacts).map(Contact::getAvatarAttachment).filter(a -> a != null).toList());
List<Attachment> 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<NetworkFailure> networkFailures = new LinkedList<>();
List<IdentityKeyMismatch> 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) {

View File

@ -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;

View File

@ -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);

View File

@ -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);
}

View File

@ -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<String> 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<RecipientSettings> 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 {

View File

@ -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<IdentityKeyMismatch>(),
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<IdentityKeyMismatch> getMismatches(String document) {

View File

@ -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();

View File

@ -55,11 +55,12 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> failures, int subscriptionId,
long expiresIn, long expireStarted, int readReceiptCount,
@Nullable Quote quote, @Nullable List<Contact> contacts)
@Nullable Quote quote, @Nullable List<Contact> 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;

View File

@ -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<IdentityKeyMismatch> mismatches,
List<NetworkFailure> 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;
}
}

View File

@ -27,9 +27,9 @@ public abstract class MmsMessageRecord extends MessageRecord {
long type, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> networkFailures, int subscriptionId, long expiresIn,
long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount,
@Nullable Quote quote, @NonNull List<Contact> contacts)
@Nullable Quote quote, @NonNull List<Contact> 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;

View File

@ -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<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId,
0, 0, slideDeck, readReceiptCount, null, Collections.emptyList());
0, 0, slideDeck, readReceiptCount, null, Collections.emptyList(), false);
this.contentLocation = contentLocation;
this.messageSize = messageSize;

View File

@ -47,12 +47,12 @@ public class SmsMessageRecord extends MessageRecord {
long type, long threadId,
int status, List<IdentityKeyMismatch> 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() {

View File

@ -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;

View File

@ -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<GroupRecord> 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> insertResult = smsDatabase.insertMessageInbox(groupMessage);

View File

@ -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,

View File

@ -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);

View File

@ -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> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
if (insertResult.isPresent()) {

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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));
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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<Long> 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<IceUpdateMessage> 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<Long> 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<Long> smsMessageId)
private void handleEndSessionMessage(@NonNull SignalServiceContent content,
@NonNull Optional<Long> 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<Long> 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<Long> 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<Long> smsMessageId)
throws MmsException
throws StorageFailedException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getMessageDestination(envelope, message);
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> 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<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> 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> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
if (insertResult.isPresent()) {
List<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId());
if (insertResult.isPresent()) {
List<DatabaseAttachment> 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<Long> 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> 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<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(envelope);
Optional<InsertResult> 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<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(envelope);
Optional<InsertResult> 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<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(envelope);
Optional<InsertResult> 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<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(envelope);
Optional<InsertResult> 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<Long> smsMessageId)
{
// Let's start ignoring these now
@ -842,45 +856,11 @@ public class PushDecryptJob extends ContextJob {
// }
}
private void handleUntrustedIdentityMessage(@NonNull SignalServiceEnvelope envelope,
@NonNull Optional<Long> 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> 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<InsertResult> insertPlaceholder(@NonNull SignalServiceEnvelope envelope) {
private Optional<InsertResult> 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;
}
}
}

View File

@ -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<NetworkFailure> existingNetworkFailures = message.getNetworkFailures();
List<IdentityKeyMismatch> existingIdentityMismatches = message.getIdentityKeyMismatches();
try {
Log.i(TAG, "Sending message: " + messageId);
List<Address> 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<SendMessageResult> results = deliver(message, target);
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Address.fromSerialized(result.getAddress().getNumber()))).toList();
List<IdentityKeyMismatch> identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Address.fromSerialized(result.getAddress().getNumber()), result.getIdentityFailure().getIdentityKey())).toList();
Set<Address> successAddresses = Stream.of(results).filter(result -> result.getSuccess() != null).map(result -> Address.fromSerialized(result.getAddress().getNumber())).collect(Collectors.toSet());
List<NetworkFailure> resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successAddresses.contains(failure.getAddress())).toList();
List<IdentityKeyMismatch> resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successAddresses.contains(failure.getAddress())).toList();
List<SendMessageResult> 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<NetworkFailure> 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<SendMessageResult> deliver(OutgoingMediaMessage message, @NonNull List<Address> destinations)
throws IOException, RecipientFormattingException, InvalidNumberException,
EncapsulatedExceptions, UndeliverableMessageException
UndeliverableMessageException, UntrustedIdentityException
{
String groupId = message.getRecipient().getAddress().toGroupString();
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
List<Address> recipients = getGroupMessageRecipients(groupId, messageId);
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
List<Attachment> scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments());
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(scaledAttachments);
Optional<Quote> quote = getQuoteFor(message);
List<SharedContact> sharedContacts = getSharedContactsFor(message);
List<SignalServiceAddress> addresses = Stream.of(destinations).map(this::getPushAddress).toList();
List<SignalServiceAddress> addresses;
if (filterAddress != null) addresses = getPushAddresses(filterAddress);
else addresses = getPushAddresses(recipients);
List<Optional<UnidentifiedAccessPair>> 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<SignalServiceAddress> getPushAddresses(Address address) {
List<SignalServiceAddress> addresses = new LinkedList<>();
addresses.add(getPushAddress(address));
return addresses;
}
private List<SignalServiceAddress> getPushAddresses(List<Address> addresses) {
return Stream.of(addresses).map(this::getPushAddress).toList();
}
private @NonNull List<Address> getGroupMessageRecipients(String groupId, long messageId) {
List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId);
if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getAddress).toList();

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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()),

View File

@ -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));
}

View File

@ -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<byte[]> 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<byte[]> profileKey = getProfileKey(message.getIndividualRecipient());
Optional<UnidentifiedAccessPair> 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);

View File

@ -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

View File

@ -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

View File

@ -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> 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> 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<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Recipient recipient) {
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
if (unidentifiedAccess.isPresent()) {
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
}
return Optional.absent();
}
}

View File

@ -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!");
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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<Attachment> attachments = new LinkedList<>();
private final List<Contact> sharedContacts = new LinkedList<>();
@ -35,7 +36,8 @@ public class IncomingMediaMessage {
List<Attachment> 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<String> relay,
boolean unidentified,
Optional<String> body,
Optional<SignalServiceGroup> group,
Optional<List<SignalServiceAttachment>> 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<Contact> getSharedContacts() {
return sharedContacts;
}
public boolean isUnidentified() {
return unidentified;
}
}

View File

@ -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<Attachment> attachments;
private final long sentTimeMillis;
private final int distributionType;
private final int subscriptionId;
private final long expiresIn;
private final QuoteModel outgoingQuote;
private final List<Contact> contacts = new LinkedList<>();
private final Recipient recipient;
protected final String body;
protected final List<Attachment> attachments;
private final long sentTimeMillis;
private final int distributionType;
private final int subscriptionId;
private final long expiresIn;
private final QuoteModel outgoingQuote;
private final List<NetworkFailure> networkFailures = new LinkedList<>();
private final List<IdentityKeyMismatch> identityKeyMismatches = new LinkedList<>();
private final List<Contact> contacts = new LinkedList<>();
public OutgoingMediaMessage(Recipient recipient, String message,
List<Attachment> attachments, long sentTimeMillis,
int subscriptionId, long expiresIn,
int distributionType, @Nullable QuoteModel outgoingQuote,
@NonNull List<Contact> contacts)
int distributionType,
@Nullable QuoteModel outgoingQuote,
@NonNull List<Contact> contacts,
@NonNull List<NetworkFailure> networkFailures,
@NonNull List<IdentityKeyMismatch> 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<Contact> 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<NetworkFailure> getNetworkFailures() {
return networkFailures;
}
public @NonNull List<IdentityKeyMismatch> 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;

View File

@ -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<Contact> 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) {

View File

@ -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 ");

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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";

View File

@ -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;
}

View File

@ -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<Recipient> participants;
@Nullable final String profileName;
final boolean seenInviteReminder;
final Optional<Integer> 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<Recipient> participants;
@Nullable final String profileName;
final boolean seenInviteReminder;
final Optional<Integer> 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<Recipient> 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;

View File

@ -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...");

View File

@ -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<Intent> 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);
}
}
}

View File

@ -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());
}
}

View File

@ -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<Boolean> callable = new Callable<Boolean>() {
@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;
}
};

View File

@ -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.<SignalServiceGroup>absent(), 0);
super(sender, 1, System.currentTimeMillis(), null, Optional.<SignalServiceGroup>absent(), 0, false);
}
@Override

View File

@ -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<SignalServiceGroup> 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<IncomingTextMessage> 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);
}
}

View File

@ -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);
}

View File

@ -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> insertResult = smsDatabase.insertMessageInbox(individualUpdate);

View File

@ -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);
}