Do not attempt to send to unregistered users when using CDS flag.

CDS is slow, and unregistered users will always trigger a CDS lookup on
send (since we can't get their UUID).

This starts skipping sends to unregistered users and shortens the time
window to do a full CDS lookup from every 12 hours to every 6 hours.
master
Greyson Parrelli 2020-08-31 11:33:35 -04:00
parent 1e37951701
commit 4714895c59
11 changed files with 149 additions and 75 deletions

View File

@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.RecipientDetails;
import org.thoughtcrime.securesms.registration.RegistrationUtil; import org.thoughtcrime.securesms.registration.RegistrationUtil;
import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -92,61 +93,35 @@ public class DirectoryHelper {
return; return;
} }
Stopwatch stopwatch = new Stopwatch("full");
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
Set<String> databaseNumbers = sanitizeNumbers(recipientDatabase.getAllPhoneNumbers()); Set<String> databaseNumbers = sanitizeNumbers(recipientDatabase.getAllPhoneNumbers());
Set<String> systemNumbers = sanitizeNumbers(ContactAccessor.getInstance().getAllContactsWithNumbers(context)); Set<String> systemNumbers = sanitizeNumbers(ContactAccessor.getInstance().getAllContactsWithNumbers(context));
Set<String> allNumbers = SetUtil.union(databaseNumbers, systemNumbers);
DirectoryResult result; refreshNumbers(context, databaseNumbers, systemNumbers, notifyOfNewUsers);
if (FeatureFlags.cds()) {
result = ContactDiscoveryV2.getDirectoryResult(context, databaseNumbers, systemNumbers);
} else {
result = ContactDiscoveryV1.getDirectoryResult(databaseNumbers, systemNumbers);
}
stopwatch.split("network");
if (result.getNumberRewrites().size() > 0) {
Log.i(TAG, "[getDirectoryResult] Need to rewrite some numbers.");
recipientDatabase.updatePhoneNumbers(result.getNumberRewrites());
}
Map<RecipientId, String> uuidMap = recipientDatabase.bulkProcessCdsResult(result.getRegisteredNumbers());
Set<String> activeNumbers = result.getRegisteredNumbers().keySet();
Set<RecipientId> activeIds = uuidMap.keySet();
Set<RecipientId> inactiveIds = Stream.of(allNumbers)
.filterNot(activeNumbers::contains)
.filterNot(n -> result.getNumberRewrites().containsKey(n))
.map(recipientDatabase::getOrInsertFromE164)
.collect(Collectors.toSet());
recipientDatabase.bulkUpdatedRegisteredStatus(uuidMap, inactiveIds);
updateContactsDatabase(context, activeIds, true, result.getNumberRewrites());
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob());
}
if (TextSecurePreferences.hasSuccessfullyRetrievedDirectory(context) && notifyOfNewUsers) {
Set<RecipientId> existingSignalIds = new HashSet<>(recipientDatabase.getRegistered());
Set<RecipientId> existingSystemIds = new HashSet<>(recipientDatabase.getSystemContacts());
Set<RecipientId> newlyActiveIds = new HashSet<>(activeIds);
newlyActiveIds.removeAll(existingSignalIds);
newlyActiveIds.retainAll(existingSystemIds);
notifyNewUsers(context, newlyActiveIds);
} else {
TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true);
}
StorageSyncHelper.scheduleSyncForDataChange(); StorageSyncHelper.scheduleSyncForDataChange();
}
stopwatch.split("disk"); @WorkerThread
stopwatch.stop(TAG); public static void refreshDirectoryFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean notifyOfNewUsers) throws IOException {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
for (Recipient recipient : recipients) {
if (recipient.hasUuid() && !recipient.hasE164()) {
if (isUuidRegistered(context, recipient)) {
recipientDatabase.markRegistered(recipient.getId(), recipient.requireUuid());
} else {
recipientDatabase.markUnregistered(recipient.getId());
}
}
}
Set<String> numbers = Stream.of(recipients)
.filter(Recipient::hasE164)
.map(Recipient::requireE164)
.collect(Collectors.toSet());
refreshNumbers(context, numbers, numbers, notifyOfNewUsers);
} }
@WorkerThread @WorkerThread
@ -231,6 +206,61 @@ public class DirectoryHelper {
return newRegisteredState; return newRegisteredState;
} }
@WorkerThread
private static void refreshNumbers(@NonNull Context context, @NonNull Set<String> databaseNumbers, @NonNull Set<String> systemNumbers, boolean notifyOfNewUsers) throws IOException {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
Set<String> allNumbers = SetUtil.union(databaseNumbers, systemNumbers);
if (allNumbers.isEmpty()) {
Log.w(TAG, "No numbers to refresh!");
return;
}
DirectoryResult result;
if (FeatureFlags.cds()) {
result = ContactDiscoveryV2.getDirectoryResult(context, databaseNumbers, systemNumbers);
} else {
result = ContactDiscoveryV1.getDirectoryResult(databaseNumbers, systemNumbers);
}
if (result.getNumberRewrites().size() > 0) {
Log.i(TAG, "[getDirectoryResult] Need to rewrite some numbers.");
recipientDatabase.updatePhoneNumbers(result.getNumberRewrites());
}
Map<RecipientId, String> uuidMap = recipientDatabase.bulkProcessCdsResult(result.getRegisteredNumbers());
Set<String> activeNumbers = result.getRegisteredNumbers().keySet();
Set<RecipientId> activeIds = uuidMap.keySet();
Set<RecipientId> inactiveIds = Stream.of(allNumbers)
.filterNot(activeNumbers::contains)
.filterNot(n -> result.getNumberRewrites().containsKey(n))
.map(recipientDatabase::getOrInsertFromE164)
.collect(Collectors.toSet());
recipientDatabase.bulkUpdatedRegisteredStatus(uuidMap, inactiveIds);
updateContactsDatabase(context, activeIds, true, result.getNumberRewrites());
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob());
}
if (TextSecurePreferences.hasSuccessfullyRetrievedDirectory(context) && notifyOfNewUsers) {
Set<RecipientId> existingSignalIds = new HashSet<>(recipientDatabase.getRegistered());
Set<RecipientId> existingSystemIds = new HashSet<>(recipientDatabase.getSystemContacts());
Set<RecipientId> newlyActiveIds = new HashSet<>(activeIds);
newlyActiveIds.removeAll(existingSignalIds);
newlyActiveIds.retainAll(existingSystemIds);
notifyNewUsers(context, newlyActiveIds);
} else {
TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true);
}
}
private static boolean isUuidRegistered(@NonNull Context context, @NonNull Recipient recipient) throws IOException { private static boolean isUuidRegistered(@NonNull Context context, @NonNull Recipient recipient) throws IOException {
try { try {
ProfileUtil.retrieveProfile(context, recipient, SignalServiceProfile.RequestType.PROFILE).get(10, TimeUnit.SECONDS); ProfileUtil.retrieveProfile(context, recipient, SignalServiceProfile.RequestType.PROFILE).get(10, TimeUnit.SECONDS);

View File

@ -56,7 +56,7 @@ public class ProfileKeySendJob extends BaseJob {
throw new AssertionError("Do not send profile keys directly for GV2"); throw new AssertionError("Do not send profile keys directly for GV2");
} }
List<RecipientId> recipients = conversationRecipient.isGroup() ? Stream.of(conversationRecipient.getParticipants()).map(Recipient::getId).toList() List<RecipientId> recipients = conversationRecipient.isGroup() ? Stream.of(RecipientUtil.getEligibleForSending(conversationRecipient.getParticipants())).map(Recipient::getId).toList()
: Stream.of(conversationRecipient.getId()).toList(); : Stream.of(conversationRecipient.getId()).toList();
recipients.remove(Recipient.self().getId()); recipients.remove(Recipient.self().getId());

View File

@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo
import org.thoughtcrime.securesms.database.MessageDatabase; import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException; 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.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -195,7 +197,13 @@ public final class PushGroupSendJob extends PushSendJob {
List<Pair<RecipientId, Boolean>> successUnidentifiedStatus = Stream.of(successes).map(result -> new Pair<>(findId(result.getAddress(), idByE164, idByUuid), result.getSuccess().isUnidentified())).toList(); List<Pair<RecipientId, Boolean>> successUnidentifiedStatus = Stream.of(successes).map(result -> new Pair<>(findId(result.getAddress(), idByE164, idByUuid), result.getSuccess().isUnidentified())).toList();
Set<RecipientId> successIds = Stream.of(successUnidentifiedStatus).map(Pair::first).collect(Collectors.toSet()); Set<RecipientId> successIds = Stream.of(successUnidentifiedStatus).map(Pair::first).collect(Collectors.toSet());
List<NetworkFailure> resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList(); List<NetworkFailure> resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList();
List<IdentityKeyMismatch> resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList(); List<IdentityKeyMismatch> resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successIds.contains(failure.getRecipientId(context))).toList();
List<Recipient> unregisteredRecipients = Stream.of(results).filter(SendMessageResult::isUnregisteredFailure).map(result -> Recipient.externalPush(context, result.getAddress())).toList();
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
for (Recipient unregistered : unregisteredRecipients) {
recipientDatabase.markUnregistered(unregistered.getId());
}
for (NetworkFailure resolvedFailure : resolvedNetworkFailures) { for (NetworkFailure resolvedFailure : resolvedNetworkFailures) {
database.removeFailure(messageId, resolvedFailure); database.removeFailure(messageId, resolvedFailure);
@ -366,7 +374,10 @@ public final class PushGroupSendJob extends PushSendJob {
List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId); List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId);
if (!destinations.isEmpty()) { if (!destinations.isEmpty()) {
return Stream.of(destinations).map(GroupReceiptInfo::getRecipientId).map(Recipient::resolved).toList(); return RecipientUtil.getEligibleForSending(Stream.of(destinations)
.map(GroupReceiptInfo::getRecipientId)
.map(Recipient::resolved)
.toList());
} }
List<Recipient> members = Stream.of(DatabaseFactory.getGroupDatabase(context) List<Recipient> members = Stream.of(DatabaseFactory.getGroupDatabase(context)
@ -377,8 +388,8 @@ public final class PushGroupSendJob extends PushSendJob {
if (members.size() > 0) { if (members.size() > 0) {
Log.w(TAG, "No destinations found for group message " + groupId + " using current group membership"); Log.w(TAG, "No destinations found for group message " + groupId + " using current group membership");
} }
return members; return RecipientUtil.getEligibleForSending(members);
} }
public static class Factory implements Job.Factory<PushGroupSendJob> { public static class Factory implements Job.Factory<PushGroupSendJob> {

View File

@ -11,6 +11,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
@ -23,6 +24,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
@ -75,7 +77,15 @@ public final class PushGroupSilentUpdateSendJob extends BaseJob {
Set<RecipientId> recipients = Stream.concat(Stream.of(memberUuids), Stream.of(pendingUuids)) Set<RecipientId> recipients = Stream.concat(Stream.of(memberUuids), Stream.of(pendingUuids))
.filter(uuid -> !UuidUtil.UNKNOWN_UUID.equals(uuid)) .filter(uuid -> !UuidUtil.UNKNOWN_UUID.equals(uuid))
.filter(uuid -> !Recipient.self().getUuid().get().equals(uuid)) .filter(uuid -> !Recipient.self().getUuid().get().equals(uuid))
.map(uuid -> RecipientId.from(uuid, null)) .map(uuid -> Recipient.externalPush(context, uuid, null, false))
.filter(recipient -> {
if (FeatureFlags.cds()) {
return recipient.getRegistered() != RecipientDatabase.RegisteredState.NOT_REGISTERED;
} else {
return true;
}
})
.map(Recipient::getId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
MessageGroupContext.GroupV2Properties properties = groupMessage.requireGroupV2Properties(); MessageGroupContext.GroupV2Properties properties = groupMessage.requireGroupV2Properties();

View File

@ -75,7 +75,7 @@ public class ReactionSendJob extends BaseJob {
throw new AssertionError("We have a message, but couldn't find the thread!"); throw new AssertionError("We have a message, but couldn't find the thread!");
} }
List<RecipientId> recipients = conversationRecipient.isGroup() ? Stream.of(conversationRecipient.getParticipants()).map(Recipient::getId).toList() List<RecipientId> recipients = conversationRecipient.isGroup() ? Stream.of(RecipientUtil.getEligibleForSending(conversationRecipient.getParticipants())).map(Recipient::getId).toList()
: Stream.of(conversationRecipient.getId()).toList(); : Stream.of(conversationRecipient.getId()).toList();
recipients.remove(Recipient.self().getId()); recipients.remove(Recipient.self().getId());

View File

@ -65,7 +65,7 @@ public class RemoteDeleteSendJob extends BaseJob {
throw new AssertionError("We have a message, but couldn't find the thread!"); throw new AssertionError("We have a message, but couldn't find the thread!");
} }
List<RecipientId> recipients = conversationRecipient.isGroup() ? Stream.of(conversationRecipient.getParticipants()).map(Recipient::getId).toList() List<RecipientId> recipients = conversationRecipient.isGroup() ? Stream.of(RecipientUtil.getEligibleForSending(conversationRecipient.getParticipants())).map(Recipient::getId).toList()
: Stream.of(conversationRecipient.getId()).toList(); : Stream.of(conversationRecipient.getId()).toList();
recipients.remove(Recipient.self().getId()); recipients.remove(Recipient.self().getId());

View File

@ -9,6 +9,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.CancelationException; import org.whispersystems.signalservice.api.CancelationException;
@ -104,7 +106,9 @@ public class TypingSendJob extends BaseJob {
groupId = Optional.of(recipient.requireGroupId().getDecodedId()); groupId = Optional.of(recipient.requireGroupId().getDecodedId());
} }
recipients = Stream.of(recipients).map(Recipient::resolve).toList(); recipients = RecipientUtil.getEligibleForSending(Stream.of(recipients)
.map(Recipient::resolve)
.toList());
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, recipients); List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, recipients);

View File

@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import com.google.android.gms.common.Feature;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -29,6 +30,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
public class RecipientUtil { public class RecipientUtil {
@ -38,7 +40,8 @@ public class RecipientUtil {
/** /**
* This method will do it's best to craft a fully-populated {@link SignalServiceAddress} based on * This method will do it's best to craft a fully-populated {@link SignalServiceAddress} based on
* the provided recipient. This includes performing a possible network request if no UUID is * the provided recipient. This includes performing a possible network request if no UUID is
* available. * available. If the request to get a UUID fails, the exception is swallowed an an E164-only
* recipient is returned.
*/ */
@WorkerThread @WorkerThread
public static @NonNull SignalServiceAddress toSignalServiceAddressBestEffort(@NonNull Context context, @NonNull Recipient recipient) { public static @NonNull SignalServiceAddress toSignalServiceAddressBestEffort(@NonNull Context context, @NonNull Recipient recipient) {
@ -53,7 +56,7 @@ public class RecipientUtil {
/** /**
* This method will do it's best to craft a fully-populated {@link SignalServiceAddress} based on * This method will do it's best to craft a fully-populated {@link SignalServiceAddress} based on
* the provided recipient. This includes performing a possible network request if no UUID is * the provided recipient. This includes performing a possible network request if no UUID is
* available. * available. If the request to get a UUID fails, an IOException is thrown.
*/ */
@WorkerThread @WorkerThread
public static @NonNull SignalServiceAddress toSignalServiceAddress(@NonNull Context context, @NonNull Recipient recipient) public static @NonNull SignalServiceAddress toSignalServiceAddress(@NonNull Context context, @NonNull Recipient recipient)
@ -79,25 +82,27 @@ public class RecipientUtil {
public static @NonNull List<SignalServiceAddress> toSignalServiceAddresses(@NonNull Context context, @NonNull List<RecipientId> recipients) public static @NonNull List<SignalServiceAddress> toSignalServiceAddresses(@NonNull Context context, @NonNull List<RecipientId> recipients)
throws IOException throws IOException
{ {
List<SignalServiceAddress> addresses = new ArrayList<>(recipients.size()); return toSignalServiceAddressesFromResolved(context, Recipient.resolvedList(recipients));
for (RecipientId id : recipients) {
addresses.add(toSignalServiceAddress(context, Recipient.resolved(id)));
}
return addresses;
} }
public static @NonNull List<SignalServiceAddress> toSignalServiceAddressesFromResolved(@NonNull Context context, @NonNull List<Recipient> recipients) public static @NonNull List<SignalServiceAddress> toSignalServiceAddressesFromResolved(@NonNull Context context, @NonNull List<Recipient> recipients)
throws IOException throws IOException
{ {
List<SignalServiceAddress> addresses = new ArrayList<>(recipients.size()); if (FeatureFlags.cds()) {
List<Recipient> recipientsWithoutUuids = Stream.of(recipients)
.map(Recipient::resolve)
.filterNot(Recipient::hasUuid)
.toList();
for (Recipient recipient : recipients) { if (recipientsWithoutUuids.size() > 0) {
addresses.add(toSignalServiceAddress(context, recipient)); DirectoryHelper.refreshDirectoryFor(context, recipientsWithoutUuids, false);
}
} }
return addresses; return Stream.of(recipients)
.map(Recipient::resolve)
.map(r -> new SignalServiceAddress(r.getUuid().orNull(), r.getE164().orNull()))
.toList();
} }
public static boolean isBlockable(@NonNull Recipient recipient) { public static boolean isBlockable(@NonNull Recipient recipient) {
@ -105,6 +110,16 @@ public class RecipientUtil {
return resolved.isPushGroup() || resolved.hasServiceIdentifier(); return resolved.isPushGroup() || resolved.hasServiceIdentifier();
} }
public static List<Recipient> getEligibleForSending(@NonNull List<Recipient> recipients) {
if (FeatureFlags.cds()) {
return Stream.of(recipients)
.filter(r -> r.getRegistered() != RegisteredState.NOT_REGISTERED)
.toList();
} else {
return recipients;
}
}
/** /**
* You can call this for non-groups and not have to handle any network errors. * You can call this for non-groups and not have to handle any network errors.
*/ */

View File

@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit;
public class DirectoryRefreshListener extends PersistentAlarmManagerListener { public class DirectoryRefreshListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.HOURS.toMillis(12); private static final long INTERVAL = TimeUnit.HOURS.toMillis(6);
@Override @Override
protected long getNextScheduledExecutionTime(Context context) { protected long getNextScheduledExecutionTime(Context context) {

View File

@ -59,7 +59,7 @@ public final class FeatureFlags {
private static final String GROUPS_V2_JOIN_VERSION = "android.groupsv2.joinVersion"; private static final String GROUPS_V2_JOIN_VERSION = "android.groupsv2.joinVersion";
private static final String GROUPS_V2_LINKS_VERSION = "android.groupsv2.manageGroupLinksVersion"; private static final String GROUPS_V2_LINKS_VERSION = "android.groupsv2.manageGroupLinksVersion";
private static final String GROUPS_V2_CAPACITY = "global.groupsv2.maxGroupSize"; private static final String GROUPS_V2_CAPACITY = "global.groupsv2.maxGroupSize";
private static final String CDS = "android.cds.4"; private static final String CDS_VERSION = "android.cdsVersion";
private static final String INTERNAL_USER = "android.internalUser"; private static final String INTERNAL_USER = "android.internalUser";
private static final String MENTIONS = "android.mentions"; private static final String MENTIONS = "android.mentions";
private static final String VERIFY_V2 = "android.verifyV2"; private static final String VERIFY_V2 = "android.verifyV2";
@ -77,7 +77,7 @@ public final class FeatureFlags {
GROUPS_V2_CAPACITY, GROUPS_V2_CAPACITY,
GROUPS_V2_JOIN_VERSION, GROUPS_V2_JOIN_VERSION,
GROUPS_V2_LINKS_VERSION, GROUPS_V2_LINKS_VERSION,
CDS, CDS_VERSION,
INTERNAL_USER, INTERNAL_USER,
USERNAMES, USERNAMES,
MENTIONS, MENTIONS,
@ -106,7 +106,7 @@ public final class FeatureFlags {
GROUPS_V2_CREATE, GROUPS_V2_CREATE,
GROUPS_V2_JOIN_VERSION, GROUPS_V2_JOIN_VERSION,
VERIFY_V2, VERIFY_V2,
CDS CDS_VERSION
); );
/** /**
@ -266,7 +266,7 @@ public final class FeatureFlags {
/** Whether or not to use the new contact discovery service endpoint, which supports UUIDs. */ /** Whether or not to use the new contact discovery service endpoint, which supports UUIDs. */
public static boolean cds() { public static boolean cds() {
return getBoolean(CDS, false); return getVersionFlag(CDS_VERSION) == VersionFlag.ON;
} }
/** Whether or not we allow mentions send support in groups. */ /** Whether or not we allow mentions send support in groups. */

View File

@ -368,6 +368,10 @@ public class SignalServiceAccountManager {
public Map<String, UUID> getRegisteredUsers(KeyStore iasKeyStore, Set<String> e164numbers, String mrenclave) public Map<String, UUID> getRegisteredUsers(KeyStore iasKeyStore, Set<String> e164numbers, String mrenclave)
throws IOException, Quote.InvalidQuoteFormatException, UnauthenticatedQuoteException, SignatureException, UnauthenticatedResponseException throws IOException, Quote.InvalidQuoteFormatException, UnauthenticatedQuoteException, SignatureException, UnauthenticatedResponseException
{ {
if (e164numbers.isEmpty()) {
return Collections.emptyMap();
}
try { try {
String authorization = this.pushServiceSocket.getContactDiscoveryAuthorization(); String authorization = this.pushServiceSocket.getContactDiscoveryAuthorization();
Map<String, RemoteAttestation> attestations = RemoteAttestationUtil.getAndVerifyMultiRemoteAttestation(pushServiceSocket, Map<String, RemoteAttestation> attestations = RemoteAttestationUtil.getAndVerifyMultiRemoteAttestation(pushServiceSocket,