diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt index 76fb0bcb2d..0ca921ede5 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.database import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse -import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -75,21 +74,6 @@ class GroupTableTest { assertEquals(2, groups.size) } - @Test - fun givenGroups_whenIQueryGroupsByMembership_thenIExpectBothGroups() { - insertPushGroup() - insertMmsGroup(members = listOf(harness.others[1])) - - val groups = groupTable.queryGroupsByMembership( - setOf(harness.self.id, harness.others[1]), - includeInactive = false, - excludeV1 = false, - excludeMms = false - ) - - assertEquals(2, groups.cursor?.count) - } - @Test fun givenGroups_whenIGetGroups_thenIExpectBothGroups() { insertPushGroup() @@ -181,68 +165,6 @@ class GroupTableTest { assertFalse(actual) } - @Test - fun givenAGroup_whenIUpdateMembers_thenIExpectUpdatedMembers() { - val v2Group = insertPushGroup() - groupTable.updateMembers(v2Group, listOf(harness.self.id, harness.others[1])) - val groupRecord = groupTable.getGroup(v2Group) - - assertEquals(setOf(harness.self.id, harness.others[1]), groupRecord.get().members.toSet()) - } - - @Test - fun givenAnMmsGroup_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() { - val members: List = listOf(harness.self.id, harness.others[0]) - val other = insertMmsGroup(members + listOf(harness.others[1])) - val mmsGroup = insertMmsGroup(members) - val actual = groupTable.getOrCreateMmsGroupForMembers(members.toSet()) - - assertNotEquals(other, actual) - assertEquals(mmsGroup, actual) - } - - @Test - fun givenMultipleMmsGroups_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() { - val group1Members: List = listOf(harness.self.id, harness.others[0], harness.others[1]) - val group2Members: List = listOf(harness.self.id, harness.others[0], harness.others[2]) - - val group1: GroupId = insertMmsGroup(group1Members) - val group2: GroupId = insertMmsGroup(group2Members) - - val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.toSet()) - val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.toSet()) - - assertEquals(group1, group1Result) - assertEquals(group2, group2Result) - assertNotEquals(group1Result, group2Result) - } - - @Test - fun givenMultipleMmsGroupsWithDifferentMemberOrders_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() { - val group1Members: List = listOf(harness.self.id, harness.others[0], harness.others[1], harness.others[2]).shuffled() - val group2Members: List = listOf(harness.self.id, harness.others[0], harness.others[2], harness.others[3]).shuffled() - - val group1: GroupId = insertMmsGroup(group1Members) - val group2: GroupId = insertMmsGroup(group2Members) - - val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.shuffled().toSet()) - val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.shuffled().toSet()) - - assertEquals(group1, group1Result) - assertEquals(group2, group2Result) - assertNotEquals(group1Result, group2Result) - } - - @Test - fun givenMmsGroupWithOneMember_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() { - val groupMembers: List = listOf(harness.self.id) - val group: GroupId = insertMmsGroup(groupMembers) - - val groupResult: GroupId = groupTable.getOrCreateMmsGroupForMembers(groupMembers.toSet()) - - assertEquals(group, groupResult) - } - @Test fun givenTwoGroupsWithoutMembers_whenIQueryThem_thenIExpectEach() { val g1 = insertPushGroup(listOf()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt index 5164e32712..e8c6dc09df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt @@ -60,13 +60,13 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemo import org.whispersystems.signalservice.api.push.DistributionId import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId.ACI -import org.whispersystems.signalservice.api.push.ServiceId.ACI.Companion.parseOrNull import org.whispersystems.signalservice.api.push.ServiceId.PNI import java.io.Closeable import java.security.SecureRandom import java.util.Optional import java.util.stream.Collectors import javax.annotation.CheckReturnValue +import kotlin.math.abs class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference { @@ -155,7 +155,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT .toList() //language=sql - private val JOINED_GROUP_SELECT = """ + private const val JOINED_GROUP_SELECT = """ SELECT DISTINCT $TABLE_NAME.*, ( @@ -178,8 +178,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT const val RECIPIENT_ID = "recipient_id" //language=sql - @JvmField - val CREATE_TABLE = """ + const val CREATE_TABLE = """ CREATE TABLE $TABLE_NAME ( $ID INTEGER PRIMARY KEY, $GROUP_ID TEXT NOT NULL REFERENCES ${GroupTable.TABLE_NAME} (${GroupTable.GROUP_ID}) ON DELETE CASCADE, @@ -221,7 +220,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT .filterNot { (old, new) -> new == null || old == new } if (oldToNew.isNotEmpty()) { - writableDatabase.withinTransaction { db -> + writableDatabase.withinTransaction { oldToNew.forEach { remapRecipient(it.first, it.second) } } } @@ -261,22 +260,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT .run() } - /** - * @return A gv1 group whose expected v2 ID matches the one provided. - */ - fun getGroupV1ByExpectedV2(gv2Id: GroupId.V2): Optional { - return getGroup(SqlUtil.Query("$TABLE_NAME.$EXPECTED_V2_ID = ?", buildArgs(gv2Id))) - } - - /** - * @return A gv1 group whose expected v2 ID matches the one provided or a gv2 group whose ID matches the one provided. - * - * If a gv1 group is present, it will be returned first. - */ - fun getGroupV1OrV2ByExpectedV2(gv2Id: GroupId.V2): Optional { - return getGroup(SqlUtil.Query("$TABLE_NAME.$EXPECTED_V2_ID = ? OR $TABLE_NAME.$GROUP_ID = ? ORDER BY $TABLE_NAME.$EXPECTED_V2_ID DESC", buildArgs(gv2Id, gv2Id))) - } - fun getGroupByDistributionId(distributionId: DistributionId): Optional { return getGroup(SqlUtil.Query("$TABLE_NAME.$DISTRIBUTION_ID = ?", buildArgs(distributionId))) } @@ -295,7 +278,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT * Removes the specified members from the list of 'unmigrated V1 members' -- the list of members * that were either dropped or had to be invited when migrating the group from V1->V2. */ - fun removeUnmigratedV1Members(id: GroupId.V2, toRemove: List) { + private fun removeUnmigratedV1Members(id: GroupId.V2, toRemove: List) { val group = getGroup(id) if (group.isAbsent()) { Log.w(TAG, "Couldn't find the group!", Throwable()) @@ -382,54 +365,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT return Reader(cursor) } - fun queryGroupsByMembership(recipientIds: Set, includeInactive: Boolean, excludeV1: Boolean, excludeMms: Boolean): Reader { - var recipientIds = recipientIds - if (recipientIds.isEmpty()) { - return Reader(null) - } - - if (recipientIds.size > 30) { - Log.w(TAG, "[queryGroupsByMembership] Large set of recipientIds (${recipientIds.size})! Using the first 30.") - recipientIds = recipientIds.take(30).toSet() - } - - val membershipQuery = SqlUtil.buildSingleCollectionQuery("${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID}", recipientIds) - - var query: String - val queryArgs: Array - - if (includeInactive) { - query = "${membershipQuery.where} AND ($TABLE_NAME.$ACTIVE = ? OR $TABLE_NAME.$RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME} WHERE ${ThreadTable.TABLE_NAME}.${ThreadTable.ACTIVE} = 1))" - queryArgs = membershipQuery.whereArgs + buildArgs(1) - } else { - query = "${membershipQuery.where} AND $TABLE_NAME.$ACTIVE = ?" - queryArgs = membershipQuery.whereArgs + buildArgs(1) - } - - if (excludeV1) { - query += " AND $EXPECTED_V2_ID IS NULL" - } - - if (excludeMms) { - query += " AND $MMS = 0" - } - - val selection = """ - SELECT DISTINCT - $TABLE_NAME.*, - ( - SELECT GROUP_CONCAT(${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID}) - FROM ${MembershipTable.TABLE_NAME} - WHERE ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} = $TABLE_NAME.$GROUP_ID - ) as $MEMBER_GROUP_CONCAT - FROM ${MembershipTable.TABLE_NAME} - INNER JOIN $TABLE_NAME ON ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} = $TABLE_NAME.$GROUP_ID - WHERE $query - """ - - return Reader(readableDatabase.query(selection, queryArgs)) - } - private fun queryGroupsByRecency(groupQuery: GroupQuery): Reader { val query = getGroupQueryWhereStatement(groupQuery.searchQuery, groupQuery.includeInactive, !groupQuery.includeV1, !groupQuery.includeMms) val sql = """ @@ -505,41 +440,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } } - fun getOrCreateMmsGroupForMembers(members: Set): GroupId.Mms { - val joinedTestMembers = members - .toList() - .map { it.toLong() } - .sorted() - .joinToString(separator = ",") - - //language=sql - val statement = """ - SELECT - $TABLE_NAME.$GROUP_ID as gid, - ( - SELECT GROUP_CONCAT(${MembershipTable.RECIPIENT_ID}, ',') - FROM ( - SELECT ${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID} - FROM ${MembershipTable.TABLE_NAME} - WHERE ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} = $TABLE_NAME.$GROUP_ID - ORDER BY ${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID} ASC - ) - ) as $MEMBER_GROUP_CONCAT - FROM $TABLE_NAME - WHERE $MEMBER_GROUP_CONCAT = ? - """ - - return readableDatabase.rawQuery(statement, buildArgs(joinedTestMembers)).use { cursor -> - if (cursor.moveToNext()) { - return GroupId.parseOrThrow(cursor.requireNonNullString("gid")).requireMms() - } else { - val groupId = GroupId.createMms(SecureRandom()) - create(groupId, null, members) - groupId - } - } - } - @WorkerThread fun getPushGroupNamesContainingMember(recipientId: RecipientId): List { return getPushGroupsContainingMember(recipientId) @@ -654,7 +554,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT .orElse(null) if (invitedByAci != null) { - val serviceId: ServiceId? = parseOrNull(invitedByAci) + val serviceId: ServiceId? = ACI.parseOrNull(invitedByAci) if (serviceId != null) { return Recipient.externalPush(serviceId) } @@ -678,7 +578,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null) } - @JvmOverloads @CheckReturnValue fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup): GroupId.V2? { val groupId = GroupId.v2(groupMasterKey) @@ -934,36 +833,13 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } } - fun updateTitle(groupId: GroupId.V1, title: String?) { - updateTitle(groupId as GroupId, title) - } - - fun updateTitle(groupId: GroupId.Mms, title: String?) { - updateTitle(groupId as GroupId, if (title.isNullOrEmpty()) null else title) - } - - private fun updateTitle(groupId: GroupId, title: String?) { - if (!groupId.isV1 && !groupId.isMms) { - throw AssertionError() - } - - writableDatabase - .update(TABLE_NAME) - .values(TITLE to title) - .where("$GROUP_ID = ?", groupId) - .run() - - val groupRecipient = recipients.getOrInsertFromGroupId(groupId) - Recipient.live(groupRecipient).refresh() - } - /** * Used to bust the Glide cache when an avatar changes. */ fun onAvatarUpdated(groupId: GroupId, hasAvatar: Boolean) { writableDatabase .update(TABLE_NAME) - .values(AVATAR_ID to if (hasAvatar) Math.abs(SecureRandom().nextLong()) else 0) + .values(AVATAR_ID to if (hasAvatar) abs(SecureRandom().nextLong()) else 0) .where("$GROUP_ID = ?", groupId) .run() @@ -971,21 +847,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT Recipient.live(groupRecipient).refresh() } - fun updateMembers(groupId: GroupId, members: List) { - writableDatabase.withinTransaction { database -> - database - .update(TABLE_NAME) - .values(ACTIVE to 1) - .where("$GROUP_ID = ?", groupId) - .run() - - performMembershipUpdate(database, groupId, members) - } - - val groupRecipient = recipients.getOrInsertFromGroupId(groupId) - Recipient.live(groupRecipient).refresh() - } - fun remove(groupId: GroupId, source: RecipientId) { writableDatabase .delete(MembershipTable.TABLE_NAME) @@ -1171,7 +1032,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } } - class V2GroupProperties(val groupMasterKey: GroupMasterKey, val groupRevision: Int, val decryptedGroupBytes: ByteArray) { + class V2GroupProperties(val groupMasterKey: GroupMasterKey, val groupRevision: Int, private val decryptedGroupBytes: ByteArray) { val decryptedGroup: DecryptedGroup by lazy { DecryptedGroup.ADAPTER.decode(decryptedGroupBytes) } @@ -1410,7 +1271,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } enum class MemberSet(val includeSelf: Boolean, val includePending: Boolean) { - FULL_MEMBERS_INCLUDING_SELF(true, false), FULL_MEMBERS_EXCLUDING_SELF(false, false), FULL_MEMBERS_AND_PENDING_INCLUDING_SELF(true, true), FULL_MEMBERS_AND_PENDING_EXCLUDING_SELF(false, true) + FULL_MEMBERS_INCLUDING_SELF(true, false), FULL_MEMBERS_EXCLUDING_SELF(false, false) } /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index 4f3e7c37d8..d80712700a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.database.PendingRetryReceiptCache; import org.thoughtcrime.securesms.groups.GroupsV2Authorization; import org.thoughtcrime.securesms.groups.GroupsV2AuthorizationMemoryValueCache; -import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.megaphone.MegaphoneRepository; @@ -101,7 +100,6 @@ public class ApplicationDependencies { private static volatile FrameRateTracker frameRateTracker; private static volatile MegaphoneRepository megaphoneRepository; private static volatile GroupsV2Authorization groupsV2Authorization; - private static volatile GroupsV2StateProcessor groupsV2StateProcessor; private static volatile GroupsV2Operations groupsV2Operations; private static volatile EarlyMessageCache earlyMessageCache; private static volatile TypingStatusRepository typingStatusRepository; @@ -198,18 +196,6 @@ public class ApplicationDependencies { return groupsV2Operations; } - public static @NonNull GroupsV2StateProcessor getGroupsV2StateProcessor() { - if (groupsV2StateProcessor == null) { - synchronized (LOCK) { - if (groupsV2StateProcessor == null) { - groupsV2StateProcessor = new GroupsV2StateProcessor(application); - } - } - } - - return groupsV2StateProcessor; - } - public static @NonNull SignalServiceMessageSender getSignalServiceMessageSender() { SignalServiceMessageSender local = messageSender; 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 09de6bca61..53d9c9927c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -12,7 +12,6 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.storageservice.protos.groups.GroupExternalCredential; -import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -20,7 +19,6 @@ import org.thoughtcrime.securesms.database.model.GroupRecord; import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl; import org.thoughtcrime.securesms.groups.v2.GroupLinkPassword; import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor; -import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; @@ -28,8 +26,6 @@ import org.whispersystems.signalservice.api.push.ServiceId; import java.io.IOException; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -41,101 +37,60 @@ public final class GroupManager { private static final String TAG = Log.tag(GroupManager.class); @WorkerThread - public static @NonNull GroupActionResult createGroup(@NonNull ServiceId authServiceId, - @NonNull Context context, - @NonNull Set members, + public static @NonNull GroupActionResult createGroup(@NonNull Context context, + @NonNull Set members, @Nullable byte[] avatar, @Nullable String name, - boolean mms, int disappearingMessagesTimer) throws GroupChangeBusyException, GroupChangeFailedException, IOException { - boolean shouldAttemptToCreateV2 = !mms; - Set memberIds = getMemberIds(members); - - if (shouldAttemptToCreateV2) { - try { - try (GroupManagerV2.GroupCreator groupCreator = new GroupManagerV2(context).create()) { - return groupCreator.createGroup(authServiceId, memberIds, name, avatar, disappearingMessagesTimer); - } - } catch (MembershipNotSuitableForV2Exception e) { - Log.w(TAG, "Attempted to make a GV2, but membership was not suitable, falling back to GV1", e); - - return GroupManagerV1.createGroup(context, memberIds, avatar, name, false); - } - } else { - return GroupManagerV1.createGroup(context, memberIds, avatar, name, mms); + try (GroupManagerV2.GroupCreator groupCreator = new GroupManagerV2(context).create()) { + return groupCreator.createGroup(members, name, avatar, disappearingMessagesTimer); + } catch (MembershipNotSuitableForV2Exception e) { + Log.w(TAG, "Attempted to make a GV2, but membership was not suitable", e); + throw new GroupChangeFailedException(e); } } @WorkerThread - public static GroupActionResult updateGroupDetails(@NonNull Context context, - @NonNull GroupId groupId, - @Nullable byte[] avatar, - boolean avatarChanged, - @NonNull String name, - boolean nameChanged, - @NonNull String description, - boolean descriptionChanged) + public static void updateGroupDetails(@NonNull Context context, + @NonNull GroupId groupId, + @Nullable byte[] avatar, + boolean avatarChanged, + @NonNull String name, + boolean nameChanged, + @NonNull String description, + boolean descriptionChanged) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException { - if (groupId.isV2()) { - try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) { - return edit.updateGroupTitleDescriptionAndAvatar(nameChanged ? name : null, - descriptionChanged ? description : null, - avatar, - avatarChanged); - } - } else if (groupId.isV1()) { - List members = SignalDatabase.groups() - .getGroupMembers(groupId, GroupTable.MemberSet.FULL_MEMBERS_EXCLUDING_SELF); - - Set recipientIds = getMemberIds(new HashSet<>(members)); - - return GroupManagerV1.updateGroup(context, groupId.requireV1(), recipientIds, avatar, name, 0); - } else { - return GroupManagerV1.updateGroup(context, groupId.requireMms(), avatar, name); - } - } - - @WorkerThread - public static void migrateGroupToServer(@NonNull Context context, - @NonNull GroupId.V1 groupIdV1, - @NonNull Collection members) - throws IOException, GroupChangeFailedException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException - { - new GroupManagerV2(context).migrateGroupOnToServer(groupIdV1, members); - } - - private static Set getMemberIds(Collection recipients) { - Set results = new HashSet<>(recipients.size()); - - for (Recipient recipient : recipients) { - results.add(recipient.getId()); + if (!groupId.isV2()) { + throw new GroupChangeFailedException("Not gv2"); } - return results; + try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) { + edit.updateGroupTitleDescriptionAndAvatar(nameChanged ? name : null, + descriptionChanged ? description : null, + avatar, + avatarChanged); + } } @WorkerThread public static void leaveGroup(@NonNull Context context, @NonNull GroupId.Push groupId, boolean sendToMembers) throws GroupChangeBusyException, GroupChangeFailedException, IOException { - if (groupId.isV2()) { - try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) { - edit.leaveGroup(sendToMembers); - Log.i(TAG, "Left group " + groupId); - } catch (GroupInsufficientRightsException e) { - Log.w(TAG, "Unexpected prevention from leaving " + groupId + " due to rights", e); - throw new GroupChangeFailedException(e); - } catch (GroupNotAMemberException e) { - Log.w(TAG, "Already left group " + groupId, e); - } - } else { - if (!GroupManagerV1.leaveGroup(context, groupId.requireV1())) { - Log.w(TAG, "GV1 group leave failed" + groupId); - throw new GroupChangeFailedException(); - } + if (!groupId.isV2()) { + throw new GroupChangeFailedException("Not gv2"); + } + + try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) { + edit.leaveGroup(sendToMembers); + Log.i(TAG, "Left group " + groupId); + } catch (GroupInsufficientRightsException e) { + Log.w(TAG, "Unexpected prevention from leaving " + groupId + " due to rights", e); + throw new GroupChangeFailedException(e); + } catch (GroupNotAMemberException e) { + Log.w(TAG, "Already left group " + groupId, e); } SignalDatabase.recipients().getByGroupId(groupId).ifPresent(id -> SignalDatabase.messages().deleteScheduledMessages(id)); @@ -145,13 +100,11 @@ public final class GroupManager { public static void leaveGroupFromBlockOrMessageRequest(@NonNull Context context, @NonNull GroupId.Push groupId) throws IOException, GroupChangeBusyException, GroupChangeFailedException { - if (groupId.isV2()) { - leaveGroup(context, groupId.requireV2(), true); - } else { - if (!GroupManagerV1.silentLeaveGroup(context, groupId.requireV1())) { - throw new GroupChangeFailedException(); - } + if (!groupId.isV2()) { + throw new GroupChangeFailedException("Not gv2"); } + + leaveGroup(context, groupId.requireV2(), true); } @WorkerThread @@ -181,15 +134,14 @@ public final class GroupManager { * processing deny messages. */ @WorkerThread - public static GroupsV2StateProcessor.GroupUpdateResult updateGroupFromServer(@NonNull Context context, - @NonNull GroupMasterKey groupMasterKey, - int revision, - long timestamp, - @Nullable byte[] signedGroupChange) + public static void updateGroupFromServer(@NonNull Context context, + @NonNull GroupMasterKey groupMasterKey, + int revision, + long timestamp) throws GroupChangeBusyException, IOException, GroupNotAMemberException { try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) { - return updater.updateLocalToServerRevision(revision, timestamp, null, signedGroupChange); + updater.updateLocalToServerRevision(revision, timestamp); } } @@ -220,36 +172,6 @@ public final class GroupManager { } } - @WorkerThread - public static V2GroupServerStatus v2GroupStatus(@NonNull Context context, - @NonNull ServiceId authServiceId, - @NonNull GroupMasterKey groupMasterKey) - throws IOException - { - try { - new GroupManagerV2(context).groupServerQuery(authServiceId, groupMasterKey); - return V2GroupServerStatus.FULL_OR_PENDING_MEMBER; - } catch (GroupNotAMemberException e) { - return V2GroupServerStatus.NOT_A_MEMBER; - } catch (GroupDoesNotExistException e) { - return V2GroupServerStatus.DOES_NOT_EXIST; - } - } - - /** - * Tries to gets the exact version of the group at the time you joined. - *

- * If it fails to get the exact version, it will give the latest. - */ - @WorkerThread - public static DecryptedGroup addedGroupVersion(@NonNull ServiceId authServiceId, - @NonNull Context context, - @NonNull GroupMasterKey groupMasterKey) - throws IOException, GroupDoesNotExistException, GroupNotAMemberException - { - return new GroupManagerV2(context).addedGroupVersion(authServiceId, groupMasterKey); - } - @WorkerThread public static void setMemberAdmin(@NonNull Context context, @NonNull GroupId.V2 groupId, @@ -290,12 +212,12 @@ public final class GroupManager { public static void updateGroupTimer(@NonNull Context context, @NonNull GroupId.Push groupId, int expirationTime) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException { - if (groupId.isV2()) { - try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { - editor.updateGroupTimer(expirationTime); - } - } else { - GroupManagerV1.updateGroupTimer(context, groupId.requireV1(), expirationTime); + if (!groupId.isV2()) { + throw new GroupChangeFailedException("Not gv2"); + } + + try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { + editor.updateGroupTimer(expirationTime); } } @@ -330,17 +252,6 @@ public final class GroupManager { } } - @WorkerThread - public static void unban(@NonNull Context context, - @NonNull GroupId.V2 groupId, - @NonNull RecipientId recipientId) - throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException - { - try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { - editor.unban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId())); - } - } - @WorkerThread public static void applyMembershipAdditionRightsChange(@NonNull Context context, @NonNull GroupId.V2 groupId, @@ -366,7 +277,7 @@ public final class GroupManager { @WorkerThread public static void applyAnnouncementGroupChange(@NonNull Context context, @NonNull GroupId.V2 groupId, - @NonNull boolean isAnnouncementGroup) + boolean isAnnouncementGroup) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException { try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { @@ -423,21 +334,14 @@ public final class GroupManager { @NonNull Collection newMembers) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException, MembershipNotSuitableForV2Exception { - if (groupId.isV2()) { - GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); + if (!groupId.isV2()) { + throw new GroupChangeFailedException("Not gv2"); + } - try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { - return editor.addMembers(newMembers, groupRecord.requireV2GroupProperties().getBannedMembers()); - } - } else { - GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); - List members = groupRecord.getMembers(); - byte[] avatar = groupRecord.hasAvatar() ? AvatarHelper.getAvatarBytes(context, groupRecord.getRecipientId()) : null; - Set recipientIds = new HashSet<>(members); - int originalSize = recipientIds.size(); + GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); - recipientIds.addAll(newMembers); - return GroupManagerV1.updateGroup(context, groupId, recipientIds, avatar, groupRecord.getTitle(), recipientIds.size() - originalSize); + try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { + return editor.addMembers(newMembers, groupRecord.requireV2GroupProperties().getBannedMembers()); } } @@ -478,10 +382,6 @@ public final class GroupManager { } } - public static void sendNoopUpdate(@NonNull Context context, @NonNull GroupMasterKey groupMasterKey, @NonNull DecryptedGroup currentState) { - new GroupManagerV2(context).sendNoopGroupUpdate(groupMasterKey, currentState); - } - @WorkerThread public static @NonNull GroupExternalCredential getGroupExternalCredential(@NonNull Context context, @NonNull GroupId.V2 groupId) @@ -534,13 +434,4 @@ public final class GroupManager { ENABLED, ENABLED_WITH_APPROVAL } - - public enum V2GroupServerStatus { - /** The group does not exist. The expected pre-migration state for V1 groups. */ - DOES_NOT_EXIST, - /** Group exists but self is not in the group. */ - NOT_A_MEMBER, - /** Self is a full or pending member of the group. */ - FULL_OR_PENDING_MEMBER - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java deleted file mode 100644 index 588a2ba679..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java +++ /dev/null @@ -1,229 +0,0 @@ -package org.thoughtcrime.securesms.groups; - -import android.content.Context; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.attachments.Attachment; -import org.thoughtcrime.securesms.attachments.UriAttachment; -import org.thoughtcrime.securesms.database.AttachmentTable; -import org.thoughtcrime.securesms.database.GroupTable; -import org.thoughtcrime.securesms.database.RecipientTable; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult; -import org.thoughtcrime.securesms.mms.MessageGroupContext; -import org.thoughtcrime.securesms.mms.OutgoingMessage; -import org.thoughtcrime.securesms.profiles.AvatarHelper; -import org.thoughtcrime.securesms.providers.BlobProvider; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.util.MediaUtil; -import org.whispersystems.signalservice.internal.push.GroupContext; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import okio.ByteString; - -final class GroupManagerV1 { - - private static final String TAG = Log.tag(GroupManagerV1.class); - - static @NonNull GroupActionResult createGroup(@NonNull Context context, - @NonNull Set memberIds, - @Nullable byte[] avatarBytes, - @Nullable String name, - boolean mms) - { - final GroupTable groupDatabase = SignalDatabase.groups(); - final SecureRandom secureRandom = new SecureRandom(); - final GroupId groupId = mms ? GroupId.createMms(secureRandom) : GroupId.createV1(secureRandom); - final RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId); - final Recipient groupRecipient = Recipient.resolved(groupRecipientId); - - memberIds.add(Recipient.self().getId()); - - if (groupId.isV1()) { - GroupId.V1 groupIdV1 = groupId.requireV1(); - - groupDatabase.create(groupIdV1, name, memberIds, null); - - try { - AvatarHelper.setAvatar(context, groupRecipientId, avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null); - } catch (IOException e) { - Log.w(TAG, "Failed to save avatar!", e); - } - groupDatabase.onAvatarUpdated(groupIdV1, avatarBytes != null); - SignalDatabase.recipients().setProfileSharing(groupRecipient.getId(), true); - return sendGroupUpdate(context, groupIdV1, memberIds, name, avatarBytes, memberIds.size() - 1); - } else { - groupDatabase.create(groupId.requireMms(), name, memberIds); - - try { - AvatarHelper.setAvatar(context, groupRecipientId, avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null); - } catch (IOException e) { - Log.w(TAG, "Failed to save avatar!", e); - } - groupDatabase.onAvatarUpdated(groupId, avatarBytes != null); - - long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(groupRecipient, ThreadTable.DistributionTypes.CONVERSATION); - return new GroupActionResult(groupRecipient, threadId, memberIds.size() - 1, Collections.emptyList()); - } - } - - static GroupActionResult updateGroup(@NonNull Context context, - @NonNull GroupId groupId, - @NonNull Set memberAddresses, - @Nullable byte[] avatarBytes, - @Nullable String name, - int newMemberCount) - { - final GroupTable groupDatabase = SignalDatabase.groups(); - final RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId); - - memberAddresses.add(Recipient.self().getId()); - groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses)); - - if (groupId.isPush()) { - GroupId.V1 groupIdV1 = groupId.requireV1(); - - groupDatabase.updateTitle(groupIdV1, name); - groupDatabase.onAvatarUpdated(groupIdV1, avatarBytes != null); - - try { - AvatarHelper.setAvatar(context, groupRecipientId, avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null); - } catch (IOException e) { - Log.w(TAG, "Failed to save avatar!", e); - } - return sendGroupUpdate(context, groupIdV1, memberAddresses, name, avatarBytes, newMemberCount); - } else { - Recipient groupRecipient = Recipient.resolved(groupRecipientId); - long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(groupRecipient); - return new GroupActionResult(groupRecipient, threadId, newMemberCount, Collections.emptyList()); - } - } - - static GroupActionResult updateGroup(@NonNull Context context, - @NonNull GroupId.Mms groupId, - @Nullable byte[] avatarBytes, - @Nullable String name) - { - GroupTable groupDatabase = SignalDatabase.groups(); - RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId); - Recipient groupRecipient = Recipient.resolved(groupRecipientId); - long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(groupRecipient); - - groupDatabase.updateTitle(groupId, name); - groupDatabase.onAvatarUpdated(groupId, avatarBytes != null); - - try { - AvatarHelper.setAvatar(context, groupRecipientId, avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null); - } catch (IOException e) { - Log.w(TAG, "Failed to save avatar!", e); - } - - return new GroupActionResult(groupRecipient, threadId, 0, Collections.emptyList()); - } - - private static GroupActionResult sendGroupUpdate(@NonNull Context context, - @NonNull GroupId.V1 groupId, - @NonNull Set members, - @Nullable String groupName, - @Nullable byte[] avatar, - int newMemberCount) - { - Attachment avatarAttachment = null; - RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId); - Recipient groupRecipient = Recipient.resolved(groupRecipientId); - - List uuidMembers = new ArrayList<>(members.size()); - List e164Members = new ArrayList<>(members.size()); - - for (RecipientId member : members) { - Recipient recipient = Recipient.resolved(member); - if (recipient.getHasE164()) { - e164Members.add(recipient.requireE164()); - } - } - - GroupContext.Builder groupContextBuilder = new GroupContext.Builder() - .id(ByteString.of(groupId.getDecodedId())) - .type(GroupContext.Type.UPDATE) - .membersE164(e164Members) - .members(uuidMembers); - - if (groupName != null) groupContextBuilder.name(groupName); - - GroupContext groupContext = groupContextBuilder.build(); - - if (avatar != null) { - Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory(); - avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentTable.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, false, false, null, null, null, null, null); - } - - OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, - new MessageGroupContext(groupContext), - avatarAttachment != null ? Collections.singletonList(avatarAttachment) : Collections.emptyList(), - System.currentTimeMillis(), - 0, - false, - null, - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList()); - - long threadId = MessageSender.send(context, outgoingMessage, -1, MessageSender.SendType.SIGNAL, null, null); - - return new GroupActionResult(groupRecipient, threadId, newMemberCount, Collections.emptyList()); - } - - @WorkerThread - static boolean leaveGroup(@NonNull Context context, @NonNull GroupId.V1 groupId) { - return false; - } - - @WorkerThread - static boolean silentLeaveGroup(@NonNull Context context, @NonNull GroupId.V1 groupId) { - return false; - } - - @WorkerThread - static void updateGroupTimer(@NonNull Context context, @NonNull GroupId.V1 groupId, int expirationTime) { - RecipientTable recipientTable = SignalDatabase.recipients(); - ThreadTable threadTable = SignalDatabase.threads(); - Recipient recipient = Recipient.externalGroupExact(groupId); - long threadId = threadTable.getOrCreateThreadIdFor(recipient); - - recipientTable.setExpireMessages(recipient.getId(), expirationTime); - OutgoingMessage outgoingMessage = OutgoingMessage.expirationUpdateMessage(recipient, System.currentTimeMillis(), expirationTime * 1000L); - MessageSender.send(context, outgoingMessage, threadId, MessageSender.SendType.SIGNAL, null, null); - } - - @WorkerThread - private static Optional createGroupLeaveMessage(@NonNull Context context, - @NonNull GroupId.V1 groupId, - @NonNull Recipient groupRecipient) - { - GroupTable groupDatabase = SignalDatabase.groups(); - - if (!groupDatabase.isActive(groupId)) { - Log.w(TAG, "Group has already been left."); - return Optional.empty(); - } - - return Optional.empty(); - } -} 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 34972895b8..acd65337e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -7,9 +7,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; -import com.annimon.stream.Collectors; -import com.annimon.stream.Stream; - import org.signal.core.util.logging.Log; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; @@ -34,7 +31,6 @@ import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.model.GroupRecord; -import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context; import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper; @@ -52,7 +48,6 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.ProfileUtil; -import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import org.whispersystems.signalservice.api.groupsv2.GroupCandidate; import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct; @@ -88,6 +83,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import okio.ByteString; @@ -95,12 +91,11 @@ final class GroupManagerV2 { private static final String TAG = Log.tag(GroupManagerV2.class); - private final Context context; - private final GroupTable groupDatabase; - private final GroupsV2Api groupsV2Api; + private final Context context; + private final GroupTable groupDatabase; + private final GroupsV2Api groupsV2Api; private final GroupsV2Operations groupsV2Operations; private final GroupsV2Authorization authorization; - private final GroupsV2StateProcessor groupsV2StateProcessor; private final ServiceIds serviceIds; private final ACI selfAci; private final PNI selfPni; @@ -113,7 +108,6 @@ final class GroupManagerV2 { ApplicationDependencies.getSignalServiceAccountManager().getGroupsV2Api(), ApplicationDependencies.getGroupsV2Operations(), ApplicationDependencies.getGroupsV2Authorization(), - ApplicationDependencies.getGroupsV2StateProcessor(), SignalStore.account().getServiceIds(), new GroupCandidateHelper(), new SendGroupUpdateHelper(context)); @@ -124,7 +118,6 @@ final class GroupManagerV2 { GroupsV2Api groupsV2Api, GroupsV2Operations groupsV2Operations, GroupsV2Authorization authorization, - GroupsV2StateProcessor groupsV2StateProcessor, ServiceIds serviceIds, GroupCandidateHelper groupCandidateHelper, SendGroupUpdateHelper sendGroupUpdateHelper) @@ -134,7 +127,6 @@ final class GroupManagerV2 { this.groupsV2Api = groupsV2Api; this.groupsV2Operations = groupsV2Operations; this.authorization = authorization; - this.groupsV2StateProcessor = groupsV2StateProcessor; this.serviceIds = serviceIds; this.selfAci = serviceIds.getAci(); this.selfPni = serviceIds.requirePni(); @@ -212,65 +204,6 @@ final class GroupManagerV2 { return new GroupUpdater(groupId, GroupsV2ProcessingLock.acquireGroupProcessingLock()); } - @WorkerThread - void groupServerQuery(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey) - throws GroupNotAMemberException, IOException, GroupDoesNotExistException - { - new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey) - .getCurrentGroupStateFromServer(); - } - - @WorkerThread - @NonNull DecryptedGroup addedGroupVersion(@NonNull ServiceId authServiceId, @NonNull GroupMasterKey groupMasterKey) - throws GroupNotAMemberException, IOException, GroupDoesNotExistException - { - GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey); - DecryptedGroup latest = stateProcessorForGroup.getCurrentGroupStateFromServer(); - - if (latest.revision == 0) { - return latest; - } - - Optional selfInFullMemberList = DecryptedGroupUtil.findMemberByAci(latest.members, selfAci); - - if (!selfInFullMemberList.isPresent()) { - return latest; - } - - DecryptedGroup joinedVersion = stateProcessorForGroup.getSpecificVersionFromServer(selfInFullMemberList.get().joinedAtRevision); - - if (joinedVersion != null) { - return joinedVersion; - } else { - Log.w(TAG, "Unable to retrieve exact version joined at, using latest"); - return latest; - } - } - - @WorkerThread - void migrateGroupOnToServer(@NonNull GroupId.V1 groupIdV1, @NonNull Collection members) - throws IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException, GroupChangeFailedException - { - GroupMasterKey groupMasterKey = groupIdV1.deriveV2MigrationMasterKey(); - GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - GroupRecord groupRecord = groupDatabase.requireGroup(groupIdV1); - String name = Util.emptyIfNull(groupRecord.getTitle()); - byte[] avatar = groupRecord.hasAvatar() ? AvatarHelper.getAvatarBytes(context, groupRecord.getRecipientId()) : null; - int messageTimer = Recipient.resolved(groupRecord.getRecipientId()).getExpiresInSeconds(); - Set memberIds = Stream.of(members) - .map(Recipient::getId) - .filterNot(m -> m.equals(Recipient.self().getId())) - .collect(Collectors.toSet()); - - createGroupOnServer(groupSecretParams, name, avatar, memberIds, Member.Role.ADMINISTRATOR, messageTimer); - } - - @WorkerThread - void sendNoopGroupUpdate(@NonNull GroupMasterKey masterKey, @NonNull DecryptedGroup currentState) { - sendGroupUpdateHelper.sendGroupUpdate(masterKey, new GroupMutation(currentState, new DecryptedGroupChange(), currentState), null); - } - - final class GroupCreator extends LockOwner { GroupCreator(@NonNull Closeable lock) { @@ -278,8 +211,7 @@ final class GroupManagerV2 { } @WorkerThread - @NonNull GroupManager.GroupActionResult createGroup(@NonNull ServiceId authServiceId, - @NonNull Collection members, + @NonNull GroupManager.GroupActionResult createGroup(@NonNull Collection members, @Nullable String name, @Nullable byte[] avatar, int disappearingMessagesTimer) @@ -289,7 +221,7 @@ final class GroupManagerV2 { DecryptedGroup decryptedGroup; try { - decryptedGroup = createGroupOnServer(groupSecretParams, name, avatar, members, Member.Role.DEFAULT, disappearingMessagesTimer); + decryptedGroup = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer); } catch (GroupAlreadyExistsException e) { throw new GroupChangeFailedException(e); } @@ -430,9 +362,9 @@ final class GroupManagerV2 { @NonNull GroupManager.GroupActionResult approveRequests(@NonNull Collection recipientIds) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { - Set uuids = Stream.of(recipientIds) - .map(r -> Recipient.resolved(r).requireServiceId().getRawUuid()) - .collect(Collectors.toSet()); + Set uuids = recipientIds.stream() + .map(r -> Recipient.resolved(r).requireServiceId().getRawUuid()) + .collect(Collectors.toSet()); return commitChangeWithConflictResolution(selfAci, groupOperations.createApproveGroupJoinRequest(uuids)); } @@ -441,9 +373,9 @@ final class GroupManagerV2 { @NonNull GroupManager.GroupActionResult denyRequests(@NonNull Collection recipientIds) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { - Set uuids = Stream.of(recipientIds) - .map(r -> Recipient.resolved(r).requireAci()) - .collect(Collectors.toSet()); + Set uuids = recipientIds.stream() + .map(r -> Recipient.resolved(r).requireAci()) + .collect(Collectors.toSet()); return commitChangeWithConflictResolution(selfAci, groupOperations.createRefuseGroupJoinRequest(uuids, true, v2GroupProperties.getDecryptedGroup().bannedMembers)); } @@ -471,7 +403,7 @@ final class GroupManagerV2 { if (aciPendingMember.isPresent()) { selfPendingMember = aciPendingMember; - } else if (pniPendingMember.isPresent() && !selfMember.isPresent()) { + } else if (pniPendingMember.isPresent() && selfMember.isEmpty()) { selfPendingMember = pniPendingMember; serviceId = selfPni; } @@ -506,7 +438,7 @@ final class GroupManagerV2 { @NonNull GroupManager.GroupActionResult addMemberAdminsAndLeaveGroup(Collection newAdmins) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException { - List newAdminRecipients = Stream.of(newAdmins).map(id -> Recipient.resolved(id).requireServiceId().getRawUuid()).toList(); + List newAdminRecipients = newAdmins.stream().map(id -> Recipient.resolved(id).requireServiceId().getRawUuid()).collect(Collectors.toList()); return commitChangeWithConflictResolution(selfAci, groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci, newAdminRecipients)); @@ -520,7 +452,7 @@ final class GroupManagerV2 { DecryptedGroup group = groupDatabase.requireGroup(groupId).requireV2GroupProperties().getDecryptedGroup(); Optional selfInGroup = DecryptedGroupUtil.findMemberByAci(group.members, selfAci); - if (!selfInGroup.isPresent()) { + if (selfInGroup.isEmpty()) { Log.w(TAG, "Self not in group " + groupId); return null; } @@ -584,12 +516,6 @@ final class GroupManagerV2 { return commitChangeWithConflictResolution(selfAci, groupOperations.createBanServiceIdsChange(Collections.singleton(serviceId), rejectJoinRequest, v2GroupProperties.getDecryptedGroup().bannedMembers)); } - public GroupManager.GroupActionResult unban(Set serviceIds) - throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException - { - return commitChangeWithConflictResolution(selfAci, groupOperations.createUnbanServiceIdsChange(serviceIds)); - } - @WorkerThread public GroupManager.GroupActionResult cycleGroupLinkPassword() throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException @@ -637,13 +563,7 @@ final class GroupManagerV2 { private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException { - return commitChangeWithConflictResolution(authServiceId, change, false); - } - - private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked) - throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException - { - return commitChangeWithConflictResolution(authServiceId, change, allowWhenBlocked, true); + return commitChangeWithConflictResolution(authServiceId, change, false, true); } private @NonNull GroupManager.GroupActionResult commitChangeWithConflictResolution(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers) @@ -683,7 +603,7 @@ final class GroupManagerV2 { private GroupChange.Actions.Builder resolveConflict(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change) throws IOException, GroupNotAMemberException, GroupChangeFailedException { - GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(serviceIds, groupMasterKey) + GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey) .updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), null); if (groupUpdateResult.getLatestServer() == null) { @@ -716,7 +636,7 @@ final class GroupManagerV2 { List ids = groupOperations.decryptAddMembers(change.addMembers) .stream() .map(RecipientId::from) - .collect(java.util.stream.Collectors.toList()); + .collect(Collectors.toList()); for (RecipientId id : ids) { ProfileUtil.updateExpiringProfileKeyCredential(Recipient.resolved(id)); @@ -798,11 +718,11 @@ final class GroupManagerV2 { } @WorkerThread - GroupsV2StateProcessor.GroupUpdateResult updateLocalToServerRevision(int revision, long timestamp, @Nullable GroupSecretParams groupSecretParams, @Nullable byte[] signedGroupChange) + void updateLocalToServerRevision(int revision, long timestamp) throws IOException, GroupNotAMemberException { - return new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey, groupSecretParams) - .updateLocalGroupToRevision(revision, timestamp, getDecryptedGroupChange(signedGroupChange)); + GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey) + .updateLocalGroupToRevision(revision, timestamp, null); } @WorkerThread @@ -814,16 +734,16 @@ final class GroupManagerV2 { @Nullable String serverGuid) throws IOException, GroupNotAMemberException { - return new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey, groupSecretParams) - .updateLocalGroupToRevision(revision, timestamp, localRecord, getDecryptedGroupChange(signedGroupChange), serverGuid); + return GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey, groupSecretParams) + .updateLocalGroupToRevision(revision, timestamp, localRecord, getDecryptedGroupChange(signedGroupChange), serverGuid); } @WorkerThread void forceSanityUpdateFromServer(long timestamp) throws IOException, GroupNotAMemberException { - new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey) - .forceSanityUpdateFromServer(timestamp); + GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey) + .forceSanityUpdateFromServer(timestamp); } private DecryptedGroupChange getDecryptedGroupChange(@Nullable byte[] signedGroupChange) { @@ -847,7 +767,6 @@ final class GroupManagerV2 { @Nullable String name, @Nullable byte[] avatar, @NonNull Collection members, - @NonNull Member.Role memberRole, int disappearingMessageTimerSeconds) throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException { @@ -873,7 +792,7 @@ final class GroupManagerV2 { Optional.ofNullable(avatar), self, candidates, - memberRole, + Member.Role.DEFAULT, disappearingMessageTimerSeconds); try { @@ -951,7 +870,7 @@ final class GroupManagerV2 { Log.i(TAG, "Group already present locally"); if (decryptedChange != null) { try { - groupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey) + GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey) .updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange); } catch (GroupNotAMemberException e) { Log.w(TAG, "Unable to apply join change to existing group", e); @@ -965,7 +884,7 @@ final class GroupManagerV2 { Log.i(TAG, "Create placeholder failed, group suddenly present locally, attempting to apply change"); if (decryptedChange != null) { try { - groupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey) + GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey) .updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange); } catch (GroupNotAMemberException e) { Log.w(TAG, "Unable to apply join change to existing group", e); @@ -1014,10 +933,10 @@ final class GroupManagerV2 { throws GroupChangeFailedException, IOException { try { - new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey) - .updateLocalGroupToRevision(decryptedChange.revision, - System.currentTimeMillis(), - decryptedChange); + GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey) + .updateLocalGroupToRevision(decryptedChange.revision, + System.currentTimeMillis(), + decryptedChange); RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, new GroupMutation(null, decryptedChange, decryptedGroup), signedGroupChange); @@ -1331,9 +1250,10 @@ final class GroupManagerV2 { } private static @NonNull List getPendingMemberRecipientIds(@NonNull List newPendingMembersList) { - return Stream.of(DecryptedGroupUtil.pendingToServiceIdList(newPendingMembersList)) - .map(serviceId -> RecipientId.from(serviceId)) - .toList(); + return DecryptedGroupUtil.pendingToServiceIdList(newPendingMembersList) + .stream() + .map(RecipientId::from) + .collect(Collectors.toList()); } private static @NonNull AccessControl.AccessRequired rightsToAccessControl(@NonNull GroupAccessControl rights) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsRepository.java index a2a78d169b..d6d43d952a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsRepository.java @@ -6,10 +6,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Consumer; -import com.annimon.stream.Stream; - import org.signal.core.util.concurrent.SignalExecutors; -import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupManager; @@ -21,14 +18,11 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; final class AddGroupDetailsRepository { - private static final String TAG = Log.tag(AddGroupDetailsRepository.class); - private final Context context; AddGroupDetailsRepository(@NonNull Context context) { @@ -50,20 +44,15 @@ final class AddGroupDetailsRepository { void createGroup(@NonNull Set members, @Nullable byte[] avatar, @Nullable String name, - boolean mms, @Nullable Integer disappearingMessagesTimer, Consumer resultConsumer) { SignalExecutors.BOUNDED.execute(() -> { - Set recipients = new HashSet<>(Stream.of(members).map(Recipient::resolved).toList()); - try { - GroupManager.GroupActionResult result = GroupManager.createGroup(SignalStore.account().requireAci(), - context, - recipients, + GroupManager.GroupActionResult result = GroupManager.createGroup(context, + members, avatar, name, - mms, disappearingMessagesTimer != null ? disappearingMessagesTimer : SignalStore.settings().getUniversalExpireTimer()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsViewModel.java index 5f4120a476..1a6e941ceb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsViewModel.java @@ -104,11 +104,10 @@ public final class AddGroupDetailsViewModel extends ViewModel { List members = Objects.requireNonNull(this.members.getValue()); Set memberIds = Stream.of(members).map(member -> member.getMember().getId()).collect(Collectors.toSet()); byte[] avatarBytes = avatar.getValue(); - boolean isGroupMms = isMms.getValue() == Boolean.TRUE; String groupName = name.getValue(); Integer disappearingTimer = disappearingMessagesTimer.getValue(); - if (!isGroupMms && TextUtils.isEmpty(groupName)) { + if (TextUtils.isEmpty(groupName)) { groupCreateResult.postValue(GroupCreateResult.error(GroupCreateResult.Error.Type.ERROR_INVALID_NAME)); return; } @@ -116,7 +115,6 @@ public final class AddGroupDetailsViewModel extends ViewModel { repository.createGroup(memberIds, avatarBytes, groupName, - isGroupMms, disappearingTimer, groupCreateResult::postValue); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index 01d25d1ffe..b7e87faa6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.groups.v2.processing; -import android.content.Context; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -8,8 +7,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; -import com.annimon.stream.Stream; - import org.signal.core.util.logging.Log; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.groups.GroupMasterKey; @@ -18,7 +15,6 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; -import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate; import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.RecipientTable; @@ -29,7 +25,6 @@ import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter; import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context; import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.groups.GroupDoesNotExistException; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupMutation; import org.thoughtcrime.securesms.groups.GroupNotAMemberException; @@ -93,30 +88,25 @@ public class GroupsV2StateProcessor { */ public static final int RESTORE_PLACEHOLDER_REVISION = GroupStateMapper.RESTORE_PLACEHOLDER_REVISION; - private final Context context; - private final RecipientTable recipientTable; - private final GroupTable groupDatabase; - private final GroupsV2Authorization groupsV2Authorization; - private final GroupsV2Api groupsV2Api; - - public GroupsV2StateProcessor(@NonNull Context context) { - this.context = context.getApplicationContext(); - this.groupsV2Authorization = ApplicationDependencies.getGroupsV2Authorization(); - this.groupsV2Api = ApplicationDependencies.getSignalServiceAccountManager().getGroupsV2Api(); - this.recipientTable = SignalDatabase.recipients(); - this.groupDatabase = SignalDatabase.groups(); + private GroupsV2StateProcessor() { } - public StateProcessorForGroup forGroup(@NonNull ServiceIds serviceIds, @NonNull GroupMasterKey groupMasterKey) { + public static StateProcessorForGroup forGroup(@NonNull ServiceIds serviceIds, @NonNull GroupMasterKey groupMasterKey) { return forGroup(serviceIds, groupMasterKey, null); } - public StateProcessorForGroup forGroup(@NonNull ServiceIds serviceIds, @NonNull GroupMasterKey groupMasterKey, @Nullable GroupSecretParams groupSecretParams) { - if (groupSecretParams == null) { - return new StateProcessorForGroup(serviceIds, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, recipientTable); - } else { - return new StateProcessorForGroup(serviceIds, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, groupSecretParams, recipientTable); - } + public static StateProcessorForGroup forGroup(@NonNull ServiceIds serviceIds, @NonNull GroupMasterKey groupMasterKey, @Nullable GroupSecretParams groupSecretParams) { + groupSecretParams = groupSecretParams != null ? groupSecretParams : GroupSecretParams.deriveFromMasterKey(groupMasterKey); + + return new StateProcessorForGroup( + serviceIds, + SignalDatabase.groups(), + ApplicationDependencies.getSignalServiceAccountManager().getGroupsV2Api(), + ApplicationDependencies.getGroupsV2Authorization(), + groupMasterKey, + groupSecretParams, + SignalDatabase.recipients() + ); } public enum GroupState { @@ -160,18 +150,6 @@ public class GroupsV2StateProcessor { private final ProfileAndMessageHelper profileAndMessageHelper; private StateProcessorForGroup(@NonNull ServiceIds serviceIds, - @NonNull Context context, - @NonNull GroupTable groupDatabase, - @NonNull GroupsV2Api groupsV2Api, - @NonNull GroupsV2Authorization groupsV2Authorization, - @NonNull GroupMasterKey groupMasterKey, - @NonNull RecipientTable recipientTable) - { - this(serviceIds, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, GroupSecretParams.deriveFromMasterKey(groupMasterKey), recipientTable); - } - - private StateProcessorForGroup(@NonNull ServiceIds serviceIds, - @NonNull Context context, @NonNull GroupTable groupDatabase, @NonNull GroupsV2Api groupsV2Api, @NonNull GroupsV2Authorization groupsV2Authorization, @@ -186,7 +164,7 @@ public class GroupsV2StateProcessor { this.masterKey = groupMasterKey; this.groupSecretParams = groupSecretParams; this.groupId = GroupId.v2(groupSecretParams.getPublicParams().getGroupIdentifier()); - this.profileAndMessageHelper = new ProfileAndMessageHelper(context, serviceIds.getAci(), groupMasterKey, groupId, recipientTable); + this.profileAndMessageHelper = new ProfileAndMessageHelper(serviceIds.getAci(), groupMasterKey, groupId, recipientTable); } @VisibleForTesting StateProcessorForGroup(@NonNull ServiceIds serviceIds, @@ -522,40 +500,6 @@ public class GroupsV2StateProcessor { return new GroupUpdateResult(GroupState.GROUP_UPDATED, finalState); } - @WorkerThread - public @NonNull DecryptedGroup getCurrentGroupStateFromServer() - throws IOException, GroupNotAMemberException, GroupDoesNotExistException - { - try { - return groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams)); - } catch (GroupNotFoundException e) { - throw new GroupDoesNotExistException(e); - } catch (NotInGroupException e) { - throw new GroupNotAMemberException(e); - } catch (VerificationFailedException | InvalidGroupStateException e) { - throw new IOException(e); - } - } - - @WorkerThread - public @Nullable DecryptedGroup getSpecificVersionFromServer(int revision) - throws IOException, GroupNotAMemberException, GroupDoesNotExistException - { - try { - return groupsV2Api.getGroupHistoryPage(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), true) - .getResults() - .get(0) - .getGroup() - .orElse(null); - } catch (GroupNotFoundException e) { - throw new GroupDoesNotExistException(e); - } catch (NotInGroupException e) { - throw new GroupNotAMemberException(e); - } catch (VerificationFailedException | InvalidGroupStateException e) { - throw new IOException(e); - } - } - private void insertGroupLeave() { if (!groupDatabase.isActive(groupId)) { warn("Group has already been left."); @@ -655,11 +599,7 @@ public class GroupsV2StateProcessor { } private void info(String message) { - info(message, null); - } - - private void info(String message, Throwable t) { - Log.i(TAG, "[" + groupId.toString() + "] " + message, t); + Log.i(TAG, "[" + groupId.toString() + "] " + message); } private void warn(String message) { @@ -674,7 +614,6 @@ public class GroupsV2StateProcessor { @VisibleForTesting static class ProfileAndMessageHelper { - private final Context context; private final ACI aci; private final GroupId.V2 groupId; private final RecipientTable recipientTable; @@ -682,8 +621,7 @@ public class GroupsV2StateProcessor { @VisibleForTesting GroupMasterKey masterKey; - ProfileAndMessageHelper(@NonNull Context context, @NonNull ACI aci, @NonNull GroupMasterKey masterKey, @NonNull GroupId.V2 groupId, @NonNull RecipientTable recipientTable) { - this.context = context; + ProfileAndMessageHelper(@NonNull ACI aci, @NonNull GroupMasterKey masterKey, @NonNull GroupId.V2 groupId, @NonNull RecipientTable recipientTable) { this.aci = aci; this.masterKey = masterKey; this.groupId = groupId; @@ -706,13 +644,13 @@ public class GroupsV2StateProcessor { DecryptedMember selfAsMember = selfAsMemberOptional.get(); int revisionJoinedAt = selfAsMember.joinedAtRevision; - Optional addedByOptional = Stream.of(inputGroupState.getServerHistory()) - .map(ServerGroupLogEntry::getChange) - .filter(c -> c != null && c.revision == revisionJoinedAt) - .findFirst() - .map(c -> Optional.ofNullable(ServiceId.parseOrNull(c.editorServiceIdBytes)) - .map(Recipient::externalPush)) - .orElse(Optional.empty()); + Optional addedByOptional = inputGroupState.getServerHistory() + .stream() + .map(ServerGroupLogEntry::getChange) + .filter(c -> c != null && c.revision == revisionJoinedAt) + .findFirst() + .flatMap(c -> Optional.ofNullable(ServiceId.parseOrNull(c.editorServiceIdBytes)) + .map(Recipient::externalPush)); if (addedByOptional.isPresent()) { Recipient addedBy = addedByOptional.get(); @@ -804,7 +742,7 @@ public class GroupsV2StateProcessor { void storeMessage(@NonNull DecryptedGroupV2Context decryptedGroupV2Context, long timestamp, @Nullable String serverGuid) { Optional editor = getEditor(decryptedGroupV2Context); - boolean outgoing = !editor.isPresent() || aci.equals(editor.get()); + boolean outgoing = editor.isEmpty() || aci.equals(editor.get()); GV2UpdateDescription updateDescription = new GV2UpdateDescription.Builder() .gv2ChangeDescription(decryptedGroupV2Context) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupV2InfoWorkerJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupV2InfoWorkerJob.java index 7b85123901..ab7531e0e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupV2InfoWorkerJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupV2InfoWorkerJob.java @@ -89,7 +89,7 @@ final class RequestGroupV2InfoWorkerJob extends BaseJob { return; } - GroupManager.updateGroupFromServer(context, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis(), null); + GroupManager.updateGroupFromServer(context, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis()); } @Override diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt index df1ea1926f..28ccd6f85e 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt @@ -32,7 +32,6 @@ import org.thoughtcrime.securesms.database.GroupStateTestData import org.thoughtcrime.securesms.database.GroupTable import org.thoughtcrime.securesms.database.model.databaseprotos.member import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper -import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.testutil.SystemOutLogger @@ -66,7 +65,6 @@ class GroupManagerV2Test_edit { private lateinit var groupsV2API: GroupsV2Api private lateinit var groupsV2Operations: GroupsV2Operations private lateinit var groupsV2Authorization: GroupsV2Authorization - private lateinit var groupsV2StateProcessor: GroupsV2StateProcessor private lateinit var groupCandidateHelper: GroupCandidateHelper private lateinit var sendGroupUpdateHelper: GroupManagerV2.SendGroupUpdateHelper private lateinit var groupOperations: GroupsV2Operations.GroupOperations @@ -89,7 +87,6 @@ class GroupManagerV2Test_edit { groupsV2API = mockk() groupsV2Operations = GroupsV2Operations(clientZkOperations, 1000) groupsV2Authorization = mockk(relaxed = true) - groupsV2StateProcessor = mockk() groupCandidateHelper = mockk() sendGroupUpdateHelper = mockk() groupOperations = groupsV2Operations.forGroup(groupSecretParams) @@ -100,7 +97,6 @@ class GroupManagerV2Test_edit { groupsV2API, groupsV2Operations, groupsV2Authorization, - groupsV2StateProcessor, serviceIds, groupCandidateHelper, sendGroupUpdateHelper