+ // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
+ final var isRecipientUpdate = false;
+ Set<RecipientId> senderKeyTargets = distributionId == null
+ ? Set.of()
+ : getSenderKeyCapableRecipientIds(recipientIds);
+ final var allResults = new ArrayList<SendMessageResult>(recipientIds.size());
+
+ if (senderKeyTargets.size() > 0) {
+ final var results = sendGroupMessageInternalWithSenderKey(message,
+ senderKeyTargets,
+ distributionId,
+ isRecipientUpdate);
+
+ if (results == null) {
+ senderKeyTargets = Set.of();
+ } else {
+ results.stream().filter(SendMessageResult::isSuccess).forEach(allResults::add);
+ final var failedTargets = results.stream()
+ .filter(r -> !r.isSuccess())
+ .map(r -> recipientResolver.resolveRecipient(r.getAddress()))
+ .toList();
+ if (failedTargets.size() > 0) {
+ senderKeyTargets = new HashSet<>(senderKeyTargets);
+ failedTargets.forEach(senderKeyTargets::remove);
+ }
+ }
+ }
+
+ final var legacyTargets = new HashSet<>(recipientIds);
+ legacyTargets.removeAll(senderKeyTargets);
+ final boolean onlyTargetIsSelfWithLinkedDevice = recipientIds.isEmpty() && account.isMultiDevice();
+
+ if (legacyTargets.size() > 0 || onlyTargetIsSelfWithLinkedDevice) {
+ if (legacyTargets.size() > 0) {
+ logger.debug("Need to do {} legacy sends.", legacyTargets.size());
+ } else {
+ logger.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
+ }
+
+ final List<SendMessageResult> results = sendGroupMessageInternalWithLegacy(message,
+ legacyTargets,
+ isRecipientUpdate || allResults.size() > 0);
+ allResults.addAll(results);
+ }
+
+ return allResults;
+ }
+
+ private Set<RecipientId> getSenderKeyCapableRecipientIds(final Set<RecipientId> recipientIds) {
+ final var selfProfile = profileProvider.getProfile(account.getSelfRecipientId());
+ if (selfProfile == null || !selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
+ logger.debug("Not all of our devices support sender key. Using legacy.");
+ return Set.of();
+ }
+
+ final var senderKeyTargets = new HashSet<RecipientId>();
+ for (final var recipientId : recipientIds) {
+ // TODO filter out unregistered
+ final var profile = profileProvider.getProfile(recipientId);
+ if (profile == null || !profile.getCapabilities().contains(Profile.Capability.senderKey)) {
+ continue;
+ }
+
+ final var access = unidentifiedAccessHelper.getAccessFor(recipientId);
+ if (!access.isPresent() || !access.get().getTargetUnidentifiedAccess().isPresent()) {
+ continue;
+ }
+
+ final var identity = account.getIdentityKeyStore().getIdentity(recipientId);
+ if (identity == null || !identity.getTrustLevel().isTrusted()) {
+ continue;
+ }
+
+ senderKeyTargets.add(recipientId);
+ }
+
+ if (senderKeyTargets.size() < 2) {
+ logger.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets.size());
+ return Set.of();
+ }
+
+ logger.debug("Can use sender key for {}/{} recipients.", senderKeyTargets.size(), recipientIds.size());
+ return senderKeyTargets;
+ }
+
+ private List<SendMessageResult> sendGroupMessageInternalWithLegacy(
+ final SignalServiceDataMessage message, final Set<RecipientId> recipientIds, final boolean isRecipientUpdate
+ ) throws IOException {
+ final var recipientIdList = new ArrayList<>(recipientIds);
+ final var addresses = recipientIdList.stream().map(addressResolver::resolveSignalServiceAddress).toList();
+ final var unidentifiedAccesses = unidentifiedAccessHelper.getAccessFor(recipientIdList);
+ final var messageSender = dependencies.getMessageSender();