Fetch newly found profiles on Groups V2 inline.

master
Alan Evans 2020-07-24 16:53:49 -03:00 committed by Greyson Parrelli
parent 12533d1414
commit 8cb9ab3204
9 changed files with 76 additions and 62 deletions

View File

@ -132,7 +132,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
NotificationChannels.create(this);
RefreshPreKeysJob.scheduleIfNecessary();
StorageSyncHelper.scheduleRoutineSync();
RetrieveProfileJob.enqueueRoutineFetchIfNeccessary(this);
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
RegistrationUtil.maybeMarkRegistrationComplete(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);

View File

@ -286,8 +286,9 @@ public final class GroupDatabase extends Database {
List<Recipient> recipients = new ArrayList<>(currentMembers.size());
for (RecipientId member : currentMembers) {
if (memberSet.includeSelf || !Recipient.resolved(member).isLocalNumber()) {
recipients.add(Recipient.resolved(member));
Recipient resolved = Recipient.resolved(member);
if (memberSet.includeSelf || !resolved.isLocalNumber()) {
recipients.add(resolved);
}
}
@ -817,9 +818,7 @@ public final class GroupDatabase extends Database {
}
public List<Recipient> getMemberRecipients(@NonNull MemberSet memberSet) {
return Stream.of(getMemberRecipientIds(memberSet))
.map(Recipient::resolved)
.toList();
return Recipient.resolvedList(getMemberRecipientIds(memberSet));
}
public List<RecipientId> getMemberRecipientIds(@NonNull MemberSet memberSet) {

View File

@ -1391,13 +1391,13 @@ public class RecipientDatabase extends Database {
* If from authoritative source, this will overwrite local, otherwise it will only write to the
* database if missing.
*/
public Collection<RecipientId> persistProfileKeySet(@NonNull ProfileKeySet profileKeySet) {
public Set<RecipientId> persistProfileKeySet(@NonNull ProfileKeySet profileKeySet) {
Map<UUID, ProfileKey> profileKeys = profileKeySet.getProfileKeys();
Map<UUID, ProfileKey> authoritativeProfileKeys = profileKeySet.getAuthoritativeProfileKeys();
int totalKeys = profileKeys.size() + authoritativeProfileKeys.size();
if (totalKeys == 0) {
return Collections.emptyList();
return Collections.emptySet();
}
Log.i(TAG, String.format(Locale.US, "Persisting %d Profile keys, %d of which are authoritative", totalKeys, authoritativeProfileKeys.size()));

View File

@ -65,15 +65,14 @@ final class GroupManagerV2 {
private static final String TAG = Log.tag(GroupManagerV2.class);
private final Context context;
private final GroupDatabase groupDatabase;
private final GroupsV2Api groupsV2Api;
private final GroupsV2Operations groupsV2Operations;
private final GroupsV2Authorization authorization;
private final GroupsV2StateProcessor groupsV2StateProcessor;
private final UUID selfUuid;
private final GroupCandidateHelper groupCandidateHelper;
private final GroupsV2CapabilityChecker capabilityChecker;
private final Context context;
private final GroupDatabase groupDatabase;
private final GroupsV2Api groupsV2Api;
private final GroupsV2Operations groupsV2Operations;
private final GroupsV2Authorization authorization;
private final GroupsV2StateProcessor groupsV2StateProcessor;
private final UUID selfUuid;
private final GroupCandidateHelper groupCandidateHelper;
GroupManagerV2(@NonNull Context context) {
this.context = context;
@ -84,7 +83,6 @@ final class GroupManagerV2 {
this.groupsV2StateProcessor = ApplicationDependencies.getGroupsV2StateProcessor();
this.selfUuid = Recipient.self().getUuid().get();
this.groupCandidateHelper = new GroupCandidateHelper(context);
this.capabilityChecker = new GroupsV2CapabilityChecker();
}
@WorkerThread
@ -116,7 +114,7 @@ final class GroupManagerV2 {
@Nullable byte[] avatar)
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception
{
if (!capabilityChecker.allAndSelfSupportGroupsV2AndUuid(members)) {
if (!GroupsV2CapabilityChecker.allAndSelfSupportGroupsV2AndUuid(members)) {
throw new MembershipNotSuitableForV2Exception("At least one potential new member does not support GV2 or UUID capabilities");
}
@ -196,7 +194,7 @@ final class GroupManagerV2 {
@NonNull GroupManager.GroupActionResult addMembers(@NonNull Collection<RecipientId> newMembers)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, MembershipNotSuitableForV2Exception
{
if (!capabilityChecker.allSupportGroupsV2AndUuid(newMembers)) {
if (!GroupsV2CapabilityChecker.allSupportGroupsV2AndUuid(newMembers)) {
throw new MembershipNotSuitableForV2Exception("At least one potential new member does not support GV2 or UUID capabilities");
}

View File

@ -3,6 +3,7 @@ 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;
@ -24,17 +25,17 @@ public final class GroupsV2CapabilityChecker {
private static final String TAG = Log.tag(GroupsV2CapabilityChecker.class);
public GroupsV2CapabilityChecker() {}
private 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();
public static void refreshCapabilitiesIfNecessary(@NonNull Collection<Recipient> resolved) throws IOException {
Set<RecipientId> needsRefresh = Stream.of(resolved)
.filter(r -> r.getGroupsV2Capability() != Recipient.Capability.SUPPORTED)
.map(Recipient::getId)
.collect(Collectors.toSet());
if (needsRefresh.size() > 0) {
Log.d(TAG, "[refreshCapabilitiesIfNecessary] Need to refresh " + needsRefresh.size() + " recipients.");
@ -51,7 +52,7 @@ public final class GroupsV2CapabilityChecker {
}
@WorkerThread
boolean allAndSelfSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds)
static boolean allAndSelfSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds)
throws IOException
{
HashSet<RecipientId> recipientIdsSet = new HashSet<>(recipientIds);
@ -62,7 +63,7 @@ public final class GroupsV2CapabilityChecker {
}
@WorkerThread
boolean allSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds)
static boolean allSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds)
throws IOException
{
Set<RecipientId> recipientIdsSet = new HashSet<>(recipientIds);

View File

@ -152,7 +152,7 @@ public class CreateGroupActivity extends ContactSelectionActivity {
if (FeatureFlags.groupsV2create()) {
try {
new GroupsV2CapabilityChecker().refreshCapabilitiesIfNecessary(resolved);
GroupsV2CapabilityChecker.refreshCapabilitiesIfNecessary(resolved);
} catch (IOException e) {
Log.w(TAG, "Failed to refresh all recipient capabilities.", e);
}

View File

@ -25,7 +25,9 @@ import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
import org.thoughtcrime.securesms.groups.GroupProtoUtil;
import org.thoughtcrime.securesms.groups.GroupsV2Authorization;
import org.thoughtcrime.securesms.groups.v2.ProfileKeySet;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.JobTracker;
import org.thoughtcrime.securesms.jobs.AvatarGroupsV2DownloadJob;
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
@ -51,6 +53,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
/**
@ -297,11 +300,14 @@ public final class GroupsV2StateProcessor {
}
}
Collection<RecipientId> updated = recipientDatabase.persistProfileKeySet(profileKeys);
Set<RecipientId> updated = recipientDatabase.persistProfileKeySet(profileKeys);
if (!updated.isEmpty()) {
Log.i(TAG, String.format(Locale.US, "Learned %d new profile keys, scheduling profile retrievals", updated.size()));
RetrieveProfileJob.enqueue(updated);
Log.i(TAG, String.format(Locale.US, "Learned %d new profile keys, fetching profiles", updated.size()));
for (Job job : RetrieveProfileJob.forRecipients(updated)) {
jobManager.runSynchronously(job, 5000);
}
}
}

View File

@ -65,7 +65,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class PushGroupSendJob extends PushSendJob {
public final class PushGroupSendJob extends PushSendJob {
public static final String KEY = "PushGroupSendJob";
@ -74,8 +74,8 @@ public class PushGroupSendJob extends PushSendJob {
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_FILTER_RECIPIENT = "filter_recipient";
private long messageId;
private RecipientId filterRecipient;
private final long messageId;
private final RecipientId filterRecipient;
public PushGroupSendJob(long messageId, @NonNull RecipientId destination, @Nullable RecipientId filterRecipient, boolean hasMedia) {
this(new Job.Parameters.Builder()
@ -237,7 +237,10 @@ public class PushGroupSendJob extends PushSendJob {
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
List<RecipientId> mismatchRecipientIds = Stream.of(identityMismatches).map(mismatch -> mismatch.getRecipientId(context)).toList();
Set<RecipientId> mismatchRecipientIds = Stream.of(identityMismatches)
.map(mismatch -> mismatch.getRecipientId(context))
.collect(Collectors.toSet());
RetrieveProfileJob.enqueue(mismatchRecipientIds);
}
} catch (UntrustedIdentityException | UndeliverableMessageException e) {

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.jobs;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
@ -9,6 +8,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.signal.zkgroup.profiles.ProfileKey;
@ -50,10 +50,9 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
import java.io.IOException;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@ -73,22 +72,20 @@ public class RetrieveProfileJob extends BaseJob {
private static final String KEY_RECIPIENTS = "recipients";
private final List<RecipientId> recipientIds;
private final Set<RecipientId> recipientIds;
/**
* Identical to {@link #enqueue(Collection)})}, but run on a background thread for convenience.
* Identical to {@link #enqueue(Set)})}, but run on a background thread for convenience.
*/
public static void enqueueAsync(@NonNull RecipientId recipientId) {
SignalExecutors.BOUNDED.execute(() -> {
ApplicationDependencies.getJobManager().add(forRecipient(recipientId));
});
SignalExecutors.BOUNDED.execute(() -> ApplicationDependencies.getJobManager().add(forRecipient(recipientId)));
}
/**
* Submits the necessary job to refresh the profile of the requested recipient. Works for any
* RecipientId, including individuals, groups, or yourself.
*
* Identical to {@link #enqueue(Collection)})}
* Identical to {@link #enqueue(Set)})}
*/
@WorkerThread
public static void enqueue(@NonNull RecipientId recipientId) {
@ -100,7 +97,7 @@ public class RetrieveProfileJob extends BaseJob {
* RecipientIds, including individuals, groups, or yourself.
*/
@WorkerThread
public static void enqueue(@NonNull Collection<RecipientId> recipientIds) {
public static void enqueue(@NonNull Set<RecipientId> recipientIds) {
JobManager jobManager = ApplicationDependencies.getJobManager();
for (Job job : forRecipients(recipientIds)) {
@ -121,26 +118,28 @@ public class RetrieveProfileJob extends BaseJob {
Context context = ApplicationDependencies.getApplication();
List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
return new RetrieveProfileJob(Stream.of(recipients).map(Recipient::getId).toList());
return new RetrieveProfileJob(Stream.of(recipients).map(Recipient::getId).collect(Collectors.toSet()));
} else {
return new RetrieveProfileJob(Collections.singletonList(recipientId));
return new RetrieveProfileJob(Collections.singleton(recipientId));
}
}
/**
* Works for any RecipientId, whether it's an individual, group, or yourself.
*
* @return A list of length 2 or less. Two iff you are in the recipients.
*/
@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<>();
public static @NonNull List<Job> forRecipients(@NonNull Set<RecipientId> recipientIds) {
Context context = ApplicationDependencies.getApplication();
Set<RecipientId> combined = new HashSet<>(recipientIds.size());
boolean includeSelf = false;
for (RecipientId recipientId : recipientIds) {
Recipient recipient = Recipient.resolved(recipientId);
if (recipient.isLocalNumber()) {
jobs.add(new RefreshOwnProfileJob());
includeSelf = true;
} 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());
@ -149,7 +148,15 @@ public class RetrieveProfileJob extends BaseJob {
}
}
jobs.add(new RetrieveProfileJob(combined));
List<Job> jobs = new ArrayList<>(2);
if (includeSelf) {
jobs.add(new RefreshOwnProfileJob());
}
if (combined.size() > 0) {
jobs.add(new RetrieveProfileJob(combined));
}
return jobs;
}
@ -158,7 +165,7 @@ public class RetrieveProfileJob extends BaseJob {
* Will fetch some profiles to ensure we're decently up-to-date if we haven't done so within a
* certain time period.
*/
public static void enqueueRoutineFetchIfNeccessary(Application application) {
public static void enqueueRoutineFetchIfNecessary(Application application) {
long timeSinceRefresh = System.currentTimeMillis() - SignalStore.misc().getLastProfileRefreshTime();
if (timeSinceRefresh < TimeUnit.HOURS.toMillis(12)) {
Log.i(TAG, "Too soon to refresh. Did the last refresh " + timeSinceRefresh + " ms ago.");
@ -175,7 +182,7 @@ public class RetrieveProfileJob extends BaseJob {
if (ids.size() > 0) {
Log.i(TAG, "Optimistically refreshing " + ids.size() + " eligible recipient(s).");
enqueue(ids);
enqueue(new HashSet<>(ids));
} else {
Log.i(TAG, "No recipients to refresh.");
}
@ -184,7 +191,7 @@ public class RetrieveProfileJob extends BaseJob {
});
}
private RetrieveProfileJob(@NonNull List<RecipientId> recipientIds) {
private RetrieveProfileJob(@NonNull Set<RecipientId> recipientIds) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(3)
@ -192,7 +199,7 @@ public class RetrieveProfileJob extends BaseJob {
recipientIds);
}
private RetrieveProfileJob(@NonNull Job.Parameters parameters, @NonNull List<RecipientId> recipientIds) {
private RetrieveProfileJob(@NonNull Job.Parameters parameters, @NonNull Set<RecipientId> recipientIds) {
super(parameters);
this.recipientIds = recipientIds;
}
@ -279,7 +286,7 @@ public class RetrieveProfileJob extends BaseJob {
@Override
public void onFailure() {}
private void process(Recipient recipient, ProfileAndCredential profileAndCredential) throws IOException {
private void process(Recipient recipient, ProfileAndCredential profileAndCredential) {
SignalServiceProfile profile = profileAndCredential.getProfile();
ProfileKey recipientProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
@ -410,7 +417,7 @@ public class RetrieveProfileJob extends BaseJob {
}
}
private void setProfileAvatar(Recipient recipient, String profileAvatar) {
private static void setProfileAvatar(Recipient recipient, String profileAvatar) {
if (recipient.getProfileKey() == null) return;
if (!Util.equals(profileAvatar, recipient.getProfileAvatar())) {
@ -434,8 +441,8 @@ public class RetrieveProfileJob extends BaseJob {
@Override
public @NonNull RetrieveProfileJob create(@NonNull Parameters parameters, @NonNull Data data) {
String[] ids = data.getStringArray(KEY_RECIPIENTS);
List<RecipientId> recipientIds = Stream.of(ids).map(RecipientId::from).toList();
String[] ids = data.getStringArray(KEY_RECIPIENTS);
Set<RecipientId> recipientIds = Stream.of(ids).map(RecipientId::from).collect(Collectors.toSet());
return new RetrieveProfileJob(parameters, recipientIds);
}