From 70e33518a9d5a9dec822420c2667268bb4fe527a Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 2 Jul 2020 08:17:44 -0700 Subject: [PATCH] Do registration checks for new numbers during group creation. --- .../groups/GroupsV2CapabilityChecker.java | 48 ++++++++--- .../ui/creategroup/CreateGroupActivity.java | 85 ++++++++++++++++++- .../securesms/jobs/RetrieveProfileJob.java | 46 ++++++---- 3 files changed, 145 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2CapabilityChecker.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2CapabilityChecker.java index c43f507bdc..157726a108 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2CapabilityChecker.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2CapabilityChecker.java @@ -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 resolved) throws IOException { + List 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 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 recipientIds) @@ -37,25 +66,16 @@ public final class GroupsV2CapabilityChecker { boolean allSupportGroupsV2AndUuid(@NonNull Collection recipientIds) throws IOException { - final HashSet recipientIdsSet = new HashSet<>(recipientIds); + Set recipientIdsSet = new HashSet<>(recipientIds); + Set 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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java index 33008a0ec0..f0e4cb4bf5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java @@ -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 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 resolved = Stream.of(ids) + .map(Recipient::resolved) + .toList(); + + stopwatch.split("resolve"); + + List 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); + }); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java index 49fc0bc1c8..653bf8ab9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java @@ -101,24 +101,11 @@ public class RetrieveProfileJob extends BaseJob { */ @WorkerThread public static void enqueue(@NonNull Collection recipientIds) { - Context context = ApplicationDependencies.getApplication(); - JobManager jobManager = ApplicationDependencies.getJobManager(); - List 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 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 forRecipients(@NonNull Collection recipientIds) { + Context context = ApplicationDependencies.getApplication(); + List combined = new LinkedList<>(); + List jobs = new LinkedList<>(); + + for (RecipientId recipientId : recipientIds) { + Recipient recipient = Recipient.resolved(recipientId); + + if (recipient.isLocalNumber()) { + jobs.add(new RefreshOwnProfileJob()); + } else if (recipient.isGroup()) { + List 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.