Job changes for GroupsV2 message receive and profile key updates.

master
Alan Evans 2020-05-08 10:46:16 -03:00 committed by Alex Hart
parent 36c43ed2fa
commit 9ac1897880
9 changed files with 261 additions and 45 deletions

View File

@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.groups.UuidCiphertext;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
@ -120,12 +121,13 @@ public final class GroupManager {
@WorkerThread
public static void updateGroupFromServer(@NonNull Context context,
@NonNull GroupId.V2 groupId,
int version)
@NonNull GroupMasterKey groupMasterKey,
int version,
long timestamp)
throws GroupChangeBusyException, IOException, GroupNotAMemberException
{
try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId)) {
edit.updateLocalToServerVersion(version);
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
updater.updateLocalToServerVersion(version, timestamp);
}
}

View File

@ -89,6 +89,11 @@ final class GroupManagerV2 {
return new GroupEditor(groupId, GroupsV2ProcessingLock.acquireGroupProcessingLock());
}
@WorkerThread
GroupUpdater updater(@NonNull GroupMasterKey groupId) throws GroupChangeBusyException {
return new GroupUpdater(groupId, GroupsV2ProcessingLock.acquireGroupProcessingLock());
}
class GroupCreator implements Closeable {
private final Closeable lock;
@ -316,14 +321,6 @@ final class GroupManagerV2 {
return commitChangeWithConflictResolution(groupOperations.createAcceptInviteChange(groupCandidate.getProfileKeyCredential().get()));
}
@WorkerThread
void updateLocalToServerVersion(int version)
throws IOException, GroupNotAMemberException
{
new GroupsV2StateProcessor(context).forGroup(groupMasterKey)
.updateLocalGroupToRevision(version, System.currentTimeMillis());
}
private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull GroupChange.Actions.Builder change)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
@ -421,6 +418,30 @@ final class GroupManagerV2 {
}
}
class GroupUpdater implements Closeable {
private final Closeable lock;
private final GroupMasterKey groupMasterKey;
GroupUpdater(@NonNull GroupMasterKey groupMasterKey, @NonNull Closeable lock) {
this.lock = lock;
this.groupMasterKey = groupMasterKey;
}
@WorkerThread
void updateLocalToServerVersion(int version, long timestamp)
throws IOException, GroupNotAMemberException
{
new GroupsV2StateProcessor(context).forGroup(groupMasterKey)
.updateLocalGroupToRevision(version, timestamp);
}
@Override
public void close() throws IOException {
lock.close();
}
}
private @NonNull GroupManager.GroupActionResult sendGroupUpdate(@NonNull GroupMasterKey masterKey,
@NonNull DecryptedGroup decryptedGroup,
@Nullable DecryptedGroupChange plainGroupChange)

View File

@ -0,0 +1,92 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* When your profile key changes, this job can be used to update it on a single given group.
* <p>
* Your membership is confirmed first, so safe to run against any known {@link GroupId.V2}
*/
public final class GroupV2UpdateSelfProfileKeyJob extends BaseJob {
public static final String KEY = "GroupV2UpdateSelfProfileKeyJob";
private static final String QUEUE = "GroupV2UpdateSelfProfileKeyJob";
@SuppressWarnings("unused")
private static final String TAG = Log.tag(GroupV2UpdateSelfProfileKeyJob.class);
private static final String KEY_GROUP_ID = "group_id";
private final GroupId.V2 groupId;
GroupV2UpdateSelfProfileKeyJob(@NonNull GroupId.V2 groupId) {
this(new Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.setQueue(QUEUE)
.build(),
groupId);
}
private GroupV2UpdateSelfProfileKeyJob(@NonNull Parameters parameters, @NonNull GroupId.V2 groupId) {
super(parameters);
this.groupId = groupId;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_GROUP_ID, groupId.toString())
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun()
throws IOException, GroupNotAMemberException, GroupChangeFailedException, GroupInsufficientRightsException, GroupChangeBusyException
{
Log.i(TAG, "Updating profile key on group " + groupId);
GroupManager.updateSelfProfileKeyInGroup(context, groupId);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
return e instanceof PushNetworkException ||
e instanceof NoCredentialForRedemptionTimeException||
e instanceof GroupChangeBusyException;
}
@Override
public void onFailure() {
}
public static final class Factory implements Job.Factory<GroupV2UpdateSelfProfileKeyJob> {
@Override
public @NonNull GroupV2UpdateSelfProfileKeyJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new GroupV2UpdateSelfProfileKeyJob(parameters,
GroupId.parseOrThrow(data.getString(KEY_GROUP_ID)).requireV2());
}
}
}

View File

@ -20,12 +20,12 @@ import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMi
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.SendReadReceiptsJobMigration;
import org.thoughtcrime.securesms.migrations.AvatarIdRemovalMigrationJob;
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
import org.thoughtcrime.securesms.migrations.AvatarMigrationJob;
import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob;
import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob;
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob;
@ -91,6 +91,7 @@ public final class JobManagerFactories {
put(ResumableUploadSpecJob.KEY, new ResumableUploadSpecJob.Factory());
put(StorageAccountRestoreJob.KEY, new StorageAccountRestoreJob.Factory());
put(RequestGroupV2InfoJob.KEY, new RequestGroupV2InfoJob.Factory());
put(GroupV2UpdateSelfProfileKeyJob.KEY, new GroupV2UpdateSelfProfileKeyJob.Factory());
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory());
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory());
put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory());

View File

@ -12,6 +12,8 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment;
@ -44,7 +46,10 @@ import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.BadGroupIdException;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
import org.thoughtcrime.securesms.groups.GroupV1MessageProcessor;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
@ -76,6 +81,7 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.IdentityUtil;
@ -84,12 +90,15 @@ import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.state.SessionStore;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
@ -110,6 +119,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage
import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.security.SecureRandom;
@ -180,6 +190,7 @@ public final class PushProcessMessageJob extends BaseJob {
this(new Parameters.Builder()
.setQueue(QUEUE)
.setMaxAttempts(Parameters.UNLIMITED)
// TODO [Alan] GV2 add network constraint and split queues.
.build(),
messageState,
serializedPlaintextContent,
@ -234,7 +245,7 @@ public final class PushProcessMessageJob extends BaseJob {
}
@Override
public void onRun() {
public void onRun() throws Exception {
Optional<Long> optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.absent();
if (messageState == MessageState.DECRYPTED_OK) {
@ -258,15 +269,19 @@ public final class PushProcessMessageJob extends BaseJob {
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
return false;
public boolean onShouldRetry(@NonNull Exception e) {
return e instanceof PushNetworkException ||
e instanceof NoCredentialForRedemptionTimeException ||
e instanceof GroupChangeBusyException;
}
@Override
public void onFailure() {
}
private void handleMessage(@Nullable SignalServiceContent content, @NonNull Optional<Long> smsMessageId) {
private void handleMessage(@Nullable SignalServiceContent content, @NonNull Optional<Long> smsMessageId)
throws VerificationFailedException, IOException, InvalidGroupStateException, GroupChangeBusyException
{
try {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
@ -282,14 +297,25 @@ public final class PushProcessMessageJob extends BaseJob {
boolean isGv2Message = groupId.isPresent() && groupId.get().isV2();
if (isGv2Message) {
Log.w(TAG, "Ignoring GV2 message.");
return;
GroupMasterKey groupMasterKey = message.getGroupContext().get().getGroupV2().get().getMasterKey();
if (!groupV2PreProcessMessage(content, groupMasterKey, message.getGroupContext().get().getGroupV2().get())) {
Log.i(TAG, "Ignoring GV2 message for group we are not currently in " + groupId);
return;
}
GroupId.V2 groupIdV2 = groupId.get().requireV2();
Recipient sender = Recipient.externalPush(context, content.getSender());
if (!groupDatabase.isCurrentMember(groupIdV2, sender.getId())) {
Log.i(TAG, "Ignoring GV2 message from member not in group " + groupId);
return;
}
}
if (isInvalidMessage(message)) handleInvalidMessage(content.getSender(), content.getSenderDevice(), groupId, content.getTimestamp(), smsMessageId);
else if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
else if (message.isGroupV1Update()) handleGroupV1Message(content, message, smsMessageId);
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId);
else if (message.isGroupV1Update()) handleGroupV1Message(content, message, smsMessageId, groupId.get().requireV1());
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId, groupId);
else if (message.getReaction().isPresent()) handleReaction(content, message);
else if (message.getRemoteDelete().isPresent()) handleRemoteDelete(content, message);
else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId);
@ -351,6 +377,26 @@ public final class PushProcessMessageJob extends BaseJob {
}
}
/**
* Attempts to update the group to the version mentioned in the message.
* If the local version is at least that it will not check the server.
*
* @return false iff self is not a current member of the group.
*/
private boolean groupV2PreProcessMessage(@NonNull SignalServiceContent content,
@NonNull GroupMasterKey groupMasterKey,
@NonNull SignalServiceGroupV2 groupV2)
throws IOException, GroupChangeBusyException
{
try {
GroupManager.updateGroupFromServer(context, groupMasterKey, groupV2.getRevision(), content.getTimestamp());
return true;
} catch (GroupNotAMemberException e) {
Log.w(TAG, "Ignoring message for a group we're not in");
return false;
}
}
private void handleExceptionMessage(@NonNull ExceptionMetadata e, @NonNull Optional<Long> smsMessageId) {
switch (messageState) {
@ -551,13 +597,14 @@ public final class PushProcessMessageJob extends BaseJob {
private void handleGroupV1Message(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId)
@NonNull Optional<Long> smsMessageId,
@NonNull GroupId.V1 groupId)
throws StorageFailedException, BadGroupIdException
{
GroupV1MessageProcessor.process(context, content, message, false);
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent());
handleExpirationUpdate(content, message, Optional.absent(), Optional.of(groupId));
}
if (smsMessageId.isPresent()) {
@ -583,32 +630,46 @@ public final class PushProcessMessageJob extends BaseJob {
private void handleExpirationUpdate(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId)
@NonNull Optional<Long> smsMessageId,
@NonNull Optional<GroupId> groupId)
throws StorageFailedException, BadGroupIdException
{
if (groupId.isPresent() && groupId.get().isV2()) {
Log.w(TAG, "Expiration update received for GV2. Ignoring.");
return;
}
int expiresInSeconds = message.getExpiresInSeconds();
Optional<SignalServiceGroupContext> groupContext = message.getGroupContext();
Recipient recipient = getMessageDestination(content, groupContext);
if (recipient.getExpireMessages() == expiresInSeconds) {
Log.i(TAG, "No change in message expiry for group. Ignoring.");
return;
}
try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient sender = Recipient.externalPush(context, content.getSender());
Recipient recipient = getMessageDestination(content, message);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender.getId(),
message.getTimestamp(),
content.getTimestamp(),
content.getServerTimestamp(),
-1,
message.getExpiresInSeconds() * 1000L,
true,
expiresInSeconds * 1000L,
true,
false,
content.isNeedsReceipt(),
Optional.absent(),
message.getGroupContext(),
groupContext,
Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.absent());
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient.getId(), message.getExpiresInSeconds());
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient.getId(), expiresInSeconds);
if (smsMessageId.isPresent()) {
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
@ -772,7 +833,7 @@ public final class PushProcessMessageJob extends BaseJob {
private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content,
@NonNull SentTranscriptMessage message)
throws StorageFailedException, BadGroupIdException
throws StorageFailedException, BadGroupIdException, VerificationFailedException, IOException, InvalidGroupStateException
{
try {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
@ -799,8 +860,7 @@ public final class PushProcessMessageJob extends BaseJob {
threadId = handleSynchronizeSentTextMessage(message);
}
if (message.getMessage().getGroupContext().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.idFromGroupContext(message.getMessage().getGroupContext().get()))) {
handleUnknownGroupMessage(content, message.getMessage().getGroupContext().get());
if (message.getMessage().getGroupContext().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.idFromGroupContext(message.getMessage().getGroupContext().get()))) { handleUnknownGroupMessage(content, message.getMessage().getGroupContext().get());
}
if (message.getMessage().getProfileKey().isPresent()) {
@ -1112,7 +1172,7 @@ public final class PushProcessMessageJob extends BaseJob {
Recipient recipient = getMessageDestination(content, message);
if (message.getExpiresInSeconds() != recipient.getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent());
handleExpirationUpdate(content, message, Optional.absent(), groupId);
}
Long threadId;
@ -1573,16 +1633,21 @@ public final class PushProcessMessageJob extends BaseJob {
private Recipient getSyncMessageDestination(@NonNull SentTranscriptMessage message)
throws BadGroupIdException
{
return getGroupRecipient(message.getMessage().getGroupContext())
.or(() -> Recipient.externalPush(context, message.getDestination().get()));
return getGroupRecipient(message.getMessage().getGroupContext()).or(() -> Recipient.externalPush(context, message.getDestination().get()));
}
private Recipient getMessageDestination(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message)
throws BadGroupIdException
{
return getGroupRecipient(message.getGroupContext())
.or(() -> Recipient.externalPush(context, content.getSender()));
return getGroupRecipient(message.getGroupContext()).or(() -> Recipient.externalPush(context, content.getSender()));
}
private Recipient getMessageDestination(@NonNull SignalServiceContent content,
@NonNull Optional<SignalServiceGroupContext> groupContext)
throws BadGroupIdException
{
return getGroupRecipient(groupContext).or(() -> Recipient.externalPush(context, content.getSender()));
}
private Optional<Recipient> getGroupRecipient(Optional<SignalServiceGroupContext> message)
@ -1631,11 +1696,18 @@ public final class PushProcessMessageJob extends BaseJob {
boolean isTextMessage = message.getBody().isPresent();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent();
boolean isExpireMessage = message.isExpirationUpdate();
boolean isContentMessage = !message.isGroupV1Update() && !isExpireMessage && (isTextMessage || isMediaMessage);
boolean isGv2Message = message.isGroupV2Message();
boolean isGv2Update = message.isGroupV2Update();
boolean isContentMessage = !message.isGroupV1Update() && !isGv2Update && !isExpireMessage && (isTextMessage || isMediaMessage);
boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
boolean isLeaveMessage = message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1Type() == SignalServiceGroup.Type.QUIT;
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage);
if (isGv2Message && !FeatureFlags.groupsV2()) {
Log.i(TAG, "Ignoring GV2 message by feature flag.");
return true;
}
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage && !isGv2Update);
} else {
return sender.isBlocked();
}

View File

@ -81,7 +81,7 @@ public final class RequestGroupV2InfoJob extends BaseJob {
return;
}
GroupManager.updateGroupFromServer(context, groupId, toRevision);
GroupManager.updateGroupFromServer(context, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis());
}
@Override

View File

@ -7,18 +7,18 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.util.StreamDetails;
import java.util.UUID;
import java.util.List;
public class RotateProfileKeyJob extends BaseJob {
@ -55,6 +55,7 @@ public class RotateProfileKeyJob extends BaseJob {
Recipient self = Recipient.self();
recipientDatabase.setProfileKey(self.getId(), profileKey);
try (StreamDetails avatarStream = AvatarHelper.getSelfProfileAvatarStream(context)) {
if (FeatureFlags.VERSIONED_PROFILES) {
accountManager.setVersionedProfile(self.getUuid().get(),
@ -68,6 +69,16 @@ public class RotateProfileKeyJob extends BaseJob {
}
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
updateProfileKeyOnAllV2Groups();
}
private void updateProfileKeyOnAllV2Groups() {
List<GroupId.V2> allGv2Groups = DatabaseFactory.getGroupDatabase(context).getAllGroupV2Ids();
for (GroupId.V2 groupId : allGv2Groups) {
ApplicationDependencies.getJobManager().add(new GroupV2UpdateSelfProfileKeyJob(groupId));
}
}
@Override

View File

@ -60,6 +60,7 @@ public final class FeatureFlags {
private static final String CALLING_PIP = "android.callingPip";
private static final String NEW_GROUP_UI = "android.newGroupUI";
private static final String REACT_WITH_ANY_EMOJI = "android.reactWithAnyEmoji";
private static final String GROUPS_V2 = "android.groupsv2";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@ -109,7 +110,8 @@ public final class FeatureFlags {
*/
private static final Set<String> STICKY = Sets.newHashSet(
PINS_FOR_ALL_LEGACY,
PINS_FOR_ALL
PINS_FOR_ALL,
GROUPS_V2
);
/**
@ -255,6 +257,11 @@ public final class FeatureFlags {
return getBoolean(REACT_WITH_ANY_EMOJI, false);
}
/** Groups v2 send and receive. */
public static boolean groupsV2() {
return org.whispersystems.signalservice.FeatureFlags.ZK_GROUPS && getBoolean(GROUPS_V2, false);
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

View File

@ -144,6 +144,16 @@ public class SignalServiceDataMessage {
group.get().getGroupV1().get().getType() != SignalServiceGroup.Type.DELIVER;
}
public boolean isGroupV2Message() {
return group.isPresent() &&
group.get().getGroupV2().isPresent();
}
public boolean isGroupV2Update() {
return isGroupV2Message() &&
!body.isPresent();
}
public int getExpiresInSeconds() {
return expiresInSeconds;
}