Do registration checks for new numbers during group creation.

master
Greyson Parrelli 2020-07-02 08:17:44 -07:00
parent cb81a9f783
commit 70e33518a9
3 changed files with 145 additions and 34 deletions

View File

@ -3,7 +3,12 @@ package org.thoughtcrime.securesms.groups;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -12,15 +17,39 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public final class GroupsV2CapabilityChecker {
private static final String TAG = Log.tag(GroupsV2CapabilityChecker.class);
GroupsV2CapabilityChecker() {}
public GroupsV2CapabilityChecker() {}
/**
* @param resolved A collection of resolved recipients.
*/
@WorkerThread
public void refreshCapabilitiesIfNecessary(@NonNull Collection<Recipient> resolved) throws IOException {
List<RecipientId> needsRefresh = Stream.of(resolved)
.filter(r -> r.getGroupsV2Capability() != Recipient.Capability.SUPPORTED)
.map(Recipient::getId)
.toList();
if (needsRefresh.size() > 0) {
Log.d(TAG, "[refreshCapabilitiesIfNecessary] Need to refresh " + needsRefresh.size() + " recipients.");
List<Job> jobs = RetrieveProfileJob.forRecipients(needsRefresh);
JobManager jobManager = ApplicationDependencies.getJobManager();
for (Job job : jobs) {
if (!jobManager.runSynchronously(job, TimeUnit.SECONDS.toMillis(5000)).isPresent()) {
throw new IOException("Recipient capability was not retrieved in time");
}
}
}
}
@WorkerThread
boolean allAndSelfSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds)
@ -37,25 +66,16 @@ public final class GroupsV2CapabilityChecker {
boolean allSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds)
throws IOException
{
final HashSet<RecipientId> recipientIdsSet = new HashSet<>(recipientIds);
Set<RecipientId> recipientIdsSet = new HashSet<>(recipientIds);
Set<Recipient> resolved = Stream.of(recipientIdsSet).map(Recipient::resolved).collect(Collectors.toSet());
for (RecipientId recipientId : recipientIdsSet) {
Recipient member = Recipient.resolved(recipientId);
Recipient.Capability gv2Capability = member.getGroupsV2Capability();
if (gv2Capability != Recipient.Capability.SUPPORTED) {
if (!ApplicationDependencies.getJobManager().runSynchronously(RetrieveProfileJob.forRecipient(member.getId()), TimeUnit.SECONDS.toMillis(1000)).isPresent()) {
throw new IOException("Recipient capability was not retrieved in time");
}
}
}
refreshCapabilitiesIfNecessary(resolved);
boolean noSelfGV2Support = false;
int noGv2Count = 0;
int noUuidCount = 0;
for (RecipientId recipientId : recipientIdsSet) {
Recipient member = Recipient.resolved(recipientId);
for (Recipient member : resolved) {
Recipient.Capability gv2Capability = member.getGroupsV2Capability();
if (gv2Capability != Recipient.Capability.SUPPORTED) {

View File

@ -8,6 +8,7 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.annimon.stream.Stream;
@ -15,14 +16,37 @@ import org.thoughtcrime.securesms.ContactSelectionActivity;
import org.thoughtcrime.securesms.ContactSelectionListFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.groups.GroupsV2CapabilityChecker;
import org.thoughtcrime.securesms.groups.ui.creategroup.details.AddGroupDetailsActivity;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
public class CreateGroupActivity extends ContactSelectionActivity {
private static String TAG = Log.tag(CreateGroupActivity.class);
private static final int MINIMUM_GROUP_SIZE = 1;
private static final short REQUEST_CODE_ADD_DETAILS = 17275;
@ -109,10 +133,63 @@ public class CreateGroupActivity extends ContactSelectionActivity {
}
private void handleNextPressed() {
RecipientId[] ids = Stream.of(contactsFragment.getSelectedContacts())
.map(selectedContact -> selectedContact.getOrCreateRecipientId(this))
.toArray(RecipientId[]::new);
Stopwatch stopwatch = new Stopwatch("Recipient Refresh");
AtomicReference<AlertDialog> progressDialog = new AtomicReference<>();
startActivityForResult(AddGroupDetailsActivity.newIntent(this, ids), REQUEST_CODE_ADD_DETAILS);
Runnable showDialogRunnable = () -> {
Log.i(TAG, "Taking some time. Showing a progress dialog.");
progressDialog.set(SimpleProgressDialog.show(this));
};
next.postDelayed(showDialogRunnable, 300);
SimpleTask.run(getLifecycle(), () -> {
RecipientId[] ids = Stream.of(contactsFragment.getSelectedContacts())
.map(selectedContact -> selectedContact.getOrCreateRecipientId(this))
.toArray(RecipientId[]::new);
List<Recipient> resolved = Stream.of(ids)
.map(Recipient::resolved)
.toList();
stopwatch.split("resolve");
List<Recipient> registeredChecks = Stream.of(resolved)
.filter(r -> r.getRegistered() == RecipientDatabase.RegisteredState.UNKNOWN)
.toList();
Log.i(TAG, "Need to do " + registeredChecks.size() + " registration checks.");
for (Recipient recipient : registeredChecks) {
try {
DirectoryHelper.refreshDirectoryFor(this, recipient, false);
} catch (IOException e) {
Log.w(TAG, "Failed to refresh registered status for " + recipient.getId(), e);
}
}
stopwatch.split("registered");
if (FeatureFlags.groupsV2()) {
try {
new GroupsV2CapabilityChecker().refreshCapabilitiesIfNecessary(resolved);
} catch (IOException e) {
Log.w(TAG, "Failed to refresh all recipient capabilities.", e);
}
}
stopwatch.split("capabilities");
return ids;
}, ids -> {
if (progressDialog.get() != null) {
progressDialog.get().dismiss();
}
next.removeCallbacks(showDialogRunnable);
stopwatch.stop(TAG);
startActivityForResult(AddGroupDetailsActivity.newIntent(this, ids), REQUEST_CODE_ADD_DETAILS);
});
}
}

View File

@ -101,24 +101,11 @@ public class RetrieveProfileJob extends BaseJob {
*/
@WorkerThread
public static void enqueue(@NonNull Collection<RecipientId> recipientIds) {
Context context = ApplicationDependencies.getApplication();
JobManager jobManager = ApplicationDependencies.getJobManager();
List<RecipientId> combined = new LinkedList<>();
JobManager jobManager = ApplicationDependencies.getJobManager();
for (RecipientId recipientId : recipientIds) {
Recipient recipient = Recipient.resolved(recipientId);
if (recipient.isLocalNumber()) {
jobManager.add(new RefreshOwnProfileJob());
} else if (recipient.isGroup()) {
List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
combined.addAll(Stream.of(recipients).map(Recipient::getId).toList());
} else {
combined.add(recipientId);
}
for (Job job : forRecipients(recipientIds)) {
jobManager.add(job);
}
jobManager.add(new RetrieveProfileJob(combined));
}
/**
@ -140,6 +127,33 @@ public class RetrieveProfileJob extends BaseJob {
}
}
/**
* Works for any RecipientId, whether it's an individual, group, or yourself.
*/
@WorkerThread
public static @NonNull List<Job> forRecipients(@NonNull Collection<RecipientId> recipientIds) {
Context context = ApplicationDependencies.getApplication();
List<RecipientId> combined = new LinkedList<>();
List<Job> jobs = new LinkedList<>();
for (RecipientId recipientId : recipientIds) {
Recipient recipient = Recipient.resolved(recipientId);
if (recipient.isLocalNumber()) {
jobs.add(new RefreshOwnProfileJob());
} else if (recipient.isGroup()) {
List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
combined.addAll(Stream.of(recipients).map(Recipient::getId).toList());
} else {
combined.add(recipientId);
}
}
jobs.add(new RetrieveProfileJob(combined));
return jobs;
}
/**
* Will fetch some profiles to ensure we're decently up-to-date if we haven't done so within a
* certain time period.