kopia lustrzana https://github.com/ryukoposting/Signal-Android
Incrementally insert MSL entries for legacy group sends.
rodzic
acc825971b
commit
7f0a0bef5a
|
@ -148,26 +148,33 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
)
|
||||
}
|
||||
|
||||
fun insertIfPossible(recipientId: RecipientId, sentTimestamp: Long, sendMessageResult: SendMessageResult, contentHint: ContentHint, messageId: MessageId) {
|
||||
if (!FeatureFlags.senderKey()) return
|
||||
/** @return The ID of the inserted entry, or -1 if none was inserted. Can be used with [addRecipientToExistingEntryIfPossible] */
|
||||
fun insertIfPossible(recipientId: RecipientId, sentTimestamp: Long, sendMessageResult: SendMessageResult, contentHint: ContentHint, messageId: MessageId): Long {
|
||||
if (!FeatureFlags.senderKey()) return -1
|
||||
|
||||
if (sendMessageResult.isSuccess && sendMessageResult.success.content.isPresent) {
|
||||
val recipientDevice = listOf(RecipientDevice(recipientId, sendMessageResult.success.devices))
|
||||
insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, listOf(messageId))
|
||||
return insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, listOf(messageId))
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
fun insertIfPossible(recipientId: RecipientId, sentTimestamp: Long, sendMessageResult: SendMessageResult, contentHint: ContentHint, messageIds: List<MessageId>) {
|
||||
if (!FeatureFlags.senderKey()) return
|
||||
/** @return The ID of the inserted entry, or -1 if none was inserted. Can be used with [addRecipientToExistingEntryIfPossible] */
|
||||
fun insertIfPossible(recipientId: RecipientId, sentTimestamp: Long, sendMessageResult: SendMessageResult, contentHint: ContentHint, messageIds: List<MessageId>): Long {
|
||||
if (!FeatureFlags.senderKey()) return -1
|
||||
|
||||
if (sendMessageResult.isSuccess && sendMessageResult.success.content.isPresent) {
|
||||
val recipientDevice = listOf(RecipientDevice(recipientId, sendMessageResult.success.devices))
|
||||
insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, messageIds)
|
||||
return insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, messageIds)
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
fun insertIfPossible(sentTimestamp: Long, possibleRecipients: List<Recipient>, results: List<SendMessageResult>, contentHint: ContentHint, relatedMessageId: Long, isRelatedMessageMms: Boolean) {
|
||||
if (!FeatureFlags.senderKey()) return
|
||||
/** @return The ID of the inserted entry, or -1 if none was inserted. Can be used with [addRecipientToExistingEntryIfPossible] */
|
||||
fun insertIfPossible(sentTimestamp: Long, possibleRecipients: List<Recipient>, results: List<SendMessageResult>, contentHint: ContentHint, messageId: MessageId): Long {
|
||||
if (!FeatureFlags.senderKey()) return -1
|
||||
|
||||
val recipientsByUuid: Map<UUID, Recipient> = possibleRecipients.filter(Recipient::hasUuid).associateBy(Recipient::requireUuid, { it })
|
||||
val recipientsByE164: Map<String, Recipient> = possibleRecipients.filter(Recipient::hasE164).associateBy(Recipient::requireE164, { it })
|
||||
|
@ -185,12 +192,40 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
RecipientDevice(recipient.id, result.success.devices)
|
||||
}
|
||||
|
||||
if (recipientDevices.isEmpty()) {
|
||||
return -1
|
||||
}
|
||||
|
||||
val content: SignalServiceProtos.Content = results.first { it.isSuccess && it.success.content.isPresent }.success.content.get()
|
||||
|
||||
insert(recipientDevices, sentTimestamp, content, contentHint, listOf(MessageId(relatedMessageId, isRelatedMessageMms)))
|
||||
return insert(recipientDevices, sentTimestamp, content, contentHint, listOf(messageId))
|
||||
}
|
||||
|
||||
private fun insert(recipients: List<RecipientDevice>, dateSent: Long, content: SignalServiceProtos.Content, contentHint: ContentHint, messageIds: List<MessageId>) {
|
||||
fun addRecipientToExistingEntryIfPossible(payloadId: Long, recipientId: RecipientId, sendMessageResult: SendMessageResult) {
|
||||
if (!FeatureFlags.senderKey()) return
|
||||
|
||||
if (sendMessageResult.isSuccess && sendMessageResult.success.content.isPresent) {
|
||||
val db = databaseHelper.writableDatabase
|
||||
|
||||
db.beginTransaction()
|
||||
try {
|
||||
sendMessageResult.success.devices.forEach { device ->
|
||||
val recipientValues = ContentValues().apply {
|
||||
put(RecipientTable.PAYLOAD_ID, payloadId)
|
||||
put(RecipientTable.RECIPIENT_ID, recipientId.serialize())
|
||||
put(RecipientTable.DEVICE, device)
|
||||
}
|
||||
|
||||
db.insert(RecipientTable.TABLE_NAME, null, recipientValues)
|
||||
}
|
||||
db.setTransactionSuccessful()
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insert(recipients: List<RecipientDevice>, dateSent: Long, content: SignalServiceProtos.Content, contentHint: ContentHint, messageIds: List<MessageId>): Long {
|
||||
val db = databaseHelper.writableDatabase
|
||||
|
||||
db.beginTransaction()
|
||||
|
@ -226,6 +261,8 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
}
|
||||
|
||||
db.setTransactionSuccessful()
|
||||
|
||||
return payloadId
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
}
|
||||
|
|
|
@ -17,10 +17,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult;
|
||||
import org.thoughtcrime.securesms.jobs.LeaveGroupJob;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
|
@ -187,51 +184,12 @@ final class GroupManagerV1 {
|
|||
|
||||
@WorkerThread
|
||||
static boolean leaveGroup(@NonNull Context context, @NonNull GroupId.V1 groupId) {
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
Optional<OutgoingGroupUpdateMessage> leaveMessage = createGroupLeaveMessage(context, groupId, groupRecipient);
|
||||
|
||||
if (threadId != -1 && leaveMessage.isPresent()) {
|
||||
try {
|
||||
long id = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(leaveMessage.get(), threadId, false, null);
|
||||
DatabaseFactory.getMmsDatabase(context).markAsSent(id, true);
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, "Failed to insert leave message.", e);
|
||||
}
|
||||
ApplicationDependencies.getJobManager().add(LeaveGroupJob.create(groupRecipient));
|
||||
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
groupDatabase.setActive(groupId, false);
|
||||
groupDatabase.remove(groupId, Recipient.self().getId());
|
||||
return true;
|
||||
} else {
|
||||
Log.i(TAG, "Group was already inactive. Skipping.");
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
static boolean silentLeaveGroup(@NonNull Context context, @NonNull GroupId.V1 groupId) {
|
||||
if (DatabaseFactory.getGroupDatabase(context).isActive(groupId)) {
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
Optional<OutgoingGroupUpdateMessage> leaveMessage = createGroupLeaveMessage(context, groupId, groupRecipient);
|
||||
|
||||
if (threadId != -1 && leaveMessage.isPresent()) {
|
||||
ApplicationDependencies.getJobManager().add(LeaveGroupJob.create(groupRecipient));
|
||||
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
groupDatabase.setActive(groupId, false);
|
||||
groupDatabase.remove(groupId, Recipient.self().getId());
|
||||
return true;
|
||||
} else {
|
||||
Log.w(TAG, "Failed to leave group.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Group was already inactive. Skipping.");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
|
@ -9,8 +9,10 @@ import com.annimon.stream.Stream;
|
|||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.messages.GroupSendUtil;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
@ -151,18 +153,18 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
private @NonNull List<Recipient> deliver(@NonNull Recipient conversationRecipient, @NonNull List<Recipient> destinations)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, destinations);
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, destinations);;
|
||||
SignalServiceDataMessage.Builder dataMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withTimestamp(System.currentTimeMillis())
|
||||
.withGroupCallUpdate(new SignalServiceDataMessage.GroupCallUpdate(eraId));
|
||||
SignalServiceDataMessage.Builder dataMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withTimestamp(System.currentTimeMillis())
|
||||
.withGroupCallUpdate(new SignalServiceDataMessage.GroupCallUpdate(eraId));
|
||||
|
||||
if (conversationRecipient.isGroup()) {
|
||||
GroupUtil.setDataMessageGroupContext(context, dataMessage, conversationRecipient.requireGroupId().requirePush());
|
||||
}
|
||||
GroupUtil.setDataMessageGroupContext(context, dataMessage, conversationRecipient.requireGroupId().requirePush());
|
||||
|
||||
List<SendMessageResult> results = messageSender.sendDataMessage(addresses, unidentifiedAccess, false, ContentHint.DEFAULT, dataMessage.build());
|
||||
List<SendMessageResult> results = GroupSendUtil.sendUnresendableDataMessage(context,
|
||||
conversationRecipient.requireGroupId().requireV2(),
|
||||
destinations,
|
||||
false,
|
||||
ContentHint.DEFAULT,
|
||||
dataMessage.build());
|
||||
|
||||
return GroupSendJobHelper.getCompletedSends(context, results);
|
||||
}
|
||||
|
|
|
@ -89,7 +89,6 @@ public final class JobManagerFactories {
|
|||
put(GroupCallPeekWorkerJob.KEY, new GroupCallPeekWorkerJob.Factory());
|
||||
put(GroupV2UpdateSelfProfileKeyJob.KEY, new GroupV2UpdateSelfProfileKeyJob.Factory());
|
||||
put(KbsEnclaveMigrationWorkerJob.KEY, new KbsEnclaveMigrationWorkerJob.Factory());
|
||||
put(LeaveGroupJob.KEY, new LeaveGroupJob.Factory());
|
||||
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
|
||||
put(LocalBackupJobApi29.KEY, new LocalBackupJobApi29.Factory());
|
||||
put(MarkerJob.KEY, new MarkerJob.Factory());
|
||||
|
@ -205,6 +204,7 @@ public final class JobManagerFactories {
|
|||
put("StorageKeyRotationMigrationJob", new PassingMigrationJob.Factory());
|
||||
put("StorageSyncJob", new StorageSyncJob.Factory());
|
||||
put("WakeGroupV2Job", new FailingJob.Factory());
|
||||
put("LeaveGroupJob", new FailingJob.Factory());
|
||||
}};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
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.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Normally, we can do group leaves via {@link PushGroupSendJob}. However, that job relies on a
|
||||
* message being present in the database, which is not true if the user selects a message request
|
||||
* option that deletes and leaves at the same time.
|
||||
*
|
||||
* This job tracks all send state within the job and does not require a message in the database to
|
||||
* work.
|
||||
*/
|
||||
public class LeaveGroupJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "LeaveGroupJob";
|
||||
|
||||
private static final String TAG = Log.tag(LeaveGroupJob.class);
|
||||
|
||||
private static final String KEY_GROUP_ID = "group_id";
|
||||
private static final String KEY_GROUP_NAME = "name";
|
||||
private static final String KEY_MEMBERS = "members";
|
||||
private static final String KEY_RECIPIENTS = "recipients";
|
||||
|
||||
private final GroupId.Push groupId;
|
||||
private final String name;
|
||||
private final List<RecipientId> members;
|
||||
private final List<RecipientId> recipients;
|
||||
|
||||
public static @NonNull LeaveGroupJob create(@NonNull Recipient group) {
|
||||
List<RecipientId> members = Stream.of(group.resolve().getParticipants()).map(Recipient::getId).toList();
|
||||
members.remove(Recipient.self().getId());
|
||||
|
||||
return new LeaveGroupJob(group.getGroupId().get().requirePush(),
|
||||
group.resolve().getDisplayName(ApplicationDependencies.getApplication()),
|
||||
members,
|
||||
members,
|
||||
new Parameters.Builder()
|
||||
.setQueue(group.getId().toQueueKey())
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build());
|
||||
}
|
||||
|
||||
private LeaveGroupJob(@NonNull GroupId.Push groupId,
|
||||
@NonNull String name,
|
||||
@NonNull List<RecipientId> members,
|
||||
@NonNull List<RecipientId> recipients,
|
||||
@NonNull Parameters parameters)
|
||||
{
|
||||
super(parameters);
|
||||
this.groupId = groupId;
|
||||
this.name = name;
|
||||
this.members = Collections.unmodifiableList(members);
|
||||
this.recipients = recipients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return new Data.Builder().putString(KEY_GROUP_ID, Base64.encodeBytes(groupId.getDecodedId()))
|
||||
.putString(KEY_GROUP_NAME, name)
|
||||
.putString(KEY_MEMBERS, RecipientId.toSerializedList(members))
|
||||
.putString(KEY_RECIPIENTS, RecipientId.toSerializedList(recipients))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
List<Recipient> completions = deliver(context, groupId, name, members, recipients);
|
||||
|
||||
for (Recipient completion : completions) {
|
||||
recipients.remove(completion.getId());
|
||||
}
|
||||
|
||||
Log.i(TAG, "Completed now: " + completions.size() + ", Remaining: " + recipients.size());
|
||||
|
||||
if (!recipients.isEmpty()) {
|
||||
Log.w(TAG, "Still need to send to " + recipients.size() + " recipients. Retrying.");
|
||||
throw new RetryLaterException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof IOException || e instanceof RetryLaterException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
}
|
||||
|
||||
private static @NonNull List<Recipient> deliver(@NonNull Context context,
|
||||
@NonNull GroupId.Push groupId,
|
||||
@NonNull String name,
|
||||
@NonNull List<RecipientId> members,
|
||||
@NonNull List<RecipientId> destinations)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddresses(context, destinations);
|
||||
List<SignalServiceAddress> memberAddresses = RecipientUtil.toSignalServiceAddresses(context, members);
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, Stream.of(destinations).map(Recipient::resolved).toList());
|
||||
SignalServiceGroup serviceGroup = new SignalServiceGroup(SignalServiceGroup.Type.QUIT, groupId.getDecodedId(), name, memberAddresses, null);
|
||||
SignalServiceDataMessage.Builder dataMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withTimestamp(System.currentTimeMillis())
|
||||
.asGroupMessage(serviceGroup);
|
||||
|
||||
|
||||
List<SendMessageResult> results = messageSender.sendDataMessage(addresses, unidentifiedAccess, false, ContentHint.DEFAULT, dataMessage.build());
|
||||
|
||||
return GroupSendJobHelper.getCompletedSends(context, results);
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<LeaveGroupJob> {
|
||||
@Override
|
||||
public @NonNull LeaveGroupJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new LeaveGroupJob(GroupId.v1orThrow(Base64.decodeOrThrow(data.getString(KEY_GROUP_ID))),
|
||||
data.getString(KEY_GROUP_NAME),
|
||||
RecipientId.fromSerializedList(data.getString(KEY_MEMBERS)),
|
||||
RecipientId.fromSerializedList(data.getString(KEY_RECIPIENTS)),
|
||||
parameters);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.jobmanager.Data;
|
|||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.messages.GroupSendUtil;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
@ -107,7 +108,7 @@ public class ProfileKeySendJob extends BaseJob {
|
|||
}
|
||||
|
||||
List<Recipient> destinations = Stream.of(recipients).map(Recipient::resolved).toList();
|
||||
List<Recipient> completions = deliver(conversationRecipient, destinations);
|
||||
List<Recipient> completions = deliver(destinations);
|
||||
|
||||
for (Recipient completion : completions) {
|
||||
recipients.remove(completion.getId());
|
||||
|
@ -147,20 +148,13 @@ public class ProfileKeySendJob extends BaseJob {
|
|||
|
||||
}
|
||||
|
||||
private List<Recipient> deliver(@NonNull Recipient conversationRecipient, @NonNull List<Recipient> destinations) throws IOException, UntrustedIdentityException {
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, destinations);
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, destinations);
|
||||
SignalServiceDataMessage.Builder dataMessage = SignalServiceDataMessage.newBuilder()
|
||||
.asProfileKeyUpdate(true)
|
||||
.withTimestamp(System.currentTimeMillis())
|
||||
.withProfileKey(Recipient.self().resolve().getProfileKey());
|
||||
private List<Recipient> deliver(@NonNull List<Recipient> destinations) throws IOException, UntrustedIdentityException {
|
||||
SignalServiceDataMessage.Builder dataMessage = SignalServiceDataMessage.newBuilder()
|
||||
.asProfileKeyUpdate(true)
|
||||
.withTimestamp(System.currentTimeMillis())
|
||||
.withProfileKey(Recipient.self().resolve().getProfileKey());
|
||||
|
||||
if (conversationRecipient.isGroup()) {
|
||||
dataMessage.asGroupMessage(new SignalServiceGroup(conversationRecipient.requireGroupId().getDecodedId()));
|
||||
}
|
||||
|
||||
List<SendMessageResult> results = messageSender.sendDataMessage(addresses, unidentifiedAccess, false, ContentHint.IMPLICIT, dataMessage.build());
|
||||
List<SendMessageResult> results = GroupSendUtil.sendUnresendableDataMessage(context, null, destinations, false, ContentHint.IMPLICIT, dataMessage.build());
|
||||
|
||||
return GroupSendJobHelper.getCompletedSends(context, results);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
|||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
|
@ -292,8 +293,6 @@ public final class PushGroupSendJob extends PushSendJob {
|
|||
try {
|
||||
rotateSenderCertificateIfNecessary();
|
||||
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
GroupId.Push groupId = groupRecipient.requireGroupId().requirePush();
|
||||
Optional<byte[]> profileKey = getProfileKey(groupRecipient);
|
||||
Optional<Quote> quote = getQuoteFor(message);
|
||||
|
@ -301,14 +300,11 @@ public final class PushGroupSendJob extends PushSendJob {
|
|||
List<SharedContact> sharedContacts = getSharedContactsFor(message);
|
||||
List<Preview> previews = getPreviewsFor(message);
|
||||
List<SignalServiceDataMessage.Mention> mentions = getMentionsFor(message.getMentions());
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, destinations);
|
||||
List<Attachment> attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList();
|
||||
List<SignalServiceAttachment> attachmentPointers = getAttachmentPointersFor(attachments);
|
||||
boolean isRecipientUpdate = Stream.of(DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId))
|
||||
.anyMatch(info -> info.getStatus() > GroupReceiptDatabase.STATUS_UNDELIVERED);
|
||||
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, destinations);
|
||||
|
||||
if (message.isGroup()) {
|
||||
OutgoingGroupUpdateMessage groupMessage = (OutgoingGroupUpdateMessage) message;
|
||||
|
||||
|
@ -329,25 +325,9 @@ public final class PushGroupSendJob extends PushSendJob {
|
|||
.withExpiration(groupRecipient.getExpireMessages())
|
||||
.asGroupMessage(group)
|
||||
.build();
|
||||
return GroupSendUtil.sendResendableDataMessage(context, groupRecipient.requireGroupId().requireV2(), destinations, isRecipientUpdate, ContentHint.IMPLICIT, messageId, true, groupDataMessage);
|
||||
return GroupSendUtil.sendResendableDataMessage(context, groupRecipient.requireGroupId().requireV2(), destinations, isRecipientUpdate, ContentHint.IMPLICIT, new MessageId(messageId, true), groupDataMessage);
|
||||
} else {
|
||||
MessageGroupContext.GroupV1Properties properties = groupMessage.requireGroupV1Properties();
|
||||
|
||||
GroupContext groupContext = properties.getGroupContext();
|
||||
SignalServiceAttachment avatar = attachmentPointers.isEmpty() ? null : attachmentPointers.get(0);
|
||||
SignalServiceGroup.Type type = properties.isQuit() ? SignalServiceGroup.Type.QUIT : SignalServiceGroup.Type.UPDATE;
|
||||
List<SignalServiceAddress> members = Stream.of(groupContext.getMembersE164List())
|
||||
.map(e164 -> new SignalServiceAddress(null, e164))
|
||||
.toList();
|
||||
SignalServiceGroup group = new SignalServiceGroup(type, groupId.getDecodedId(), groupContext.getName(), members, avatar);
|
||||
SignalServiceDataMessage groupDataMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withTimestamp(message.getSentTimeMillis())
|
||||
.withExpiration(message.getRecipient().getExpireMessages())
|
||||
.asGroupMessage(group)
|
||||
.build();
|
||||
|
||||
Log.i(TAG, JobLogger.format(this, "Beginning update send."));
|
||||
return messageSender.sendDataMessage(addresses, unidentifiedAccess, isRecipientUpdate, ContentHint.IMPLICIT, groupDataMessage);
|
||||
throw new UndeliverableMessageException("Messages can no longer be sent to V1 groups!");
|
||||
}
|
||||
} else {
|
||||
SignalServiceDataMessage.Builder builder = SignalServiceDataMessage.newBuilder()
|
||||
|
@ -370,13 +350,13 @@ public final class PushGroupSendJob extends PushSendJob {
|
|||
|
||||
Log.i(TAG, JobLogger.format(this, "Beginning message send."));
|
||||
|
||||
if (groupRecipient.isPushV2Group()) {
|
||||
return GroupSendUtil.sendResendableDataMessage(context, groupRecipient.requireGroupId().requireV2(), destinations, isRecipientUpdate, ContentHint.RESENDABLE, messageId, true, groupMessage);
|
||||
} else {
|
||||
List<SendMessageResult> results = messageSender.sendDataMessage(addresses, unidentifiedAccess, isRecipientUpdate, ContentHint.RESENDABLE, groupMessage);
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(groupMessage.getTimestamp(), destinations, results, ContentHint.RESENDABLE, messageId, true);
|
||||
return results;
|
||||
}
|
||||
return GroupSendUtil.sendResendableDataMessage(context,
|
||||
groupRecipient.getGroupId().transform(GroupId::requireV2).orNull(),
|
||||
destinations,
|
||||
isRecipientUpdate,
|
||||
ContentHint.RESENDABLE,
|
||||
new MessageId(messageId, true),
|
||||
groupMessage);
|
||||
}
|
||||
} catch (ServerRejectedException e) {
|
||||
throw new UndeliverableMessageException(e);
|
||||
|
|
|
@ -12,9 +12,11 @@ import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
|||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
|
@ -170,6 +172,11 @@ public class ReactionSendJob extends BaseJob {
|
|||
throw new AssertionError("We have a message, but couldn't find the thread!");
|
||||
}
|
||||
|
||||
if (conversationRecipient.isPushV1Group() || conversationRecipient.isMmsGroup()) {
|
||||
Log.w(TAG, "Cannot send reactions to legacy groups.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Recipient> destinations = Stream.of(recipients).map(Recipient::resolved).toList();
|
||||
List<Recipient> completions = deliver(conversationRecipient, destinations, targetAuthor, targetSentTimestamp);
|
||||
|
||||
|
@ -227,20 +234,13 @@ public class ReactionSendJob extends BaseJob {
|
|||
}
|
||||
|
||||
SignalServiceDataMessage dataMessage = dataMessageBuilder.build();
|
||||
|
||||
List<SendMessageResult> results;
|
||||
|
||||
if (conversationRecipient.isPushV2Group()) {
|
||||
results = GroupSendUtil.sendResendableDataMessage(context, conversationRecipient.requireGroupId().requireV2(), destinations, false, ContentHint.RESENDABLE, messageId, isMms, dataMessageBuilder.build());
|
||||
} else {
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, destinations);
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, destinations);
|
||||
|
||||
results = messageSender.sendDataMessage(addresses, unidentifiedAccess, false, ContentHint.RESENDABLE, dataMessage);
|
||||
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(dataMessage.getTimestamp(), destinations, results, ContentHint.RESENDABLE, messageId, isMms);
|
||||
}
|
||||
List<SendMessageResult> results = GroupSendUtil.sendResendableDataMessage(context,
|
||||
conversationRecipient.getGroupId().transform(GroupId::requireV2).orNull(),
|
||||
destinations,
|
||||
false,
|
||||
ContentHint.RESENDABLE,
|
||||
new MessageId(messageId, isMms),
|
||||
dataMessage);
|
||||
|
||||
return GroupSendJobHelper.getCompletedSends(context, results);
|
||||
}
|
||||
|
|
|
@ -12,8 +12,10 @@ import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
|||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
|
@ -183,20 +185,13 @@ public class RemoteDeleteSendJob extends BaseJob {
|
|||
}
|
||||
|
||||
SignalServiceDataMessage dataMessage = dataMessageBuilder.build();
|
||||
|
||||
List<SendMessageResult> results;
|
||||
|
||||
if (conversationRecipient.isPushV2Group()) {
|
||||
results = GroupSendUtil.sendResendableDataMessage(context, conversationRecipient.requireGroupId().requireV2(), destinations, false, ContentHint.RESENDABLE, messageId, isMms, dataMessage);
|
||||
} else {
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, destinations);
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, destinations);
|
||||
|
||||
results = messageSender.sendDataMessage(addresses, unidentifiedAccess, false, ContentHint.RESENDABLE, dataMessage);
|
||||
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(dataMessage.getTimestamp(), destinations, results, ContentHint.RESENDABLE, messageId, isMms);
|
||||
}
|
||||
List<SendMessageResult> results = GroupSendUtil.sendResendableDataMessage(context,
|
||||
conversationRecipient.getGroupId().transform(GroupId::requireV2).orNull(),
|
||||
destinations,
|
||||
false,
|
||||
ContentHint.RESENDABLE,
|
||||
new MessageId(messageId, isMms),
|
||||
dataMessage);
|
||||
|
||||
return GroupSendJobHelper.getCompletedSends(context, results);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
|||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
|
@ -107,6 +108,16 @@ public class TypingSendJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
if (recipient.isPushV1Group() || recipient.isMmsGroup()) {
|
||||
Log.w(TAG, "Not sending typing indicators to unsupported groups.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!recipient.isRegistered() || recipient.isForceSmsSelection()) {
|
||||
Log.w(TAG, "Not sending typing indicators to non-Signal recipients.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Recipient> recipients = Collections.singletonList(recipient);
|
||||
Optional<byte[]> groupId = Optional.absent();
|
||||
|
||||
|
@ -123,25 +134,11 @@ public class TypingSendJob extends BaseJob {
|
|||
SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis(), groupId);
|
||||
|
||||
try {
|
||||
if (recipient.isPushV2Group()) {
|
||||
GroupSendUtil.sendTypingMessage(context, recipient.requireGroupId().requireV2(), recipients, typingMessage, this::isCanceled);
|
||||
} else {
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, recipients);
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipients);
|
||||
|
||||
if (addresses.isEmpty()) {
|
||||
Log.w(TAG, "No one to send typing indicators to");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCanceled()) {
|
||||
Log.w(TAG, "Canceled before send!");
|
||||
return;
|
||||
}
|
||||
|
||||
messageSender.sendTyping(addresses, unidentifiedAccess, typingMessage, this::isCanceled);
|
||||
}
|
||||
GroupSendUtil.sendTypingMessage(context,
|
||||
recipient.getGroupId().transform(GroupId::requireV2).orNull(),
|
||||
recipients,
|
||||
typingMessage,
|
||||
this::isCanceled);
|
||||
} catch (CancelationException e) {
|
||||
Log.w(TAG, "Canceled during send!");
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import org.signal.core.util.logging.Log;
|
|||
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessageSendLogDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
|
@ -33,6 +35,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
|||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.push.http.CancelationSignal;
|
||||
import org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -45,6 +48,8 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class GroupSendUtil {
|
||||
|
@ -62,19 +67,19 @@ public final class GroupSendUtil {
|
|||
*
|
||||
* Messages sent this way, if failed to be decrypted by the receiving party, can be requested to be resent.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
|
||||
*/
|
||||
public static List<SendMessageResult> sendResendableDataMessage(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@Nullable GroupId.V2 groupId,
|
||||
@NonNull List<Recipient> allTargets,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
long relatedMessageId,
|
||||
boolean isRelatedMessageMms,
|
||||
@NonNull MessageId messageId,
|
||||
@NonNull SignalServiceDataMessage message)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
return sendMessage(context, groupId, allTargets, isRecipientUpdate, DataSendOperation.resendable(message, contentHint, relatedMessageId, isRelatedMessageMms), null);
|
||||
return sendMessage(context, groupId, allTargets, isRecipientUpdate, DataSendOperation.resendable(message, contentHint, messageId), null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,11 +88,12 @@ public final class GroupSendUtil {
|
|||
*
|
||||
* Messages sent this way, if failed to be decrypted by the receiving party, can *not* be requested to be resent.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static List<SendMessageResult> sendUnresendableDataMessage(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@Nullable GroupId.V2 groupId,
|
||||
@NonNull List<Recipient> allTargets,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
|
@ -100,10 +106,12 @@ public final class GroupSendUtil {
|
|||
/**
|
||||
* Handles all of the logic of sending to a group. Will do sender key sends and legacy 1:1 sends as-needed, and give you back a list of
|
||||
* {@link SendMessageResult}s just like we're used to.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static List<SendMessageResult> sendTypingMessage(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@Nullable GroupId.V2 groupId,
|
||||
@NonNull List<Recipient> allTargets,
|
||||
@NonNull SignalServiceTypingMessage message,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
|
@ -116,11 +124,12 @@ public final class GroupSendUtil {
|
|||
* Handles all of the logic of sending to a group. Will do sender key sends and legacy 1:1 sends as-needed, and give you back a list of
|
||||
* {@link SendMessageResult}s just like we're used to.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
|
||||
*/
|
||||
@WorkerThread
|
||||
private static List<SendMessageResult> sendMessage(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@Nullable GroupId.V2 groupId,
|
||||
@NonNull List<Recipient> allTargets,
|
||||
boolean isRecipientUpdate,
|
||||
@NonNull SendOperation sendOperation,
|
||||
|
@ -147,7 +156,11 @@ public final class GroupSendUtil {
|
|||
}
|
||||
|
||||
if (FeatureFlags.senderKey()) {
|
||||
if (Recipient.self().getSenderKeyCapability() != Recipient.Capability.SUPPORTED) {
|
||||
if (groupId == null) {
|
||||
Log.i(TAG, "Recipients not in a group. Using legacy.");
|
||||
legacyTargets.addAll(senderKeyTargets);
|
||||
senderKeyTargets.clear();
|
||||
} else if (Recipient.self().getSenderKeyCapability() != Recipient.Capability.SUPPORTED) {
|
||||
Log.i(TAG, "All of our devices do not support sender key. Using legacy.");
|
||||
legacyTargets.addAll(senderKeyTargets);
|
||||
senderKeyTargets.clear();
|
||||
|
@ -168,11 +181,11 @@ public final class GroupSendUtil {
|
|||
|
||||
List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
DistributionId distributionId = DatabaseFactory.getGroupDatabase(context).getOrCreateDistributionId(groupId);
|
||||
|
||||
if (senderKeyTargets.size() > 0) {
|
||||
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(context, distributionId);
|
||||
long keyAge = System.currentTimeMillis() - keyCreateTime;
|
||||
if (senderKeyTargets.size() > 0 && groupId != null) {
|
||||
DistributionId distributionId = DatabaseFactory.getGroupDatabase(context).getOrCreateDistributionId(groupId);
|
||||
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(context, distributionId);
|
||||
long keyAge = System.currentTimeMillis() - keyCreateTime;
|
||||
|
||||
if (keyCreateTime != -1 && keyAge > MAX_KEY_AGE) {
|
||||
Log.w(TAG, "Key is " + (keyAge) + " ms old (~" + TimeUnit.MILLISECONDS.toDays(keyAge) + " days). Rotating.");
|
||||
|
@ -190,7 +203,7 @@ public final class GroupSendUtil {
|
|||
Log.d(TAG, "Successfully sent using sender key to " + successCount + "/" + targets.size() + " sender key targets.");
|
||||
|
||||
if (sendOperation.shouldIncludeInMessageLog()) {
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId(), sendOperation.isRelatedMessageMms());
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId());
|
||||
}
|
||||
} catch (NoSessionException e) {
|
||||
Log.w(TAG, "No session. Falling back to legacy sends.", e);
|
||||
|
@ -212,16 +225,29 @@ public final class GroupSendUtil {
|
|||
List<Optional<UnidentifiedAccessPair>> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList());
|
||||
boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0;
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, access, recipientUpdate, cancelationSignal);
|
||||
|
||||
final MessageSendLogDatabase messageLogDatabase = DatabaseFactory.getMessageLogDatabase(context);
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, access, recipientUpdate, result -> {
|
||||
if (!includeInMessageLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (entryId) {
|
||||
if (entryId.get() == -1) {
|
||||
entryId.set(messageLogDatabase.insertIfPossible(recipients.requireRecipientId(result.getAddress()), sendOperation.getSentTimestamp(), result, sendOperation.getContentHint(), sendOperation.getRelatedMessageId()));
|
||||
} else {
|
||||
messageLogDatabase.addRecipientToExistingEntryIfPossible(entryId.get(), recipients.requireRecipientId(result.getAddress()), result);
|
||||
}
|
||||
}
|
||||
}, cancelationSignal);
|
||||
|
||||
allResults.addAll(results);
|
||||
|
||||
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
|
||||
Log.d(TAG, "Successfully using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
|
||||
|
||||
if (sendOperation.shouldIncludeInMessageLog()) {
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), legacyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId(), sendOperation.isRelatedMessageMms());
|
||||
}
|
||||
}
|
||||
|
||||
return allResults;
|
||||
|
@ -240,37 +266,39 @@ public final class GroupSendUtil {
|
|||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException, UntrustedIdentityException;
|
||||
|
||||
@NonNull ContentHint getContentHint();
|
||||
long getSentTimestamp();
|
||||
boolean shouldIncludeInMessageLog();
|
||||
long getRelatedMessageId();
|
||||
boolean isRelatedMessageMms();
|
||||
@NonNull MessageId getRelatedMessageId();
|
||||
}
|
||||
|
||||
private static class DataSendOperation implements SendOperation {
|
||||
private final SignalServiceDataMessage message;
|
||||
private final ContentHint contentHint;
|
||||
private final long relatedMessageId;
|
||||
private final boolean isRelatedMessageMms;
|
||||
private final MessageId relatedMessageId;
|
||||
private final boolean resendable;
|
||||
|
||||
public static DataSendOperation resendable(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint, long relatedMessageId, boolean isRelatedMessageMms) {
|
||||
return new DataSendOperation(message, contentHint, true, relatedMessageId, isRelatedMessageMms);
|
||||
public static DataSendOperation resendable(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint, @NonNull MessageId relatedMessageId) {
|
||||
return new DataSendOperation(message, contentHint, true, relatedMessageId);
|
||||
}
|
||||
|
||||
public static DataSendOperation unresendable(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint) {
|
||||
return new DataSendOperation(message, contentHint, false, -1, false);
|
||||
return new DataSendOperation(message, contentHint, false, null);
|
||||
}
|
||||
|
||||
private DataSendOperation(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint, boolean resendable, long relatedMessageId, boolean isRelatedMessageMms) {
|
||||
private DataSendOperation(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint, boolean resendable, @Nullable MessageId relatedMessageId) {
|
||||
this.message = message;
|
||||
this.contentHint = contentHint;
|
||||
this.resendable = resendable;
|
||||
this.relatedMessageId = relatedMessageId;
|
||||
this.isRelatedMessageMms = isRelatedMessageMms;
|
||||
|
||||
if (resendable && relatedMessageId == null) {
|
||||
throw new IllegalArgumentException("If a message is resendable, it must have a related message ID!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -289,10 +317,11 @@ public final class GroupSendUtil {
|
|||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message);
|
||||
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message, partialListener, cancelationSignal);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -311,13 +340,12 @@ public final class GroupSendUtil {
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getRelatedMessageId() {
|
||||
return relatedMessageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelatedMessageMms() {
|
||||
return isRelatedMessageMms;
|
||||
public @NonNull MessageId getRelatedMessageId() {
|
||||
if (relatedMessageId != null) {
|
||||
return relatedMessageId;
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,6 +374,7 @@ public final class GroupSendUtil {
|
|||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -369,13 +398,8 @@ public final class GroupSendUtil {
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getRelatedMessageId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelatedMessageMms() {
|
||||
return false;
|
||||
public @NonNull MessageId getRelatedMessageId() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,10 +410,23 @@ public final class GroupSendUtil {
|
|||
|
||||
private final Map<RecipientId, Optional<UnidentifiedAccessPair>> accessById;
|
||||
private final Map<RecipientId, SignalServiceAddress> addressById;
|
||||
private final Map<UUID, RecipientId> idByUuid;
|
||||
private final Map<String, RecipientId> idByE164;
|
||||
|
||||
RecipientData(@NonNull Context context, @NonNull List<Recipient> recipients) throws IOException {
|
||||
this.accessById = UnidentifiedAccessUtil.getAccessMapFor(context, recipients);
|
||||
this.addressById = mapAddresses(context, recipients);
|
||||
this.idByUuid = new HashMap<>(recipients.size());
|
||||
this.idByE164 = new HashMap<>(recipients.size());
|
||||
|
||||
for (Recipient recipient : recipients) {
|
||||
if (recipient.hasUuid()) {
|
||||
idByUuid.put(recipient.requireUuid(), recipient.getId());
|
||||
}
|
||||
if (recipient.hasE164()) {
|
||||
idByE164.put(recipient.requireE164(), recipient.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull SignalServiceAddress getAddress(@NonNull RecipientId id) {
|
||||
|
@ -404,6 +441,16 @@ public final class GroupSendUtil {
|
|||
return Objects.requireNonNull(accessById.get(id)).get().getTargetUnidentifiedAccess().get();
|
||||
}
|
||||
|
||||
@NonNull RecipientId requireRecipientId(@NonNull SignalServiceAddress address) {
|
||||
if (address.getUuid().isPresent() && idByUuid.containsKey(address.getUuid().get())) {
|
||||
return Objects.requireNonNull(idByUuid.get(address.getUuid().get()));
|
||||
} else if (address.getNumber().isPresent() && idByE164.containsKey(address.getNumber().get())) {
|
||||
return Objects.requireNonNull(idByE164.get(address.getNumber().get()));
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull Map<RecipientId, SignalServiceAddress> mapAddresses(@NonNull Context context, @NonNull List<Recipient> recipients) throws IOException {
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, recipients);
|
||||
|
||||
|
|
|
@ -287,8 +287,8 @@ public final class MessageContentProcessor {
|
|||
ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob(false))
|
||||
.then(GroupV2UpdateSelfProfileKeyJob.withQueueLimits(groupId.get().requireV2()))
|
||||
.enqueue();
|
||||
} else if (!sender.isPushV2Group()) {
|
||||
Log.i(TAG, "Message was to a 1:1 or GV1 chat. Ensuring this user has our profile key.");
|
||||
} else if (!sender.isGroup()) {
|
||||
Log.i(TAG, "Message was to a 1:1. Ensuring this user has our profile key.");
|
||||
ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob(false))
|
||||
.then(ProfileKeySendJob.create(context, DatabaseFactory.getThreadDatabase(context).getThreadIdFor(sender), true))
|
||||
.enqueue();
|
||||
|
|
|
@ -108,6 +108,7 @@ import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevic
|
|||
import org.whispersystems.signalservice.internal.push.exceptions.StaleDevicesException;
|
||||
import org.whispersystems.signalservice.internal.push.http.AttachmentCipherOutputStreamFactory;
|
||||
import org.whispersystems.signalservice.internal.push.http.CancelationSignal;
|
||||
import org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener;
|
||||
import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
|
||||
import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
@ -273,7 +274,7 @@ public class SignalServiceMessageSender {
|
|||
Content content = createTypingContent(message);
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.absent());
|
||||
|
||||
sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), message.getTimestamp(), envelopeContent, true, cancelationSignal);
|
||||
sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), message.getTimestamp(), envelopeContent, true, null, cancelationSignal);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -388,7 +389,7 @@ public class SignalServiceMessageSender {
|
|||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.of(groupId));
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
return sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null);
|
||||
return sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,21 +444,22 @@ public class SignalServiceMessageSender {
|
|||
/**
|
||||
* Sends a message to a group using client-side fanout.
|
||||
*
|
||||
* @param recipients The group members.
|
||||
* @param message The group message.
|
||||
* @throws IOException
|
||||
* @param partialListener A listener that will be called when an individual send is completed. Will be invoked on an arbitrary background thread, *not*
|
||||
* the calling thread.
|
||||
*/
|
||||
public List<SendMessageResult> sendDataMessage(List<SignalServiceAddress> recipients,
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message)
|
||||
SignalServiceDataMessage message,
|
||||
PartialSendCompleteListener partialListener,
|
||||
CancelationSignal cancelationSignal)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
Content content = createMessageContent(message);
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, message.getGroupId());
|
||||
long timestamp = message.getTimestamp();
|
||||
List<SendMessageResult> results = sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null);
|
||||
List<SendMessageResult> results = sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, partialListener, cancelationSignal);
|
||||
boolean needsSyncInResults = false;
|
||||
|
||||
for (SendMessageResult result : results) {
|
||||
|
@ -1531,6 +1533,7 @@ public class SignalServiceMessageSender {
|
|||
long timestamp,
|
||||
EnvelopeContent content,
|
||||
boolean online,
|
||||
PartialSendCompleteListener partialListener,
|
||||
CancelationSignal cancelationSignal)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -1544,7 +1547,13 @@ public class SignalServiceMessageSender {
|
|||
while (recipientIterator.hasNext()) {
|
||||
SignalServiceAddress recipient = recipientIterator.next();
|
||||
Optional<UnidentifiedAccess> access = unidentifiedAccessIterator.next();
|
||||
futureResults.add(executor.submit(() -> sendMessage(recipient, access, timestamp, content, online, cancelationSignal)));
|
||||
futureResults.add(executor.submit(() -> {
|
||||
SendMessageResult result = sendMessage(recipient, access, timestamp, content, online, cancelationSignal);
|
||||
if (partialListener != null) {
|
||||
partialListener.onPartialSendComplete(result);
|
||||
}
|
||||
return result;
|
||||
}));
|
||||
}
|
||||
|
||||
List<SendMessageResult> results = new ArrayList<>(futureResults.size());
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package org.whispersystems.signalservice.internal.push.http;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
|
||||
/**
|
||||
* Used to let a listener know when each individual send in a collection of sends has been completed.
|
||||
*/
|
||||
public interface PartialSendCompleteListener {
|
||||
void onPartialSendComplete(SendMessageResult result);
|
||||
}
|
Ładowanie…
Reference in New Issue