diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 9a37b7094d..1504f33c32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index c3e7268d1e..a14c392dcd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java new file mode 100644 index 0000000000..12bdebe08b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupV2UpdateSelfProfileKeyJob.java @@ -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. + *

+ * 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 { + + @Override + public @NonNull GroupV2UpdateSelfProfileKeyJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new GroupV2UpdateSelfProfileKeyJob(parameters, + GroupId.parseOrThrow(data.getString(KEY_GROUP_ID)).requireV2()); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 4f1cbdc45c..f8d235af7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -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()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java index 438fda5c07..3c02645124 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -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 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 smsMessageId) { + private void handleMessage(@Nullable SignalServiceContent content, @NonNull Optional 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 smsMessageId) { switch (messageState) { @@ -551,13 +597,14 @@ public final class PushProcessMessageJob extends BaseJob { private void handleGroupV1Message(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, - @NonNull Optional smsMessageId) + @NonNull Optional 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 smsMessageId) + @NonNull Optional smsMessageId, + @NonNull Optional 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 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 groupContext) + throws BadGroupIdException + { + return getGroupRecipient(groupContext).or(() -> Recipient.externalPush(context, content.getSender())); } private Optional getGroupRecipient(Optional 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(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupV2InfoJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupV2InfoJob.java index d61533d905..ac2a995b3f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupV2InfoJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupV2InfoJob.java @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java index 85f72f39ad..55e374ba96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java @@ -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 allGv2Groups = DatabaseFactory.getGroupDatabase(context).getAllGroupV2Ids(); + + for (GroupId.V2 groupId : allGv2Groups) { + ApplicationDependencies.getJobManager().add(new GroupV2UpdateSelfProfileKeyJob(groupId)); + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 88dde0aecb..de42e56a7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -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 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 getMemoryValues() { return new TreeMap<>(REMOTE_VALUES); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java index 16601935f5..794f80b169 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java @@ -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; }