Improve GV2 update speed by only requesting a full snapshot when necessary.

This commit is contained in:
Cody Henthorne
2022-02-09 14:52:01 -05:00
committed by GitHub
parent 210bb23aa4
commit bb1e6ffae0
9 changed files with 797 additions and 181 deletions

View File

@@ -58,7 +58,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public final class GroupDatabase extends Database {
public class GroupDatabase extends Database {
private static final String TAG = Log.tag(GroupDatabase.class);

View File

@@ -16,7 +16,7 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public final class GroupsV2Authorization {
public class GroupsV2Authorization {
private static final String TAG = Log.tag(GroupsV2Authorization.class);

View File

@@ -5,6 +5,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
@@ -33,7 +34,6 @@ 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.jobs.AvatarGroupsV2DownloadJob;
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
@@ -44,7 +44,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupHistoryEntry;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
@@ -61,7 +60,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
@@ -90,7 +88,6 @@ public final class GroupsV2StateProcessor {
public static final int RESTORE_PLACEHOLDER_REVISION = GroupStateMapper.RESTORE_PLACEHOLDER_REVISION;
private final Context context;
private final JobManager jobManager;
private final RecipientDatabase recipientDatabase;
private final GroupDatabase groupDatabase;
private final GroupsV2Authorization groupsV2Authorization;
@@ -98,7 +95,6 @@ public final class GroupsV2StateProcessor {
public GroupsV2StateProcessor(@NonNull Context context) {
this.context = context.getApplicationContext();
this.jobManager = ApplicationDependencies.getJobManager();
this.groupsV2Authorization = ApplicationDependencies.getGroupsV2Authorization();
this.groupsV2Api = ApplicationDependencies.getSignalServiceAccountManager().getGroupsV2Api();
this.recipientDatabase = SignalDatabase.recipients();
@@ -106,7 +102,10 @@ public final class GroupsV2StateProcessor {
}
public StateProcessorForGroup forGroup(@NonNull GroupMasterKey groupMasterKey) {
return new StateProcessorForGroup(groupMasterKey);
ACI selfAci = Recipient.self().requireAci();
ProfileAndMessageHelper profileAndMessageHelper = new ProfileAndMessageHelper(context, selfAci, groupMasterKey, GroupId.v2(groupMasterKey), recipientDatabase);
return new StateProcessorForGroup(selfAci, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, profileAndMessageHelper);
}
public enum GroupState {
@@ -127,8 +126,8 @@ public final class GroupsV2StateProcessor {
}
public static class GroupUpdateResult {
private final GroupState groupState;
@Nullable private final DecryptedGroup latestServer;
private final GroupState groupState;
private final DecryptedGroup latestServer;
GroupUpdateResult(@NonNull GroupState groupState, @Nullable DecryptedGroup latestServer) {
this.groupState = groupState;
@@ -144,15 +143,34 @@ public final class GroupsV2StateProcessor {
}
}
public final class StateProcessorForGroup {
private final GroupMasterKey masterKey;
private final GroupId.V2 groupId;
private final GroupSecretParams groupSecretParams;
public static final class StateProcessorForGroup {
private final ACI selfAci;
private final Context context;
private final GroupDatabase groupDatabase;
private final GroupsV2Api groupsV2Api;
private final GroupsV2Authorization groupsV2Authorization;
private final GroupMasterKey masterKey;
private final GroupId.V2 groupId;
private final GroupSecretParams groupSecretParams;
private final ProfileAndMessageHelper profileAndMessageHelper;
private StateProcessorForGroup(@NonNull GroupMasterKey groupMasterKey) {
this.masterKey = groupMasterKey;
this.groupId = GroupId.v2(masterKey);
this.groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
@VisibleForTesting StateProcessorForGroup(@NonNull ACI selfAci,
@NonNull Context context,
@NonNull GroupDatabase groupDatabase,
@NonNull GroupsV2Api groupsV2Api,
@NonNull GroupsV2Authorization groupsV2Authorization,
@NonNull GroupMasterKey groupMasterKey,
@NonNull ProfileAndMessageHelper profileAndMessageHelper)
{
this.selfAci = selfAci;
this.context = context;
this.groupDatabase = groupDatabase;
this.groupsV2Api = groupsV2Api;
this.groupsV2Authorization = groupsV2Authorization;
this.masterKey = groupMasterKey;
this.groupId = GroupId.v2(masterKey);
this.groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
this.profileAndMessageHelper = profileAndMessageHelper;
}
/**
@@ -175,8 +193,8 @@ public final class GroupsV2StateProcessor {
Optional<GroupRecord> localRecord = groupDatabase.getGroup(groupId);
DecryptedGroup localState = localRecord.transform(g -> g.requireV2GroupProperties().getDecryptedGroup()).orNull();
if (signedGroupChange != null &&
localState != null &&
if (signedGroupChange != null &&
localState != null &&
localState.getRevision() + 1 == signedGroupChange.getRevision() &&
revision == signedGroupChange.getRevision())
{
@@ -217,7 +235,7 @@ public final class GroupsV2StateProcessor {
}
if (inputGroupState == null) {
if (localState != null && DecryptedGroupUtil.isPendingOrRequesting(localState, Recipient.self().requireAci().uuid())) {
if (localState != null && DecryptedGroupUtil.isPendingOrRequesting(localState, selfAci.uuid())) {
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, but we think we are a pending or requesting member");
} else {
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message");
@@ -226,8 +244,6 @@ public final class GroupsV2StateProcessor {
throw e;
}
}
} else {
Log.i(TAG, "Saved server query for group change");
}
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision);
@@ -240,11 +256,11 @@ public final class GroupsV2StateProcessor {
updateLocalDatabaseGroupState(inputGroupState, newLocalState);
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
Log.i(TAG, "Inserting single update message for restore placeholder");
insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
} else {
insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
profileAndMessageHelper.insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
}
persistLearnedProfileKeys(inputGroupState);
profileAndMessageHelper.persistLearnedProfileKeys(inputGroupState);
GlobalGroupState remainingWork = advanceGroupStateResult.getNewGlobalGroupState();
if (remainingWork.getServerHistory().size() > 0) {
@@ -256,21 +272,23 @@ public final class GroupsV2StateProcessor {
}
private boolean notInGroupAndNotBeingAdded(@NonNull Optional<GroupRecord> localRecord, @NonNull DecryptedGroupChange signedGroupChange) {
boolean currentlyInGroup = localRecord.isPresent() && localRecord.get().isActive();
boolean addedAsMember = signedGroupChange.getNewMembersList()
.stream()
.map(DecryptedMember::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.collect(Collectors.toSet())
.contains(Recipient.self().requireAci().uuid());
boolean currentlyInGroup = localRecord.isPresent() && localRecord.get().isActive();
boolean addedAsMember = signedGroupChange.getNewMembersList()
.stream()
.map(DecryptedMember::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.collect(Collectors.toSet())
.contains(selfAci.uuid());
boolean addedAsPendingMember = signedGroupChange.getNewPendingMembersList()
.stream()
.map(DecryptedPendingMember::getUuid)
.map(UuidUtil::fromByteStringOrNull)
.filter(Objects::nonNull)
.collect(Collectors.toSet())
.contains(Recipient.self().requireAci().uuid());
.contains(selfAci.uuid());
return !currentlyInGroup && !addedAsMember && !addedAsPendingMember;
}
@@ -280,9 +298,9 @@ public final class GroupsV2StateProcessor {
*/
private GroupUpdateResult updateLocalGroupFromServerPaged(int revision, DecryptedGroup localState, long timestamp) throws IOException, GroupNotAMemberException {
boolean latestRevisionOnly = revision == LATEST && (localState == null || localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION);
ACI selfAci = Recipient.self().requireAci();
ACI selfAci = this.selfAci;
Log.i(TAG, "Paging from server revision: " + (revision == LATEST ? "latest" : revision) + " latest only: " + latestRevisionOnly);
Log.i(TAG, "Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly);
DecryptedGroup latestServerGroup;
GlobalGroupState inputGroupState;
@@ -295,15 +313,21 @@ public final class GroupsV2StateProcessor {
throw new IOException(e);
}
if (localState != null && localState.getRevision() >= latestServerGroup.getRevision()) {
Log.i(TAG, "Local state is at or later than server");
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, latestServerGroup);
}
if (latestRevisionOnly || !GroupProtoUtil.isMember(selfAci.uuid(), latestServerGroup.getMembersList())) {
Log.i(TAG, "Latest revision or not a member, use latest only");
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup, null)));
} else {
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, selfAci.uuid());
int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded;
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, selfAci.uuid());
int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded;
boolean includeFirstState = localState == null || localState.getRevision() < 0 || (revision == LATEST && localState.getRevision() + 1 < latestServerGroup.getRevision());
Log.i(TAG, "Requesting from server currentRevision: " + (localState != null ? localState.getRevision() : "null") + " logsNeededFrom: " + logsNeededFrom);
inputGroupState = getFullMemberHistoryPage(localState, selfAci, logsNeededFrom);
inputGroupState = getFullMemberHistoryPage(localState, selfAci, logsNeededFrom, includeFirstState);
}
ProfileKeySet profileKeys = new ProfileKeySet();
@@ -324,7 +348,7 @@ public final class GroupsV2StateProcessor {
updateLocalDatabaseGroupState(inputGroupState, newLocalState);
if (localState == null || localState.getRevision() != GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
timestamp = insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
timestamp = profileAndMessageHelper.insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
}
for (ServerGroupLogEntry entry : inputGroupState.getServerHistory()) {
@@ -342,16 +366,16 @@ public final class GroupsV2StateProcessor {
if (hasMore) {
Log.i(TAG, "Request next page from server revision: " + finalState.getRevision() + " nextPageRevision: " + inputGroupState.getNextPageRevision());
inputGroupState = getFullMemberHistoryPage(finalState, selfAci, inputGroupState.getNextPageRevision());
inputGroupState = getFullMemberHistoryPage(finalState, selfAci, inputGroupState.getNextPageRevision(), false);
}
}
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
Log.i(TAG, "Inserting single update message for restore placeholder");
insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(finalState, null)));
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(finalState, null)));
}
persistLearnedProfileKeys(profileKeys);
profileAndMessageHelper.persistLearnedProfileKeys(profileKeys);
if (finalGlobalGroupState.getServerHistory().size() > 0) {
Log.i(TAG, String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", finalState.getRevision() + 1, finalGlobalGroupState.getLatestRevisionNumber()));
@@ -366,7 +390,7 @@ public final class GroupsV2StateProcessor {
throws IOException, GroupNotAMemberException, GroupDoesNotExistException
{
try {
return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(Recipient.self().requireAci(), groupSecretParams));
return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams));
} catch (GroupNotFoundException e) {
throw new GroupDoesNotExistException(e);
} catch (NotInGroupException e) {
@@ -381,7 +405,7 @@ public final class GroupsV2StateProcessor {
throws IOException, GroupNotAMemberException, GroupDoesNotExistException
{
try {
return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(Recipient.self().requireAci(), groupSecretParams))
return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams), true)
.getResults()
.get(0)
.getGroup()
@@ -401,30 +425,33 @@ public final class GroupsV2StateProcessor {
return;
}
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
UUID selfUuid = Recipient.self().requireAci().uuid();
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
UUID selfUuid = selfAci.uuid();
DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId)
.requireV2GroupProperties()
.getDecryptedGroup();
DecryptedGroup simulatedGroupState = DecryptedGroupUtil.removeMember(decryptedGroup, selfUuid, decryptedGroup.getRevision() + 1);
DecryptedGroup simulatedGroupState = DecryptedGroupUtil.removeMember(decryptedGroup, selfUuid, decryptedGroup.getRevision() + 1);
DecryptedGroupChange simulatedGroupChange = DecryptedGroupChange.newBuilder()
.setEditor(UuidUtil.toByteString(UuidUtil.UNKNOWN_UUID))
.setRevision(simulatedGroupState.getRevision())
.addDeleteMembers(UuidUtil.toByteString(selfUuid))
.build();
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient,
decryptedGroupV2Context,
null,
System.currentTimeMillis(),
0,
false,
null,
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList());
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient,
decryptedGroupV2Context,
null,
System.currentTimeMillis(),
0,
false,
null,
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList());
try {
MessageDatabase mmsDatabase = SignalDatabase.mms();
@@ -466,108 +493,15 @@ public final class GroupsV2StateProcessor {
}
if (needsAvatarFetch) {
jobManager.add(new AvatarGroupsV2DownloadJob(groupId, newLocalState.getAvatar()));
ApplicationDependencies.getJobManager().add(new AvatarGroupsV2DownloadJob(groupId, newLocalState.getAvatar()));
}
determineProfileSharing(inputGroupState, newLocalState);
profileAndMessageHelper.determineProfileSharing(inputGroupState, newLocalState);
}
private void determineProfileSharing(@NonNull GlobalGroupState inputGroupState,
@NonNull DecryptedGroup newLocalState)
{
if (inputGroupState.getLocalState() != null) {
boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByUuid(inputGroupState.getLocalState().getMembersList(), Recipient.self().requireAci().uuid()).isPresent();
if (wasAMemberAlready) {
Log.i(TAG, "Skipping profile sharing detection as was already a full member before update");
return;
}
}
Optional<DecryptedMember> selfAsMemberOptional = DecryptedGroupUtil.findMemberByUuid(newLocalState.getMembersList(), Recipient.self().requireAci().uuid());
if (selfAsMemberOptional.isPresent()) {
DecryptedMember selfAsMember = selfAsMemberOptional.get();
int revisionJoinedAt = selfAsMember.getJoinedAtRevision();
Optional<Recipient> addedByOptional = Stream.of(inputGroupState.getServerHistory())
.map(ServerGroupLogEntry::getChange)
.filter(c -> c != null && c.getRevision() == revisionJoinedAt)
.findFirst()
.map(c -> Optional.fromNullable(UuidUtil.fromByteStringOrNull(c.getEditor()))
.transform(a -> Recipient.externalPush(context, ACI.fromByteStringOrNull(c.getEditor()), null, false)))
.orElse(Optional.absent());
if (addedByOptional.isPresent()) {
Recipient addedBy = addedByOptional.get();
Log.i(TAG, String.format("Added as a full member of %s by %s", groupId, addedBy.getId()));
if (addedBy.isSystemContact() || addedBy.isProfileSharing()) {
Log.i(TAG, "Group 'adder' is trusted. contact: " + addedBy.isSystemContact() + ", profileSharing: " + addedBy.isProfileSharing());
Log.i(TAG, "Added to a group and auto-enabling profile sharing");
recipientDatabase.setProfileSharing(Recipient.externalGroupExact(context, groupId).getId(), true);
} else {
Log.i(TAG, "Added to a group, but not enabling profile sharing, as 'adder' is not trusted");
}
} else {
Log.w(TAG, "Could not find founding member during gv2 create. Not enabling profile sharing.");
}
} else {
Log.i(TAG, String.format("Added to %s, but not enabling profile sharing as not a fullMember.", groupId));
}
}
private long insertUpdateMessages(long timestamp,
@Nullable DecryptedGroup previousGroupState,
Collection<LocalGroupLogEntry> processedLogEntries)
{
for (LocalGroupLogEntry entry : processedLogEntries) {
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(entry.getChange()) && !DecryptedGroupUtil.changeIsEmpty(entry.getChange())) {
Log.d(TAG, "Skipping profile key changes only update message");
} else {
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmpty(entry.getChange()) && previousGroupState != null) {
Log.w(TAG, "Empty group update message seen. Not inserting.");
} else {
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(previousGroupState, entry.getChange(), entry.getGroup()), null), timestamp);
timestamp++;
}
}
previousGroupState = entry.getGroup();
}
return timestamp;
}
private void persistLearnedProfileKeys(@NonNull GlobalGroupState globalGroupState) {
final ProfileKeySet profileKeys = new ProfileKeySet();
for (ServerGroupLogEntry entry : globalGroupState.getServerHistory()) {
if (entry.getGroup() != null) {
profileKeys.addKeysFromGroupState(entry.getGroup());
}
if (entry.getChange() != null) {
profileKeys.addKeysFromGroupChange(entry.getChange());
}
}
persistLearnedProfileKeys(profileKeys);
}
private void persistLearnedProfileKeys(@NonNull ProfileKeySet profileKeys) {
Set<RecipientId> updated = recipientDatabase.persistProfileKeySet(profileKeys);
if (!updated.isEmpty()) {
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);
}
}
}
private GlobalGroupState getFullMemberHistoryPage(DecryptedGroup localState, @NonNull ACI selfAci, int logsNeededFromRevision) throws IOException {
private GlobalGroupState getFullMemberHistoryPage(DecryptedGroup localState, @NonNull ACI selfAci, int logsNeededFromRevision, boolean includeFirstState) throws IOException {
try {
GroupHistoryPage groupHistoryPage = groupsV2Api.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams));
GroupHistoryPage groupHistoryPage = groupsV2Api.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(selfAci, groupSecretParams), includeFirstState);
ArrayList<ServerGroupLogEntry> history = new ArrayList<>(groupHistoryPage.getResults().size());
boolean ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges();
@@ -589,11 +523,119 @@ public final class GroupsV2StateProcessor {
throw new IOException(e);
}
}
}
private void storeMessage(@NonNull DecryptedGroupV2Context decryptedGroupV2Context, long timestamp) {
@VisibleForTesting
static class ProfileAndMessageHelper {
private final Context context;
private final ACI aci;
private final GroupMasterKey masterKey;
private final GroupId.V2 groupId;
private final RecipientDatabase recipientDatabase;
ProfileAndMessageHelper(@NonNull Context context, @NonNull ACI aci, @NonNull GroupMasterKey masterKey, @NonNull GroupId.V2 groupId, @NonNull RecipientDatabase recipientDatabase) {
this.context = context;
this.aci = aci;
this.masterKey = masterKey;
this.groupId = groupId;
this.recipientDatabase = recipientDatabase;
}
void determineProfileSharing(@NonNull GlobalGroupState inputGroupState, @NonNull DecryptedGroup newLocalState) {
if (inputGroupState.getLocalState() != null) {
boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByUuid(inputGroupState.getLocalState().getMembersList(), aci.uuid()).isPresent();
if (wasAMemberAlready) {
return;
}
}
Optional<DecryptedMember> selfAsMemberOptional = DecryptedGroupUtil.findMemberByUuid(newLocalState.getMembersList(), aci.uuid());
if (selfAsMemberOptional.isPresent()) {
DecryptedMember selfAsMember = selfAsMemberOptional.get();
int revisionJoinedAt = selfAsMember.getJoinedAtRevision();
Optional<Recipient> addedByOptional = Stream.of(inputGroupState.getServerHistory())
.map(ServerGroupLogEntry::getChange)
.filter(c -> c != null && c.getRevision() == revisionJoinedAt)
.findFirst()
.map(c -> Optional.fromNullable(UuidUtil.fromByteStringOrNull(c.getEditor()))
.transform(a -> Recipient.externalPush(context, ACI.fromByteStringOrNull(c.getEditor()), null, false)))
.orElse(Optional.absent());
if (addedByOptional.isPresent()) {
Recipient addedBy = addedByOptional.get();
Log.i(TAG, String.format("Added as a full member of %s by %s", groupId, addedBy.getId()));
if (addedBy.isSystemContact() || addedBy.isProfileSharing()) {
Log.i(TAG, "Group 'adder' is trusted. contact: " + addedBy.isSystemContact() + ", profileSharing: " + addedBy.isProfileSharing());
Log.i(TAG, "Added to a group and auto-enabling profile sharing");
recipientDatabase.setProfileSharing(Recipient.externalGroupExact(context, groupId).getId(), true);
} else {
Log.i(TAG, "Added to a group, but not enabling profile sharing, as 'adder' is not trusted");
}
} else {
Log.w(TAG, "Could not find founding member during gv2 create. Not enabling profile sharing.");
}
} else {
Log.i(TAG, String.format("Added to %s, but not enabling profile sharing as not a fullMember.", groupId));
}
}
long insertUpdateMessages(long timestamp,
@Nullable DecryptedGroup previousGroupState,
Collection<LocalGroupLogEntry> processedLogEntries)
{
for (LocalGroupLogEntry entry : processedLogEntries) {
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(entry.getChange()) && !DecryptedGroupUtil.changeIsEmpty(entry.getChange())) {
Log.d(TAG, "Skipping profile key changes only update message");
} else {
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmpty(entry.getChange()) && previousGroupState != null) {
Log.w(TAG, "Empty group update message seen. Not inserting.");
} else {
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(previousGroupState, entry.getChange(), entry.getGroup()), null), timestamp);
timestamp++;
}
}
previousGroupState = entry.getGroup();
}
return timestamp;
}
void persistLearnedProfileKeys(@NonNull GlobalGroupState globalGroupState) {
final ProfileKeySet profileKeys = new ProfileKeySet();
for (ServerGroupLogEntry entry : globalGroupState.getServerHistory()) {
if (entry.getGroup() != null) {
profileKeys.addKeysFromGroupState(entry.getGroup());
}
if (entry.getChange() != null) {
profileKeys.addKeysFromGroupChange(entry.getChange());
}
}
persistLearnedProfileKeys(profileKeys);
}
void persistLearnedProfileKeys(@NonNull ProfileKeySet profileKeys) {
Set<RecipientId> updated = recipientDatabase.persistProfileKeySet(profileKeys);
if (!updated.isEmpty()) {
Log.i(TAG, String.format(Locale.US, "Learned %d new profile keys, fetching profiles", updated.size()));
for (Job job : RetrieveProfileJob.forRecipients(updated)) {
ApplicationDependencies.getJobManager().runSynchronously(job, 5000);
}
}
}
void storeMessage(@NonNull DecryptedGroupV2Context decryptedGroupV2Context, long timestamp) {
Optional<ACI> editor = getEditor(decryptedGroupV2Context).transform(ACI::from);
boolean outgoing = !editor.isPresent() || Recipient.self().requireAci().equals(editor.get());
boolean outgoing = !editor.isPresent() || aci.equals(editor.get());
if (outgoing) {
try {
@@ -631,7 +673,7 @@ public final class GroupsV2StateProcessor {
if (changeEditor.isPresent()) {
return changeEditor;
} else {
Optional<DecryptedPendingMember> pendingByUuid = DecryptedGroupUtil.findPendingByUuid(decryptedGroupV2Context.getGroupState().getPendingMembersList(), Recipient.self().requireAci().uuid());
Optional<DecryptedPendingMember> pendingByUuid = DecryptedGroupUtil.findPendingByUuid(decryptedGroupV2Context.getGroupState().getPendingMembersList(), aci.uuid());
if (pendingByUuid.isPresent()) {
return Optional.fromNullable(UuidUtil.fromByteStringOrNull(pendingByUuid.get().getAddedByUuid()));
}