diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java index 249d435fec..a4f25c1cdf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java @@ -78,20 +78,39 @@ final class GroupsV2UpdateMessageProducer { List describeChange(@NonNull DecryptedGroupChange change) { List updates = new LinkedList<>(); - describeMemberAdditions(change, updates); - describeMemberRemovals(change, updates); - describeModifyMemberRoles(change, updates); - describeInvitations(change, updates); - describeRevokedInvitations(change, updates); - describePromotePending(change, updates); - describeNewTitle(change, updates); - describeNewAvatar(change, updates); - describeNewTimer(change, updates); - describeNewAttributeAccess(change, updates); - describeNewMembershipAccess(change, updates); + if (change.getEditor().isEmpty() || UuidUtil.UNKNOWN_UUID.equals(UuidUtil.fromByteString(change.getEditor()))) { + describeUnknownEditorMemberAdditions(change, updates); + describeUnknownEditorMemberRemovals(change, updates); + describeUnknownEditorModifyMemberRoles(change, updates); + describeUnknownEditorInvitations(change, updates); + describeUnknownEditorRevokedInvitations(change, updates); + describeUnknownEditorPromotePending(change, updates); + describeUnknownEditorNewTitle(change, updates); + describeUnknownEditorNewAvatar(change, updates); + describeUnknownEditorNewTimer(change, updates); + describeUnknownEditorNewAttributeAccess(change, updates); + describeUnknownEditorNewMembershipAccess(change, updates); - if (updates.isEmpty()) { - describeUnknownChange(change, updates); + if (updates.isEmpty()) { + describeUnknownEditorUnknownChange(updates); + } + + } else { + describeMemberAdditions(change, updates); + describeMemberRemovals(change, updates); + describeModifyMemberRoles(change, updates); + describeInvitations(change, updates); + describeRevokedInvitations(change, updates); + describePromotePending(change, updates); + describeNewTitle(change, updates); + describeNewAvatar(change, updates); + describeNewTimer(change, updates); + describeNewAttributeAccess(change, updates); + describeNewMembershipAccess(change, updates); + + if (updates.isEmpty()) { + describeUnknownChange(change, updates); + } } return updates; @@ -110,6 +129,10 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorUnknownChange(@NonNull List updates) { + updates.add(context.getString(R.string.MessageRecord_the_group_was_updated)); + } + private void describeMemberAdditions(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); @@ -136,6 +159,18 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorMemberAdditions(@NonNull DecryptedGroupChange change, @NonNull List updates) { + for (DecryptedMember member : change.getNewMembersList()) { + boolean newMemberIsYou = member.getUuid().equals(selfUuidBytes); + + if (newMemberIsYou) { + updates.add(context.getString(R.string.MessageRecord_you_joined_the_group)); + } else { + updates.add(context.getString(R.string.MessageRecord_s_joined_the_group, describe(member.getUuid()))); + } + } + } + private void describeMemberRemovals(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); @@ -162,16 +197,28 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorMemberRemovals(@NonNull DecryptedGroupChange change, @NonNull List updates) { + for (ByteString member : change.getDeleteMembersList()) { + boolean removedMemberIsYou = member.equals(selfUuidBytes); + + if (removedMemberIsYou) { + updates.add(context.getString(R.string.MessageRecord_you_are_no_longer_in_the_group)); + } else { + updates.add(context.getString(R.string.MessageRecord_s_is_no_longer_in_the_group, describe(member))); + } + } + } + private void describeModifyMemberRoles(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); for (DecryptedModifyMemberRole roleChange : change.getModifyMemberRolesList()) { + boolean changedMemberIsYou = roleChange.getUuid().equals(selfUuidBytes); if (roleChange.getRole() == Member.Role.ADMINISTRATOR) { - boolean newMemberIsYou = roleChange.getUuid().equals(selfUuidBytes); if (editorIsYou) { updates.add(context.getString(R.string.MessageRecord_you_made_s_an_admin, describe(roleChange.getUuid()))); } else { - if (newMemberIsYou) { + if (changedMemberIsYou) { updates.add(context.getString(R.string.MessageRecord_s_made_you_an_admin, describe(change.getEditor()))); } else { updates.add(context.getString(R.string.MessageRecord_s_made_s_an_admin, describe(change.getEditor()), describe(roleChange.getUuid()))); @@ -179,11 +226,10 @@ final class GroupsV2UpdateMessageProducer { } } } else { - boolean newMemberIsYou = roleChange.getUuid().equals(selfUuidBytes); if (editorIsYou) { updates.add(context.getString(R.string.MessageRecord_you_revoked_admin_privileges_from_s, describe(roleChange.getUuid()))); } else { - if (newMemberIsYou) { + if (changedMemberIsYou) { updates.add(context.getString(R.string.MessageRecord_s_revoked_your_admin_privileges, describe(change.getEditor()))); } else { updates.add(context.getString(R.string.MessageRecord_s_revoked_admin_privileges_from_s, describe(change.getEditor()), describe(roleChange.getUuid()))); @@ -193,6 +239,26 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorModifyMemberRoles(@NonNull DecryptedGroupChange change, @NonNull List updates) { + for (DecryptedModifyMemberRole roleChange : change.getModifyMemberRolesList()) { + boolean changedMemberIsYou = roleChange.getUuid().equals(selfUuidBytes); + + if (roleChange.getRole() == Member.Role.ADMINISTRATOR) { + if (changedMemberIsYou) { + updates.add(context.getString(R.string.MessageRecord_you_are_now_an_admin)); + } else { + updates.add(context.getString(R.string.MessageRecord_s_is_now_an_admin, describe(roleChange.getUuid()))); + } + } else { + if (changedMemberIsYou) { + updates.add(context.getString(R.string.MessageRecord_you_are_no_longer_an_admin)); + } else { + updates.add(context.getString(R.string.MessageRecord_s_is_no_longer_an_admin, describe(roleChange.getUuid()))); + } + } + } + } + private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); int notYouInviteCount = 0; @@ -216,6 +282,24 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorInvitations(@NonNull DecryptedGroupChange change, @NonNull List updates) { + int notYouInviteCount = 0; + + for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) { + boolean newMemberIsYou = invitee.getUuid().equals(selfUuidBytes); + + if (newMemberIsYou) { + updates.add(context.getString(R.string.MessageRecord_you_were_invited_to_the_group)); + } else { + notYouInviteCount++; + } + } + + if (notYouInviteCount > 0) { + updates.add(context.getResources().getQuantityString(R.plurals.MessageRecord_d_people_were_invited_to_the_group, notYouInviteCount, notYouInviteCount)); + } + } + private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); int notDeclineCount = 0; @@ -242,6 +326,24 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List updates) { + int notDeclineCount = 0; + + for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) { + boolean inviteeWasYou = invitee.getUuid().equals(selfUuidBytes); + + if (inviteeWasYou) { + updates.add(context.getString(R.string.MessageRecord_your_invitation_to_the_group_was_revoked)); + } else { + notDeclineCount++; + } + } + + if (notDeclineCount > 0) { + updates.add(context.getResources().getQuantityString(R.plurals.MessageRecord_d_invitations_were_revoked, notDeclineCount, notDeclineCount)); + } + } + private void describePromotePending(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); @@ -269,6 +371,19 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorPromotePending(@NonNull DecryptedGroupChange change, @NonNull List updates) { + for (DecryptedMember newMember : change.getPromotePendingMembersList()) { + ByteString uuid = newMember.getUuid(); + boolean newMemberIsYou = uuid.equals(selfUuidBytes); + + if (newMemberIsYou) { + updates.add(context.getString(R.string.MessageRecord_you_joined_the_group)); + } else { + updates.add(context.getString(R.string.MessageRecord_s_joined_the_group, describe(uuid))); + } + } + } + private void describeNewTitle(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); @@ -281,6 +396,12 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorNewTitle(@NonNull DecryptedGroupChange change, @NonNull List updates) { + if (change.hasNewTitle()) { + updates.add(context.getString(R.string.MessageRecord_the_group_name_has_changed_to_s, change.getNewTitle().getValue())); + } + } + private void describeNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); @@ -293,6 +414,12 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List updates) { + if (change.hasNewAvatar()) { + updates.add(context.getString(R.string.MessageRecord_the_group_group_avatar_has_been_changed)); + } + } + private void describeNewTimer(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); @@ -306,6 +433,13 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorNewTimer(@NonNull DecryptedGroupChange change, @NonNull List updates) { + if (change.hasNewTimer()) { + String time = ExpirationUtil.getExpirationDisplayValue(context, change.getNewTimer().getDuration()); + updates.add(context.getString(R.string.MessageRecord_disappearing_message_time_set_to_s, time)); + } + } + private void describeNewAttributeAccess(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); @@ -319,6 +453,13 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorNewAttributeAccess(@NonNull DecryptedGroupChange change, @NonNull List updates) { + if (change.getNewAttributeAccess() != AccessControl.AccessRequired.UNKNOWN) { + String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewAttributeAccess()); + updates.add(context.getString(R.string.MessageRecord_who_can_edit_group_info_has_been_changed_to_s, accessLevel)); + } + } + private void describeNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List updates) { boolean editorIsYou = change.getEditor().equals(selfUuidBytes); @@ -332,6 +473,13 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeUnknownEditorNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List updates) { + if (change.getNewMemberAccess() != AccessControl.AccessRequired.UNKNOWN) { + String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewMemberAccess()); + updates.add(context.getString(R.string.MessageRecord_who_can_edit_group_membership_has_been_changed_to_s, accessLevel)); + } + } + private @NonNull String describe(@NonNull ByteString uuid) { return descriptionStrategy.describe(UuidUtil.fromByteString(uuid)); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 733c22e4e2..346ea37ebb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -740,6 +740,7 @@ Received a message encrypted using an old version of Signal that is no longer supported. Please ask the sender to update to the most recent version and resend the message. You have left the group. You updated the group. + The group was updated. You called Contact called Missed call @@ -752,6 +753,7 @@ %1$s disabled disappearing messages. You set the disappearing message timer to %1$s. %1$s set the disappearing message timer to %2$s. + The disappearing message timer has been set to %1$s. You created the group. @@ -770,6 +772,8 @@ %1$s removed you from the group. You left the group. %1$s left the group. + You are no longer in the group. + %1$s is no longer in the group. You made %1$s an admin. @@ -778,6 +782,10 @@ You revoked admin privileges from %1$s. %1$s revoked your admin privileges." %1$s revoked admin privileges from %2$s. + %1$s is now an admin. + You are now an admin. + %1$s is no longer an admin. + You are no longer an admin. You invited %1$s to the group. @@ -786,6 +794,11 @@ %1$s invited 1 person to the group. %1$s invited %2$d people to the group. + You were invited to the group. + + 1 person was invited to the group. + %1$d people were invited to the group. + @@ -798,6 +811,11 @@ Someone declined an invitation to the group. You declined the invitation to the group. + Your invitation to the group was revoked. + + An invitation to the group was revoked. + %1$d invitations to the group were revoked. + You accepted the invitation to the group. @@ -808,18 +826,22 @@ You changed the group name to \"%1$s\". %1$s changed the group name to \"%2$s\". + The group name has changed to \"%1$s\". You changed the group avatar. %1$s changed the group avatar. + The group avatar has been changed. You changed who can edit group info to \"%1$s\". %1$s changed who can edit group info to \"%2$s\". + Who can edit group info has been changed to \"%1$s\". You changed who can edit group membership to \"%1$s\". %1$s changed who can edit group membership to \"%2$s\". + Who can edit group membership has been changed to \"%1$s\". diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java index 422d21f54f..1d68ed195d 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java @@ -68,6 +68,14 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("You updated the group."))); } + @Test + public void empty_change_by_unknown() { + DecryptedGroupChange change = changeByUnknown() + .build(); + + assertThat(producer.describeChange(change), is(singletonList("The group was updated."))); + } + // Member additions @Test @@ -115,6 +123,24 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("Bob joined the group."))); } + @Test + public void unknown_added_you() { + DecryptedGroupChange change = changeByUnknown() + .addMember(you) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("You joined the group."))); + } + + @Test + public void unknown_added_member() { + DecryptedGroupChange change = changeByUnknown() + .addMember(bob) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("Bob joined the group."))); + } + // Member removals @Test @@ -162,6 +188,24 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("Bob left the group."))); } + @Test + public void unknown_removed_member() { + DecryptedGroupChange change = changeByUnknown() + .deleteMember(alice) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("Alice is no longer in the group."))); + } + + @Test + public void unknown_removed_you() { + DecryptedGroupChange change = changeByUnknown() + .deleteMember(you) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("You are no longer in the group."))); + } + // Member role modifications @Test @@ -218,6 +262,42 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("Alice revoked your admin privileges."))); } + @Test + public void unknown_makes_member_admin() { + DecryptedGroupChange change = changeByUnknown() + .promoteToAdmin(alice) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("Alice is now an admin."))); + } + + @Test + public void unknown_makes_you_admin() { + DecryptedGroupChange change = changeByUnknown() + .promoteToAdmin(you) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("You are now an admin."))); + } + + @Test + public void unknown_revokes_member_admin() { + DecryptedGroupChange change = changeByUnknown() + .demoteToMember(alice) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("Alice is no longer an admin."))); + } + + @Test + public void unknown_revokes_your_admin() { + DecryptedGroupChange change = changeByUnknown() + .demoteToMember(you) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("You are no longer an admin."))); + } + // Member invitation @Test @@ -269,6 +349,46 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(Arrays.asList("Bob invited you to the group.", "Bob invited 3 people to the group."))); } + @Test + public void unknown_invited_you() { + DecryptedGroupChange change = changeByUnknown() + .invite(you) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("You were invited to the group."))); + } + + @Test + public void unknown_invited_1_person() { + DecryptedGroupChange change = changeByUnknown() + .invite(alice) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("1 person was invited to the group."))); + } + + @Test + public void unknown_invited_2_persons() { + DecryptedGroupChange change = changeByUnknown() + .invite(alice) + .invite(bob) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("2 people were invited to the group."))); + } + + @Test + public void unknown_invited_3_persons_and_you() { + DecryptedGroupChange change = changeByUnknown() + .invite(alice) + .invite(you) + .invite(UUID.randomUUID()) + .invite(UUID.randomUUID()) + .build(); + + assertThat(producer.describeChange(change), is(Arrays.asList("You were invited to the group.", "3 people were invited to the group."))); + } + // Member invitation revocation @Test @@ -327,6 +447,46 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("You declined the invitation to the group."))); } + @Test + public void unknown_revokes_your_invite() { + DecryptedGroupChange change = changeByUnknown() + .uninvite(you) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("Your invitation to the group was revoked."))); + } + + @Test + public void unknown_revokes_1_invite() { + DecryptedGroupChange change = changeByUnknown() + .uninvite(bob) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("An invitation to the group was revoked."))); + } + + @Test + public void unknown_revokes_2_invites() { + DecryptedGroupChange change = changeByUnknown() + .uninvite(bob) + .uninvite(UUID.randomUUID()) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("2 invitations to the group were revoked."))); + } + + @Test + public void unknown_revokes_yours_and_three_other_invites() { + DecryptedGroupChange change = changeByUnknown() + .uninvite(bob) + .uninvite(you) + .uninvite(UUID.randomUUID()) + .uninvite(UUID.randomUUID()) + .build(); + + assertThat(producer.describeChange(change), is(Arrays.asList("Your invitation to the group was revoked.", "3 invitations to the group were revoked."))); + } + // Promote pending members @Test @@ -374,6 +534,24 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("Bob added you to the group."))); } + @Test + public void unknown_added_by_invite() { + DecryptedGroupChange change = changeByUnknown() + .promote(you) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("You joined the group."))); + } + + @Test + public void unknown_promotes_pending_member() { + DecryptedGroupChange change = changeByUnknown() + .promote(alice) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("Alice joined the group."))); + } + // Title change @Test @@ -393,6 +571,15 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("You changed the group name to \"Title 2\"."))); } + + @Test + public void unknown_changed_title() { + DecryptedGroupChange change = changeByUnknown() + .title("Title 3") + .build(); + + assertThat(producer.describeChange(change), is(singletonList("The group name has changed to \"Title 3\"."))); + } // Avatar change @@ -414,6 +601,15 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("You changed the group avatar."))); } + @Test + public void unknown_changed_avatar() { + DecryptedGroupChange change = changeByUnknown() + .avatar("Avatar3") + .build(); + + assertThat(producer.describeChange(change), is(singletonList("The group avatar has been changed."))); + } + // Timer change @Test @@ -434,6 +630,15 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("You set the disappearing message timer to 1 minute."))); } + @Test + public void unknown_change_timer() { + DecryptedGroupChange change = changeByUnknown() + .timer(120) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("The disappearing message timer has been set to 2 minutes."))); + } + // Attribute access change @Test @@ -454,6 +659,15 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("You changed who can edit group info to \"Only admins\"."))); } + @Test + public void unknown_changed_attribute_access() { + DecryptedGroupChange change = changeByUnknown() + .attributeAccess(AccessControl.AccessRequired.ADMINISTRATOR) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("Who can edit group info has been changed to \"Only admins\"."))); + } + // Membership access change @Test @@ -474,6 +688,15 @@ public final class GroupsV2UpdateMessageProducerTest { assertThat(producer.describeChange(change), is(singletonList("You changed who can edit group membership to \"All members\"."))); } + @Test + public void unknown_changed_membership_access() { + DecryptedGroupChange change = changeByUnknown() + .membershipAccess(AccessControl.AccessRequired.ADMINISTRATOR) + .build(); + + assertThat(producer.describeChange(change), is(singletonList("Who can edit group membership has been changed to \"Only admins\"."))); + } + // Multiple changes @Test @@ -492,6 +715,24 @@ public final class GroupsV2UpdateMessageProducerTest { "Alice changed who can edit group membership to \"All members\"."))); } + @Test + public void multiple_changes_by_unknown() { + DecryptedGroupChange change = changeByUnknown() + .addMember(bob) + .membershipAccess(AccessControl.AccessRequired.MEMBER) + .title("Title 2") + .avatar("Avatar 1") + .timer(600) + .build(); + + assertThat(producer.describeChange(change), is(Arrays.asList( + "Bob joined the group.", + "The group name has changed to \"Title 2\".", + "The group avatar has been changed.", + "The disappearing message timer has been set to 10 minutes.", + "Who can edit group membership has been changed to \"All members\"."))); + } + // Group state without a change record @Test @@ -579,6 +820,10 @@ public final class GroupsV2UpdateMessageProducerTest { .setEditor(UuidUtil.toByteString(editor)); } + ChangeBuilder() { + builder = DecryptedGroupChange.newBuilder(); + } + ChangeBuilder addMember(@NonNull UUID newMember) { builder.addNewMembers(DecryptedMember.newBuilder() .setUuid(UuidUtil.toByteString(newMember))); @@ -658,6 +903,10 @@ public final class GroupsV2UpdateMessageProducerTest { return new ChangeBuilder(groupEditor); } + private static ChangeBuilder changeByUnknown() { + return new ChangeBuilder(); + } + private static @NonNull GroupsV2UpdateMessageProducer.DescribeMemberStrategy createDescriber(@NonNull Map map) { return uuid -> { String name = map.get(uuid);