diff --git a/build.gradle b/build.gradle index 7b6bdea53..8f8b62a7b 100644 --- a/build.gradle +++ b/build.gradle @@ -86,7 +86,7 @@ dependencies { compile 'org.conscrypt:conscrypt-android:2.0.0' compile 'org.signal:aesgcmprovider:0.0.3' - compile 'org.whispersystems:signal-service-android:2.13.2' + compile 'org.whispersystems:signal-service-android:2.13.3' compile 'org.whispersystems:webrtc-android:M74' @@ -190,7 +190,7 @@ dependencyVerification { 'com.google.android.exoplayer:exoplayer-core:b6ab34abac36bc2bc6934b7a50008162feca2c0fde91aaf1e8c1c22f2c16e2c0', 'org.conscrypt:conscrypt-android:400ca559a49b860a82862b22cee0e3110764bdcf7ee7c79e7479895c25cdfc09', 'org.signal:aesgcmprovider:6eb4422e8a618b3b76cb2096a3619d251f9e27989dc68307a1e5414c3710f2d1', - 'org.whispersystems:signal-service-android:11fd7307248429e5bf3274b57410e60b26492621b138a3d651cc8369d5aa6507', + 'org.whispersystems:signal-service-android:ac6f2c70c4111f9f982bbfc9b34063834afdce31333405bec10506a31f0961cd', 'org.whispersystems:webrtc-android:2f7befaa3b47a04d244a4eef7c03c2d49c1685a6f92fc505cd5f4ac7eca2dc18', 'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', @@ -258,7 +258,7 @@ dependencyVerification { 'com.android.support:support-annotations:5d5b9414f02d3fa0ee7526b8d5ddae0da67c8ecc8c4d63ffa6cf91488a93b927', 'com.android.support.constraint:constraint-layout-solver:2cafbe356f71c208013d021f32943904798cd6459e5107f9fe27000eb5bc2aef', 'org.signal:signal-metadata-android:02323bc29317fa9d3b62fab0b507c94ba2e9bcc4a78d588888ffd313853757b3', - 'org.whispersystems:signal-service-java:c48607b1fa3dbb67b5204adfd681c6948c7bbb43cc3e6502b65984a3ef53dc49', + 'org.whispersystems:signal-service-java:440777f4beca894f37b86258f1493fd5c2e87df02fb9a6331f0c82b842c91012', 'com.github.bumptech.glide:disklrucache:c1b1b6f5bbd01e2fcdc9d7f60913c8d338bdb65ed4a93bfa02b56f19daaade4b', 'com.github.bumptech.glide:annotations:bede99ef9f71517a4274bac18fd3e483e9f2b6108d7d6fe8f4949be4aa4d9512', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 3d32806be..af96049f8 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -925,6 +925,14 @@ public class MmsDatabase extends MessagingDatabase { long threadId, boolean forceSms, @Nullable SmsDatabase.InsertListener insertListener) throws MmsException + { + return insertMessageOutbox(message, threadId, forceSms, GroupReceiptDatabase.STATUS_UNDELIVERED, insertListener); + } + + public long insertMessageOutbox(@NonNull OutgoingMediaMessage message, + long threadId, boolean forceSms, int defaultReceiptStatus, + @Nullable SmsDatabase.InsertListener insertListener) + throws MmsException { long type = Types.BASE_SENDING_TYPE; @@ -975,7 +983,7 @@ public class MmsDatabase extends MessagingDatabase { GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); receiptDatabase.insert(Stream.of(members).map(Recipient::getAddress).toList(), - messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getSentTimeMillis()); + messageId, defaultReceiptStatus, message.getSentTimeMillis()); for (Address address : earlyDeliveryReceipts.keySet()) receiptDatabase.update(address, messageId, GroupReceiptDatabase.STATUS_DELIVERED, -1); for (Address address : earlyReadReceipts.keySet()) receiptDatabase.update(address, messageId, GroupReceiptDatabase.STATUS_READ, -1); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 2311b6e64..86b292b99 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -45,10 +45,12 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase; +import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo; import org.thoughtcrime.securesms.database.MessagingDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; @@ -122,6 +124,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Map; public class PushDecryptJob extends BaseJob { @@ -557,9 +560,11 @@ public class PushDecryptJob extends BaseJob { try { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - Long threadId; + Long threadId = null; - if (message.getMessage().isEndSession()) { + if (message.isRecipientUpdate()) { + handleGroupRecipientUpdate(message); + } else if (message.getMessage().isEndSession()) { threadId = handleSynchronizeSentEndSessionMessage(message); } else if (message.getMessage().isGroupUpdate()) { threadId = GroupMessageProcessor.process(context, content, message.getMessage(), true); @@ -775,15 +780,10 @@ public class PushDecryptJob extends BaseJob { database.beginTransaction(); try { - long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, null); + long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null); if (recipients.getAddress().isGroup()) { - GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); - List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipients.getAddress().toGroupString(), false); - - for (Recipient member : members) { - receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize())); - } + updateGroupReceiptStatus(message, messageId, recipients.getAddress().toGroupString()); } database.markAsSent(messageId, true); @@ -823,6 +823,51 @@ public class PushDecryptJob extends BaseJob { return threadId; } + private void handleGroupRecipientUpdate(@NonNull SentTranscriptMessage message) { + Recipient recipient = getSyncMessageDestination(message); + + if (!recipient.isGroupRecipient()) { + Log.w(TAG, "Got recipient update for a non-group message! Skipping."); + return; + } + + MmsSmsDatabase database = DatabaseFactory.getMmsSmsDatabase(context); + MessageRecord record = database.getMessageFor(message.getTimestamp(), + Address.fromSerialized(TextSecurePreferences.getLocalNumber(context))); + + if (record == null) { + Log.w(TAG, "Got recipient update for non-existing message! Skipping."); + return; + } + + if (!record.isMms()) { + Log.w(TAG, "Recipient update matched a non-MMS message! Skipping."); + return; + } + + updateGroupReceiptStatus(message, record.getId(), recipient.getAddress().toGroupString()); + } + + private void updateGroupReceiptStatus(@NonNull SentTranscriptMessage message, long messageId, @NonNull String groupString) { + GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); + List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupString, false); + Map localReceipts = Stream.of(receiptDatabase.getGroupReceiptInfo(messageId)) + .collect(Collectors.toMap(info -> info.getAddress().serialize(), GroupReceiptInfo::getStatus)); + + for (String address : message.getRecipients()) { + //noinspection ConstantConditions + if (localReceipts.containsKey(address) && localReceipts.get(address) < GroupReceiptDatabase.STATUS_UNDELIVERED) { + receiptDatabase.update(Address.fromSerialized(address), messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getTimestamp()); + } else if (!localReceipts.containsKey(address)) { + receiptDatabase.insert(Collections.singletonList(Address.fromSerialized(address)), messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getTimestamp()); + } + } + + for (Recipient member : members) { + receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize())); + } + } + private void handleTextMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) @@ -867,7 +912,6 @@ public class PushDecryptJob extends BaseJob { private long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage message) throws MmsException { - Recipient recipient = getSyncMessageDestination(message); String body = message.getMessage().getBody().or(""); long expiresInMillis = message.getMessage().getExpiresInSeconds() * 1000L; @@ -886,15 +930,10 @@ public class PushDecryptJob extends BaseJob { OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList()); outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage); - messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null); + messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null); database = DatabaseFactory.getMmsDatabase(context); - GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); - List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), false); - - for (Recipient member : members) { - receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize())); - } + updateGroupReceiptStatus(message, messageId, recipient.getAddress().toGroupString()); } else { OutgoingTextMessage outgoingTextMessage = new OutgoingEncryptedMessage(recipient, body, expiresInMillis); diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index ba60310db..81ea9a0f7 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.GroupReceiptDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; @@ -32,6 +33,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.util.GroupUtil; +import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; @@ -236,6 +238,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { List addresses = Stream.of(destinations).map(this::getPushAddress).toList(); List attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList(); List attachmentPointers = getAttachmentPointersFor(attachments); + boolean isRecipientUpdate = destinations.size() != DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId).size(); List> unidentifiedAccess = Stream.of(addresses) .map(address -> Address.fromSerialized(address.getNumber())) @@ -255,7 +258,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { .asGroupMessage(group) .build(); - return messageSender.sendMessage(addresses, unidentifiedAccess, groupDataMessage); + return messageSender.sendMessage(addresses, unidentifiedAccess, isRecipientUpdate, groupDataMessage); } else { SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedId(groupId)); SignalServiceDataMessage groupMessage = SignalServiceDataMessage.newBuilder() @@ -272,7 +275,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { .withPreviews(previews) .build(); - return messageSender.sendMessage(addresses, unidentifiedAccess, groupMessage); + return messageSender.sendMessage(addresses, unidentifiedAccess, isRecipientUpdate, groupMessage); } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index d272f2676..8cf7bdef1 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -308,7 +308,8 @@ public abstract class PushSendJob extends SendJob { message.getTimestamp(), message, message.getExpiresInSeconds(), - Collections.singletonMap(localNumber, syncAccess.isPresent())); + Collections.singletonMap(localNumber, syncAccess.isPresent()), + false); return SignalServiceSyncMessage.forSentTranscript(transcript); }