use of org.thoughtcrime.securesms.database.MessageSendLogDatabase in project Signal-Android by WhisperSystems.
the class GroupSendUtil method sendMessage.
/**
* 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, @Nullable GroupId.V2 groupId, @Nullable MessageId relatedMessageId, @NonNull List<Recipient> allTargets, boolean isRecipientUpdate, @NonNull SendOperation sendOperation, @Nullable CancelationSignal cancelationSignal) throws IOException, UntrustedIdentityException {
Log.i(TAG, "Starting group send. GroupId: " + (groupId != null ? groupId.toString() : "none") + ", RelatedMessageId: " + (relatedMessageId != null ? relatedMessageId.toString() : "none") + ", Targets: " + allTargets.size() + ", RecipientUpdate: " + isRecipientUpdate + ", Operation: " + sendOperation.getClass().getSimpleName());
Set<Recipient> unregisteredTargets = allTargets.stream().filter(Recipient::isUnregistered).collect(Collectors.toSet());
List<Recipient> registeredTargets = allTargets.stream().filter(r -> !unregisteredTargets.contains(r)).collect(Collectors.toList());
RecipientData recipients = new RecipientData(context, registeredTargets);
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.absent();
List<Recipient> senderKeyTargets = new LinkedList<>();
List<Recipient> legacyTargets = new LinkedList<>();
for (Recipient recipient : registeredTargets) {
Optional<UnidentifiedAccessPair> access = recipients.getAccessPair(recipient.getId());
boolean validMembership = groupRecord.isPresent() && groupRecord.get().getMembers().contains(recipient.getId());
if (recipient.getSenderKeyCapability() == Recipient.Capability.SUPPORTED && recipient.hasServiceId() && access.isPresent() && access.get().getTargetUnidentifiedAccess().isPresent() && validMembership) {
senderKeyTargets.add(recipient);
} else {
legacyTargets.add(recipient);
}
}
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();
} else if (SignalStore.internalValues().removeSenderKeyMinimum()) {
Log.i(TAG, "Sender key minimum removed. Using for " + senderKeyTargets.size() + " recipients.");
} else if (senderKeyTargets.size() < 2) {
Log.i(TAG, "Too few sender-key-capable users (" + senderKeyTargets.size() + "). Doing all legacy sends.");
legacyTargets.addAll(senderKeyTargets);
senderKeyTargets.clear();
} else {
Log.i(TAG, "Can use sender key for " + senderKeyTargets.size() + "/" + allTargets.size() + " recipients.");
}
if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyStarted(relatedMessageId.getId());
}
List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
if (senderKeyTargets.size() > 0 && groupId != null) {
DistributionId distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(context, distributionId);
long keyAge = System.currentTimeMillis() - keyCreateTime;
if (keyCreateTime != -1 && keyAge > FeatureFlags.senderKeyMaxAge()) {
Log.w(TAG, "DistributionId " + distributionId + " was created at " + keyCreateTime + " and is " + (keyAge) + " ms old (~" + TimeUnit.MILLISECONDS.toDays(keyAge) + " days). Rotating.");
SenderKeyUtil.rotateOurKey(context, distributionId);
}
try {
List<SignalServiceAddress> targets = senderKeyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<UnidentifiedAccess> access = senderKeyTargets.stream().map(r -> recipients.requireAccess(r.getId())).collect(Collectors.toList());
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, isRecipientUpdate);
allResults.addAll(results);
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
Log.d(TAG, "Successfully sent using sender key to " + successCount + "/" + targets.size() + " sender key targets.");
if (sendOperation.shouldIncludeInMessageLog()) {
SignalDatabase.messageLog().insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId());
}
if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyMslInserted(relatedMessageId.getId());
}
} catch (InvalidUnidentifiedAccessHeaderException e) {
Log.w(TAG, "Someone had a bad UD header. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (NoSessionException e) {
Log.w(TAG, "No session. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (InvalidKeyException e) {
Log.w(TAG, "Invalid key. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (InvalidRegistrationIdException e) {
Log.w(TAG, "Invalid registrationId. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
} catch (NotFoundException e) {
Log.w(TAG, "Someone was unregistered. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
}
} else if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onSenderKeyShared(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyEncrypted(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyMessageSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeySyncSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onSenderKeyMslInserted(relatedMessageId.getId());
}
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
throw new CancelationException();
}
boolean onlyTargetIsSelfWithLinkedDevice = legacyTargets.isEmpty() && senderKeyTargets.isEmpty() && TextSecurePreferences.isMultiDevice(context);
if (legacyTargets.size() > 0 || onlyTargetIsSelfWithLinkedDevice) {
if (legacyTargets.size() > 0) {
Log.i(TAG, "Need to do " + legacyTargets.size() + " legacy sends.");
} else {
Log.i(TAG, "Need to do a legacy send to send a sync message for a group of only ourselves.");
}
List<SignalServiceAddress> targets = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<Optional<UnidentifiedAccessPair>> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList());
boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0;
final MessageSendLogDatabase messageLogDatabase = SignalDatabase.messageLog();
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 sent using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
} else if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onLegacyMessageSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onLegacySyncFinished(relatedMessageId.getId());
}
if (unregisteredTargets.size() > 0) {
Log.w(TAG, "There are " + unregisteredTargets.size() + " unregistered targets. Including failure results.");
List<SendMessageResult> unregisteredResults = unregisteredTargets.stream().filter(Recipient::hasServiceId).map(t -> SendMessageResult.unregisteredFailure(new SignalServiceAddress(t.requireServiceId(), t.getE164().orNull()))).collect(Collectors.toList());
if (unregisteredResults.size() < unregisteredTargets.size()) {
Log.w(TAG, "There are " + (unregisteredTargets.size() - unregisteredResults.size()) + " targets that have no UUID! Cannot report a failure for them.");
}
allResults.addAll(unregisteredResults);
}
return allResults;
}
Aggregations