diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java index fd038b98f3..1a1569eee0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java @@ -11,6 +11,7 @@ import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.whispersystems.libsignal.util.Pair; import java.util.Collection; import java.util.LinkedList; @@ -67,14 +68,24 @@ public class GroupReceiptDatabase extends Database { new String[] {String.valueOf(mmsId), recipientId.serialize(), String.valueOf(status)}); } - public void setUnidentified(RecipientId recipientId, long mmsId, boolean unidentified) { - SQLiteDatabase db = databaseHelper.getWritableDatabase(); - ContentValues values = new ContentValues(1); - values.put(UNIDENTIFIED, unidentified ? 1 : 0); + public void setUnidentified(Collection> results, long mmsId) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); - db.update(TABLE_NAME, values, MMS_ID + " = ? AND " + RECIPIENT_ID + " = ?", - new String[] {String.valueOf(mmsId), recipientId.serialize()}); + db.beginTransaction(); + try { + String query = MMS_ID + " = ? AND " + RECIPIENT_ID + " = ?"; + for (Pair result : results) { + ContentValues values = new ContentValues(1); + values.put(UNIDENTIFIED, result.second() ? 1 : 0); + + db.update(TABLE_NAME, values, query, new String[]{ String.valueOf(mmsId), result.first().serialize()}); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } } public @NonNull List getGroupReceiptInfo(long mmsId) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index e44be6e50e..9b878519a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil; 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; @@ -58,7 +59,10 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupC import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; public class PushGroupSendJob extends PushSendJob { @@ -165,21 +169,25 @@ public class PushGroupSendJob extends PushSendJob { RecipientUtil.shareProfileIfFirstSecureMessage(context, groupRecipient); } - List target; + List target; - if (filterRecipient != null) target = Collections.singletonList(Recipient.resolved(filterRecipient).getId()); - else if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).toList(); + if (filterRecipient != null) target = Collections.singletonList(Recipient.resolved(filterRecipient)); + else if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(nf -> Recipient.resolved(nf.getRecipientId(context))).toList(); else target = getGroupMessageRecipients(groupRecipient.requireGroupId(), messageId); + Map idByE164 = Stream.of(target).filter(Recipient::hasE164).collect(Collectors.toMap(Recipient::requireE164, r -> r)); + Map idByUuid = Stream.of(target).filter(Recipient::hasUuid).collect(Collectors.toMap(Recipient::requireUuid, r -> r)); + List results = deliver(message, groupRecipient, target); Log.i(TAG, JobLogger.format(this, "Finished send.")); - List networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Recipient.externalPush(context, result.getAddress()).getId())).toList(); - List identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Recipient.externalPush(context, result.getAddress()).getId(), result.getIdentityFailure().getIdentityKey())).toList(); - Set successIds = Stream.of(results).filter(result -> result.getSuccess() != null).map(SendMessageResult::getAddress).map(a -> Recipient.externalPush(context, a).getId()).collect(Collectors.toSet()); - List resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList(); - List resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList(); - List successes = Stream.of(results).filter(result -> result.getSuccess() != null).toList(); + List networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(findId(result.getAddress(), idByE164, idByUuid))).toList(); + List identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(findId(result.getAddress(), idByE164, idByUuid), result.getIdentityFailure().getIdentityKey())).toList(); + List successes = Stream.of(results).filter(result -> result.getSuccess() != null).toList(); + List> successUnidentifiedStatus = Stream.of(successes).map(result -> new Pair<>(findId(result.getAddress(), idByE164, idByUuid), result.getSuccess().isUnidentified())).toList(); + Set successIds = Stream.of(successUnidentifiedStatus).map(Pair::first).collect(Collectors.toSet()); + List resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList(); + List resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList(); for (NetworkFailure resolvedFailure : resolvedNetworkFailures) { database.removeFailure(messageId, resolvedFailure); @@ -199,11 +207,7 @@ public class PushGroupSendJob extends PushSendJob { database.addMismatchedIdentity(messageId, mismatch.getRecipientId(context), mismatch.getIdentityKey()); } - for (SendMessageResult success : successes) { - DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Recipient.externalPush(context, success.getAddress()).getId(), - messageId, - success.getSuccess().isUnidentified()); - } + DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(successUnidentifiedStatus, messageId); if (existingNetworkFailures.isEmpty() && networkFailures.isEmpty() && identityMismatches.isEmpty() && existingIdentityMismatches.isEmpty()) { database.markAsSent(messageId, true); @@ -245,7 +249,20 @@ public class PushGroupSendJob extends PushSendJob { DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); } - private List deliver(OutgoingMediaMessage message, @NonNull Recipient groupRecipient, @NonNull List destinations) + private static @NonNull RecipientId findId(@NonNull SignalServiceAddress address, + @NonNull Map byE164, + @NonNull Map byUuid) + { + if (address.getNumber().isPresent() && byE164.containsKey(address.getNumber().get())) { + return Objects.requireNonNull(byE164.get(address.getNumber().get())).getId(); + } else if (address.getUuid().isPresent() && byUuid.containsKey(address.getUuid().get())) { + return Objects.requireNonNull(byUuid.get(address.getUuid().get())).getId(); + } else { + throw new IllegalStateException("Found an address that was never provided!"); + } + } + + private List deliver(OutgoingMediaMessage message, @NonNull Recipient groupRecipient, @NonNull List destinations) throws IOException, UntrustedIdentityException, UndeliverableMessageException { rotateSenderCertificateIfNecessary(); @@ -256,13 +273,12 @@ public class PushGroupSendJob extends PushSendJob { Optional sticker = getStickerFor(message); List sharedContacts = getSharedContactsFor(message); List previews = getPreviewsFor(message); - List addresses = Stream.of(destinations).map(Recipient::resolved).map(this::getPushAddress).toList(); + 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(destinations) - .map(Recipient::resolved) .map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient)) .toList(); @@ -330,17 +346,17 @@ public class PushGroupSendJob extends PushSendJob { } } - private @NonNull List getGroupMessageRecipients(@NonNull GroupId groupId, long messageId) { + private @NonNull List getGroupMessageRecipients(@NonNull GroupId groupId, long messageId) { List destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId); if (!destinations.isEmpty()) { - return Stream.of(destinations).map(GroupReceiptInfo::getRecipientId).toList(); + return Stream.of(destinations).map(GroupReceiptInfo::getRecipientId).map(Recipient::resolved).toList(); } - List members = Stream.of(DatabaseFactory.getGroupDatabase(context) - .getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF)) - .map(Recipient::getId) - .toList(); + List members = Stream.of(DatabaseFactory.getGroupDatabase(context) + .getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF)) + .map(Recipient::resolve) + .toList(); if (members.size() > 0) { Log.w(TAG, "No destinations found for group message " + groupId + " using current group membership"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java index 41c59a1b75..5cec4ddc52 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -1216,9 +1216,10 @@ public final class PushProcessMessageJob extends BaseJob { } } - for (Recipient member : members) { - receiptDatabase.setUnidentified(member.getId(), messageId, message.isUnidentified(member.requireServiceId())); - } + List> unidentifiedStatus = Stream.of(members) + .map(m -> new org.whispersystems.libsignal.util.Pair<>(m.getId(), message.isUnidentified(m.requireServiceId()))) + .toList(); + receiptDatabase.setUnidentified(unidentifiedStatus, messageId); } private void handleTextMessage(@NonNull SignalServiceContent content,