Fetch newly found profiles on Groups V2 inline.
parent
12533d1414
commit
8cb9ab3204
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue