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); NotificationChannels.create(this);
RefreshPreKeysJob.scheduleIfNecessary(); RefreshPreKeysJob.scheduleIfNecessary();
StorageSyncHelper.scheduleRoutineSync(); StorageSyncHelper.scheduleRoutineSync();
RetrieveProfileJob.enqueueRoutineFetchIfNeccessary(this); RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
RegistrationUtil.maybeMarkRegistrationComplete(this); RegistrationUtil.maybeMarkRegistrationComplete(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(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()); List<Recipient> recipients = new ArrayList<>(currentMembers.size());
for (RecipientId member : currentMembers) { for (RecipientId member : currentMembers) {
if (memberSet.includeSelf || !Recipient.resolved(member).isLocalNumber()) { Recipient resolved = Recipient.resolved(member);
recipients.add(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) { public List<Recipient> getMemberRecipients(@NonNull MemberSet memberSet) {
return Stream.of(getMemberRecipientIds(memberSet)) return Recipient.resolvedList(getMemberRecipientIds(memberSet));
.map(Recipient::resolved)
.toList();
} }
public List<RecipientId> getMemberRecipientIds(@NonNull MemberSet 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 * If from authoritative source, this will overwrite local, otherwise it will only write to the
* database if missing. * 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> profileKeys = profileKeySet.getProfileKeys();
Map<UUID, ProfileKey> authoritativeProfileKeys = profileKeySet.getAuthoritativeProfileKeys(); Map<UUID, ProfileKey> authoritativeProfileKeys = profileKeySet.getAuthoritativeProfileKeys();
int totalKeys = profileKeys.size() + authoritativeProfileKeys.size(); int totalKeys = profileKeys.size() + authoritativeProfileKeys.size();
if (totalKeys == 0) { 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())); 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 static final String TAG = Log.tag(GroupManagerV2.class);
private final Context context; private final Context context;
private final GroupDatabase groupDatabase; private final GroupDatabase groupDatabase;
private final GroupsV2Api groupsV2Api; private final GroupsV2Api groupsV2Api;
private final GroupsV2Operations groupsV2Operations; private final GroupsV2Operations groupsV2Operations;
private final GroupsV2Authorization authorization; private final GroupsV2Authorization authorization;
private final GroupsV2StateProcessor groupsV2StateProcessor; private final GroupsV2StateProcessor groupsV2StateProcessor;
private final UUID selfUuid; private final UUID selfUuid;
private final GroupCandidateHelper groupCandidateHelper; private final GroupCandidateHelper groupCandidateHelper;
private final GroupsV2CapabilityChecker capabilityChecker;
GroupManagerV2(@NonNull Context context) { GroupManagerV2(@NonNull Context context) {
this.context = context; this.context = context;
@ -84,7 +83,6 @@ final class GroupManagerV2 {
this.groupsV2StateProcessor = ApplicationDependencies.getGroupsV2StateProcessor(); this.groupsV2StateProcessor = ApplicationDependencies.getGroupsV2StateProcessor();
this.selfUuid = Recipient.self().getUuid().get(); this.selfUuid = Recipient.self().getUuid().get();
this.groupCandidateHelper = new GroupCandidateHelper(context); this.groupCandidateHelper = new GroupCandidateHelper(context);
this.capabilityChecker = new GroupsV2CapabilityChecker();
} }
@WorkerThread @WorkerThread
@ -116,7 +114,7 @@ final class GroupManagerV2 {
@Nullable byte[] avatar) @Nullable byte[] avatar)
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception 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"); 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) @NonNull GroupManager.GroupActionResult addMembers(@NonNull Collection<RecipientId> newMembers)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, MembershipNotSuitableForV2Exception 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"); 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.NonNull;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@ -24,17 +25,17 @@ public final class GroupsV2CapabilityChecker {
private static final String TAG = Log.tag(GroupsV2CapabilityChecker.class); private static final String TAG = Log.tag(GroupsV2CapabilityChecker.class);
public GroupsV2CapabilityChecker() {} private GroupsV2CapabilityChecker() {}
/** /**
* @param resolved A collection of resolved recipients. * @param resolved A collection of resolved recipients.
*/ */
@WorkerThread @WorkerThread
public void refreshCapabilitiesIfNecessary(@NonNull Collection<Recipient> resolved) throws IOException { public static void refreshCapabilitiesIfNecessary(@NonNull Collection<Recipient> resolved) throws IOException {
List<RecipientId> needsRefresh = Stream.of(resolved) Set<RecipientId> needsRefresh = Stream.of(resolved)
.filter(r -> r.getGroupsV2Capability() != Recipient.Capability.SUPPORTED) .filter(r -> r.getGroupsV2Capability() != Recipient.Capability.SUPPORTED)
.map(Recipient::getId) .map(Recipient::getId)
.toList(); .collect(Collectors.toSet());
if (needsRefresh.size() > 0) { if (needsRefresh.size() > 0) {
Log.d(TAG, "[refreshCapabilitiesIfNecessary] Need to refresh " + needsRefresh.size() + " recipients."); Log.d(TAG, "[refreshCapabilitiesIfNecessary] Need to refresh " + needsRefresh.size() + " recipients.");
@ -51,7 +52,7 @@ public final class GroupsV2CapabilityChecker {
} }
@WorkerThread @WorkerThread
boolean allAndSelfSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds) static boolean allAndSelfSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds)
throws IOException throws IOException
{ {
HashSet<RecipientId> recipientIdsSet = new HashSet<>(recipientIds); HashSet<RecipientId> recipientIdsSet = new HashSet<>(recipientIds);
@ -62,7 +63,7 @@ public final class GroupsV2CapabilityChecker {
} }
@WorkerThread @WorkerThread
boolean allSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds) static boolean allSupportGroupsV2AndUuid(@NonNull Collection<RecipientId> recipientIds)
throws IOException throws IOException
{ {
Set<RecipientId> recipientIdsSet = new HashSet<>(recipientIds); Set<RecipientId> recipientIdsSet = new HashSet<>(recipientIds);

View File

@ -152,7 +152,7 @@ public class CreateGroupActivity extends ContactSelectionActivity {
if (FeatureFlags.groupsV2create()) { if (FeatureFlags.groupsV2create()) {
try { try {
new GroupsV2CapabilityChecker().refreshCapabilitiesIfNecessary(resolved); GroupsV2CapabilityChecker.refreshCapabilitiesIfNecessary(resolved);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "Failed to refresh all recipient capabilities.", 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.GroupProtoUtil;
import org.thoughtcrime.securesms.groups.GroupsV2Authorization; import org.thoughtcrime.securesms.groups.GroupsV2Authorization;
import org.thoughtcrime.securesms.groups.v2.ProfileKeySet; import org.thoughtcrime.securesms.groups.v2.ProfileKeySet;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.JobTracker;
import org.thoughtcrime.securesms.jobs.AvatarGroupsV2DownloadJob; import org.thoughtcrime.securesms.jobs.AvatarGroupsV2DownloadJob;
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob; import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
@ -51,6 +53,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import java.util.UUID; 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()) { if (!updated.isEmpty()) {
Log.i(TAG, String.format(Locale.US, "Learned %d new profile keys, scheduling profile retrievals", updated.size())); Log.i(TAG, String.format(Locale.US, "Learned %d new profile keys, fetching profiles", updated.size()));
RetrieveProfileJob.enqueue(updated);
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.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class PushGroupSendJob extends PushSendJob { public final class PushGroupSendJob extends PushSendJob {
public static final String KEY = "PushGroupSendJob"; 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_MESSAGE_ID = "message_id";
private static final String KEY_FILTER_RECIPIENT = "filter_recipient"; private static final String KEY_FILTER_RECIPIENT = "filter_recipient";
private long messageId; private final long messageId;
private RecipientId filterRecipient; private final RecipientId filterRecipient;
public PushGroupSendJob(long messageId, @NonNull RecipientId destination, @Nullable RecipientId filterRecipient, boolean hasMedia) { public PushGroupSendJob(long messageId, @NonNull RecipientId destination, @Nullable RecipientId filterRecipient, boolean hasMedia) {
this(new Job.Parameters.Builder() this(new Job.Parameters.Builder()
@ -237,7 +237,10 @@ public class PushGroupSendJob extends PushSendJob {
database.markAsSentFailed(messageId); database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, 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); RetrieveProfileJob.enqueue(mismatchRecipientIds);
} }
} catch (UntrustedIdentityException | UndeliverableMessageException e) { } catch (UntrustedIdentityException | UndeliverableMessageException e) {

View File

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