diff --git a/build.gradle b/build.gradle index 5b4ffb1e62..ce0788db7f 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,7 @@ dependencies { compile 'org.whispersystems:jobmanager:1.0.2' compile 'org.whispersystems:libpastelog:1.0.7' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - compile 'org.whispersystems:signal-service-android:2.2.0' + compile 'org.whispersystems:signal-service-android:2.3.0' compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0' compile 'com.google.zxing:core:3.2.1' @@ -130,7 +130,7 @@ dependencyVerification { 'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181', 'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88', 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb', - 'org.whispersystems:signal-service-android:96a926b0bfd1df8b66be2b574e8b8d6ef1862f715b0f1a5457a2038b28d3ad1b', + 'org.whispersystems:signal-service-android:2831fdde52117615a7ec479d76ba9cbcdc51d0845db28723981bd0e9426aadb7', 'com.h6ah4i.android.compat:mulsellistprefcompat:47167c5cb796de1a854788e9ff318358e36c8fb88123baaa6e38fb78511dfabe', 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', 'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729', @@ -140,7 +140,7 @@ dependencyVerification { 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f', 'org.whispersystems:signal-protocol-android:d83cb3d15b667fc2543fa18ce80791c72c053e8ac54fc2941f0429a5944ca691', - 'org.whispersystems:signal-service-java:7932363fec666fdc0b4b424eeca4bdca235f6bf2f226fb6a6ff742c49fc37087', + 'org.whispersystems:signal-service-java:f6541d4e4f8bd83f618696db7ff6c78215110ed82de46d19aa541a64ec69226a', 'com.google.android.gms:play-services-basement:e1d29b21e02fd2a63e5a31807415cbb17a59568e27e3254181c01ffae10659bf', 'org.whispersystems:curve25519-android:d6a3ef3a70622af4c728b7fe5f8fdfc9e6cd39b1d39b2c77e7a2add9d876bc23', 'org.whispersystems:signal-protocol-java:d518d52eeb3c44210e0b6c687360848a87afbaee0bdf42e2a8dd9974d54fdb3a', diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 9ae7aa14a0..d3d2ae4b40 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -95,6 +95,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MmsSmsColumns.Types; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType; @@ -566,6 +567,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this) .setBlocked(recipients, false); + + ApplicationContext.getInstance(ConversationActivity.this) + .getJobManager() + .add(new MultiDeviceBlockedUpdateJob(ConversationActivity.this)); + return null; } }.execute(); diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java index 068e569f55..863df016ae 100644 --- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java +++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java @@ -33,6 +33,8 @@ import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState; +import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; +import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.preferences.AdvancedRingtonePreference; import org.thoughtcrime.securesms.preferences.ColorPreference; import org.thoughtcrime.securesms.recipients.RecipientFactory; @@ -357,8 +359,13 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi new AsyncTask() { @Override protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientPreferenceDatabase(getActivity()) + Context context = getActivity(); + DatabaseFactory.getRecipientPreferenceDatabase(context) .setColor(recipients, selectedColor); + + ApplicationContext.getInstance(context) + .getJobManager() + .add(new MultiDeviceContactUpdateJob(context, recipients.getPrimaryRecipient().getRecipientId())); return null; } }.execute(); @@ -459,8 +466,14 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi new AsyncTask() { @Override protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientPreferenceDatabase(getActivity()) + Context context = getActivity(); + + DatabaseFactory.getRecipientPreferenceDatabase(context) .setBlocked(recipients, blocked); + + ApplicationContext.getInstance(context) + .getJobManager() + .add(new MultiDeviceBlockedUpdateJob(context)); return null; } }.execute(); diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java index 99a3c07112..222b1e83b5 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java @@ -66,7 +66,7 @@ public class ContactAccessor { final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME}; List pushNumbers = TextSecureDirectory.getInstance(context).getActiveNumbers(); - final Collection lookupData = new ArrayList(pushNumbers.size()); + final Collection lookupData = new ArrayList<>(pushNumbers.size()); for (String pushNumber : pushNumbers) { Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(pushNumber)); diff --git a/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java index 8578bd1d42..c20c3f7650 100644 --- a/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java +++ b/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java @@ -11,6 +11,7 @@ import android.support.annotation.Nullable; import android.util.Log; import org.thoughtcrime.securesms.color.MaterialColor; +import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; @@ -80,6 +81,10 @@ public class RecipientPreferenceDatabase extends Database { return cursor; } + public BlockedReader readerForBlocked(Cursor cursor) { + return new BlockedReader(context, cursor); + } + public Optional getRecipientsPreferences(@NonNull long[] recipients) { Arrays.sort(recipients); @@ -255,4 +260,28 @@ public class RecipientPreferenceDatabase extends Database { return expireMessages; } } + + public static class BlockedReader { + + private final Context context; + private final Cursor cursor; + + public BlockedReader(Context context, Cursor cursor) { + this.context = context; + this.cursor = cursor; + } + + public @NonNull Recipients getCurrent() { + String recipientIds = cursor.getString(cursor.getColumnIndexOrThrow(RECIPIENT_IDS)); + return RecipientFactory.getRecipientsForIds(context, recipientIds, false); + } + + public @Nullable Recipients getNext() { + if (!cursor.moveToNext()) { + return null; + } + + return getCurrent(); + } + } } diff --git a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java index c1945264e3..42fd91c779 100644 --- a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java @@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.jobs.CleanPreKeysJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob; import org.thoughtcrime.securesms.jobs.GcmRefreshJob; +import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; @@ -45,6 +46,7 @@ import dagger.Provides; MultiDeviceContactUpdateJob.class, MultiDeviceGroupUpdateJob.class, MultiDeviceReadUpdateJob.class, + MultiDeviceBlockedUpdateJob.class, DeviceListFragment.class, RefreshAttributesJob.class, GcmRefreshJob.class}) diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java new file mode 100644 index 0000000000..8b1956a591 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java @@ -0,0 +1,79 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; + +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase; +import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.BlockedReader; +import org.thoughtcrime.securesms.dependencies.InjectableType; +import org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory; +import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.jobqueue.requirements.NetworkRequirement; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; +import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import javax.inject.Inject; + +public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements InjectableType { + + private static final long serialVersionUID = 1L; + + private static final String TAG = MultiDeviceBlockedUpdateJob.class.getSimpleName(); + + @Inject transient TextSecureMessageSenderFactory messageSenderFactory; + + public MultiDeviceBlockedUpdateJob(Context context) { + super(context, JobParameters.newBuilder() + .withRequirement(new NetworkRequirement(context)) + .withRequirement(new MasterSecretRequirement(context)) + .withGroupId(MultiDeviceBlockedUpdateJob.class.getSimpleName()) + .withPersistence() + .create()); + } + + @Override + public void onRun(MasterSecret masterSecret) + throws IOException, UntrustedIdentityException + { + RecipientPreferenceDatabase database = DatabaseFactory.getRecipientPreferenceDatabase(context); + SignalServiceMessageSender messageSender = messageSenderFactory.create(); + BlockedReader reader = database.readerForBlocked(database.getBlocked()); + List blocked = new LinkedList<>(); + + Recipients recipients; + + while ((recipients = reader.getNext()) != null) { + if (recipients.isSingleRecipient()) { + blocked.add(recipients.getPrimaryRecipient().getNumber()); + } + } + + messageSender.sendMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blocked))); + } + + @Override + public boolean onShouldRetryThrowable(Exception exception) { + if (exception instanceof PushNetworkException) return true; + return false; + } + + @Override + public void onAdded() { + + } + + @Override + public void onCanceled() { + + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 1cc85a1571..aca0644935 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -6,6 +6,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.provider.ContactsContract; +import android.text.TextUtils; import android.util.Log; import org.thoughtcrime.securesms.contacts.ContactAccessor; @@ -14,6 +15,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.Recipients; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libsignal.util.guava.Optional; @@ -43,18 +47,56 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje @Inject transient TextSecureMessageSenderFactory messageSenderFactory; + private final long recipientId; + public MultiDeviceContactUpdateJob(Context context) { + this(context, -1); + } + + public MultiDeviceContactUpdateJob(Context context, long recipientId) { super(context, JobParameters.newBuilder() .withRequirement(new NetworkRequirement(context)) .withRequirement(new MasterSecretRequirement(context)) .withGroupId(MultiDeviceContactUpdateJob.class.getSimpleName()) .withPersistence() .create()); + + this.recipientId = recipientId; } @Override public void onRun(MasterSecret masterSecret) throws IOException, UntrustedIdentityException, NetworkException + { + if (recipientId <= 0) generateFullContactUpdate(); + else generateSingleContactUpdate(recipientId); + } + + private void generateSingleContactUpdate(long recipientId) + throws IOException, UntrustedIdentityException, NetworkException + { + SignalServiceMessageSender messageSender = messageSenderFactory.create(); + File contactDataFile = createTempFile("multidevice-contact-update"); + + try { + DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile)); + Recipient recipient = RecipientFactory.getRecipientForId(context, recipientId, false); + + out.write(new DeviceContact(recipient.getNumber(), + Optional.fromNullable(recipient.getName()), + getAvatar(recipient.getContactUri()), + Optional.fromNullable(recipient.getColor().serialize()))); + + out.close(); + sendUpdate(messageSender, contactDataFile); + + } finally { + if (contactDataFile != null) contactDataFile.delete(); + } + } + + private void generateFullContactUpdate() + throws IOException, UntrustedIdentityException, NetworkException { SignalServiceMessageSender messageSender = messageSenderFactory.create(); File contactDataFile = createTempFile("multidevice-contact-update"); @@ -67,8 +109,9 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactData.id)); String number = contactData.numbers.get(0).number; Optional name = Optional.fromNullable(contactData.name); + Optional color = getColor(number); - out.write(new DeviceContact(number, name, getAvatar(contactUri))); + out.write(new DeviceContact(number, name, getAvatar(contactUri), color)); } out.close(); @@ -95,6 +138,15 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje } + private Optional getColor(String number) { + if (!TextUtils.isEmpty(number)) { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, number, false); + return Optional.of(recipients.getColor().serialize()); + } else { + return Optional.absent(); + } + } + private void sendUpdate(SignalServiceMessageSender messageSender, File contactsFile) throws IOException, UntrustedIdentityException, NetworkException { diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index b8d99772ee..8dc3355b7c 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -286,6 +286,12 @@ public class PushDecryptJob extends ContextJob { .getJobManager() .add(new MultiDeviceGroupUpdateJob(getContext())); } + + if (message.isBlockedListRequest()) { + ApplicationContext.getInstance(context) + .getJobManager() + .add(new MultiDeviceBlockedUpdateJob(getContext())); + } } private void handleSynchronizeReadMessage(@NonNull MasterSecretUnion masterSecret,