mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-10 16:48:34 +00:00
Add support for GV2 group update messages.
This commit is contained in:
parent
1f994495f8
commit
326678f214
@ -0,0 +1,310 @@
|
||||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
final class GroupsV2UpdateMessageProducer {
|
||||
|
||||
@NonNull private final Context context;
|
||||
@NonNull private final DescribeMemberStrategy descriptionStrategy;
|
||||
@NonNull private final ByteString youUuid;
|
||||
|
||||
/**
|
||||
* @param descriptionStrategy Strategy for member description.
|
||||
*/
|
||||
GroupsV2UpdateMessageProducer(@NonNull Context context,
|
||||
@NonNull DescribeMemberStrategy descriptionStrategy,
|
||||
@NonNull UUID you)
|
||||
{
|
||||
this.context = context;
|
||||
this.descriptionStrategy = descriptionStrategy;
|
||||
this.youUuid = UuidUtil.toByteString(you);
|
||||
}
|
||||
|
||||
List<String> describeChange(@NonNull DecryptedGroupChange change) {
|
||||
List<String> 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 (updates.isEmpty()) {
|
||||
describeUnknownChange(change, updates);
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles case of future protocol versions where we don't know what has changed.
|
||||
*/
|
||||
private void describeUnknownChange(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_updated_group));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_updated_group, describe(change.getEditor())));
|
||||
}
|
||||
}
|
||||
|
||||
private void describeMemberAdditions(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
for (DecryptedMember member : change.getNewMembersList()) {
|
||||
boolean newMemberIsYou = member.getUuid().equals(youUuid);
|
||||
|
||||
if (editorIsYou) {
|
||||
if (newMemberIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_joined_the_group));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_added_s, describe(member.getUuid())));
|
||||
}
|
||||
} else {
|
||||
if (newMemberIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_added_you, describe(change.getEditor())));
|
||||
} else {
|
||||
if (member.getUuid().equals(change.getEditor())) {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_joined_the_group, describe(member.getUuid())));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_added_s, describe(change.getEditor()), describe(member.getUuid())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeMemberRemovals(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
for (ByteString member : change.getDeleteMembersList()) {
|
||||
boolean newMemberIsYou = member.equals(youUuid);
|
||||
|
||||
if (editorIsYou) {
|
||||
if (newMemberIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_left_the_group));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_removed_s, describe(member)));
|
||||
}
|
||||
} else {
|
||||
if (newMemberIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_removed_you_from_the_group, describe(change.getEditor())));
|
||||
} else {
|
||||
if (member.equals(change.getEditor())) {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_left_the_group, describe(member)));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_removed_s, describe(change.getEditor()), describe(member)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeModifyMemberRoles(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
for (DecryptedModifyMemberRole roleChange : change.getModifyMemberRolesList()) {
|
||||
if (roleChange.getRole() == Member.Role.ADMINISTRATOR) {
|
||||
boolean newMemberIsYou = roleChange.getUuid().equals(youUuid);
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_made_s_an_admin, describe(roleChange.getUuid())));
|
||||
} else {
|
||||
if (newMemberIsYou) {
|
||||
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())));
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean newMemberIsYou = roleChange.getUuid().equals(youUuid);
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_revoked_admin_privileges_from_s, describe(roleChange.getUuid())));
|
||||
} else {
|
||||
if (newMemberIsYou) {
|
||||
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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
int notYouInviteCount = 0;
|
||||
|
||||
for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) {
|
||||
boolean newMemberIsYou = invitee.getUuid().equals(youUuid);
|
||||
|
||||
if (newMemberIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_invited_you_to_the_group, describe(change.getEditor())));
|
||||
} else {
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_invited_s_to_the_group, describe(invitee.getUuid())));
|
||||
} else {
|
||||
notYouInviteCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (notYouInviteCount > 0) {
|
||||
updates.add(context.getResources().getQuantityString(R.plurals.MessageRecord_s_invited_members, notYouInviteCount, describe(change.getEditor()), notYouInviteCount));
|
||||
}
|
||||
}
|
||||
|
||||
private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
int notDeclineCount = 0;
|
||||
|
||||
for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) {
|
||||
boolean decline = invitee.getUuid().equals(change.getEditor());
|
||||
if (decline) {
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_declined_the_invitation_to_the_group));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_someone_declined_an_invitation_to_the_group));
|
||||
}
|
||||
} else {
|
||||
notDeclineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (notDeclineCount > 0) {
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getResources().getQuantityString(R.plurals.MessageRecord_you_revoked_invites, notDeclineCount, notDeclineCount));
|
||||
} else {
|
||||
updates.add(context.getResources().getQuantityString(R.plurals.MessageRecord_s_revoked_invites, notDeclineCount, describe(change.getEditor()), notDeclineCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describePromotePending(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
for (ByteString member : change.getPromotePendingMembersList()) {
|
||||
boolean newMemberIsYou = member.equals(youUuid);
|
||||
|
||||
if (editorIsYou) {
|
||||
if (newMemberIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_accepted_invite));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_added_invited_member_s, describe(member)));
|
||||
}
|
||||
} else {
|
||||
if (newMemberIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_added_you, describe(change.getEditor())));
|
||||
} else {
|
||||
if (member.equals(change.getEditor())) {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_accepted_invite, describe(member)));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_added_invited_member_s, describe(change.getEditor()), describe(member)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeNewTitle(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
if (change.hasNewTitle()) {
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_changed_the_group_name_to_s, change.getNewTitle().getValue()));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_changed_the_group_name_to_s, describe(change.getEditor()), change.getNewTitle().getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
if (change.hasNewAvatar()) {
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_changed_the_group_avatar));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_changed_the_group_avatar, describe(change.getEditor())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeNewTimer(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
if (change.hasNewTimer()) {
|
||||
String time = ExpirationUtil.getExpirationDisplayValue(context, change.getNewTimer().getDuration());
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, describe(change.getEditor()), time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeNewAttributeAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
if (change.getNewAttributeAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
||||
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewAttributeAccess());
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_changed_who_can_edit_group_info_to_s, accessLevel));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_changed_who_can_edit_group_info_to_s, describe(change.getEditor()), accessLevel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
||||
|
||||
if (change.getNewMemberAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
||||
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewMemberAccess());
|
||||
if (editorIsYou) {
|
||||
updates.add(context.getString(R.string.MessageRecord_you_changed_who_can_edit_group_membership_to_s, accessLevel));
|
||||
} else {
|
||||
updates.add(context.getString(R.string.MessageRecord_s_changed_who_can_edit_group_membership_to_s, describe(change.getEditor()), accessLevel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull String describe(@NonNull ByteString uuid) {
|
||||
return descriptionStrategy.describe(UuidUtil.fromByteString(uuid));
|
||||
}
|
||||
|
||||
interface DescribeMemberStrategy {
|
||||
|
||||
/**
|
||||
* Map a UUID to a string that describes the group member.
|
||||
*/
|
||||
@NonNull String describe(@NonNull UUID uuid);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.thoughtcrime.securesms.groups;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public final class GV2AccessLevelUtil {
|
||||
|
||||
private GV2AccessLevelUtil() {
|
||||
}
|
||||
|
||||
public static String toString(@NonNull Context context, @NonNull AccessControl.AccessRequired attributeAccess) {
|
||||
switch (attributeAccess) {
|
||||
case ANY : return context.getString(R.string.GroupManagement_access_level_anyone);
|
||||
case MEMBER : return context.getString(R.string.GroupManagement_access_level_all_members);
|
||||
case ADMINISTRATOR : return context.getString(R.string.GroupManagement_access_level_only_admins);
|
||||
default : return context.getString(R.string.GroupManagement_access_level_unknown);
|
||||
}
|
||||
}
|
||||
}
|
@ -442,6 +442,12 @@
|
||||
<!-- GroupMembersDialog -->
|
||||
<string name="GroupMembersDialog_you">You</string>
|
||||
|
||||
<!-- GV2 access levels -->
|
||||
<string name="GroupManagement_access_level_anyone">Anyone</string>
|
||||
<string name="GroupManagement_access_level_all_members">All members</string>
|
||||
<string name="GroupManagement_access_level_only_admins">Only admins</string>
|
||||
<string name="GroupManagement_access_level_unknown" translatable="false">Unknown</string>
|
||||
|
||||
<!-- PendingMembersActivity -->
|
||||
<string name="PendingMemberInvitesActivity_pending_group_invites">Pending group invites</string>
|
||||
<string name="PendingMembersActivity_people_you_invited">People you invited</string>
|
||||
@ -614,6 +620,74 @@
|
||||
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s disabled disappearing messages.</string>
|
||||
<string name="MessageRecord_you_set_disappearing_message_time_to_s">You set the disappearing message timer to %1$s.</string>
|
||||
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set the disappearing message timer to %2$s.</string>
|
||||
|
||||
<!-- GV2 specific -->
|
||||
<!-- GV2 member additions -->
|
||||
<string name="MessageRecord_you_added_s">You added %1$s.</string>
|
||||
<string name="MessageRecord_s_added_s">%1$s added %2$s.</string>
|
||||
<string name="MessageRecord_s_added_you">%1$s added you to the group.</string>
|
||||
<string name="MessageRecord_you_joined_the_group">You joined the group.</string>
|
||||
<string name="MessageRecord_s_joined_the_group">%1$s joined the group.</string>
|
||||
|
||||
<!-- GV2 member removals -->
|
||||
<string name="MessageRecord_you_removed_s">You removed %1$s.</string>
|
||||
<string name="MessageRecord_s_removed_s">%1$s removed %2$s.</string>
|
||||
<string name="MessageRecord_s_removed_you_from_the_group">%1$s removed you from the group.</string>
|
||||
<string name="MessageRecord_you_left_the_group">You left the group.</string>
|
||||
<string name="MessageRecord_s_left_the_group">%1$s left the group.</string>
|
||||
|
||||
<!-- GV2 role change -->
|
||||
<string name="MessageRecord_you_made_s_an_admin">You made %1$s an admin.</string>
|
||||
<string name="MessageRecord_s_made_s_an_admin">%1$s made %2$s an admin.</string>
|
||||
<string name="MessageRecord_s_made_you_an_admin">%1$s made you an admin.</string>
|
||||
<string name="MessageRecord_you_revoked_admin_privileges_from_s">You revoked admin privileges from %1$s.</string>
|
||||
<string name="MessageRecord_s_revoked_your_admin_privileges">%1$s revoked your admin privileges."</string>
|
||||
<string name="MessageRecord_s_revoked_admin_privileges_from_s">%1$s revoked admin privileges from %2$s.</string>
|
||||
|
||||
<!-- GV2 invitations -->
|
||||
<string name="MessageRecord_you_invited_s_to_the_group">You invited %1$s to the group.</string>
|
||||
<string name="MessageRecord_s_invited_you_to_the_group">%1$s invited you to the group.</string>
|
||||
<plurals name="MessageRecord_s_invited_members">
|
||||
<item quantity="one">%1$s invited 1 person to the group.</item>
|
||||
<item quantity="other">%1$s invited %2$d people to the group.</item>
|
||||
</plurals>
|
||||
|
||||
<!-- GV2 invitation revokes -->
|
||||
<plurals name="MessageRecord_you_revoked_invites">
|
||||
<item quantity="one">You revoked an invitation to the group.</item>
|
||||
<item quantity="other">You revoked %1$d invitations to the group.</item>
|
||||
</plurals>
|
||||
<plurals name="MessageRecord_s_revoked_invites">
|
||||
<item quantity="one">%1$s revoked an invitation to the group.</item>
|
||||
<item quantity="other">%1$s revoked %2$d invitations to the group.</item>
|
||||
</plurals>
|
||||
<string name="MessageRecord_someone_declined_an_invitation_to_the_group">Someone declined an invitation to the group.</string>
|
||||
<string name="MessageRecord_you_declined_the_invitation_to_the_group">You declined the invitation to the group.</string>
|
||||
|
||||
<!-- GV2 invitation acceptance -->
|
||||
<string name="MessageRecord_you_accepted_invite">You accepted the invitation to the group.</string>
|
||||
<string name="MessageRecord_s_accepted_invite">%1$s accepted an invitation to the group.</string>
|
||||
<string name="MessageRecord_you_added_invited_member_s">You added invited member %1$s.</string>
|
||||
<string name="MessageRecord_s_added_invited_member_s">%1$s added invited member %2$s.</string>
|
||||
|
||||
<!-- GV2 title change -->
|
||||
<string name="MessageRecord_you_changed_the_group_name_to_s">You changed the group name to \"%1$s\".</string>
|
||||
<string name="MessageRecord_s_changed_the_group_name_to_s">%1$s changed the group name to \"%2$s\".</string>
|
||||
|
||||
<!-- GV2 avatar change -->
|
||||
<string name="MessageRecord_you_changed_the_group_avatar">You changed the group avatar.</string>
|
||||
<string name="MessageRecord_s_changed_the_group_avatar">%1$s changed the group avatar.</string>
|
||||
|
||||
<!-- GV2 attribute access level change -->
|
||||
<string name="MessageRecord_you_changed_who_can_edit_group_info_to_s">You changed who can edit group info to \"%1$s\".</string>
|
||||
<string name="MessageRecord_s_changed_who_can_edit_group_info_to_s">%1$s changed who can edit group info to \"%2$s\".</string>
|
||||
|
||||
<!-- GV2 membership access level change -->
|
||||
<string name="MessageRecord_you_changed_who_can_edit_group_membership_to_s">You changed who can edit group membership to \"%1$s\".</string>
|
||||
<string name="MessageRecord_s_changed_who_can_edit_group_membership_to_s">%1$s changed who can edit group membership to \"%2$s\".</string>
|
||||
|
||||
<!-- End of GV2 specific update messages -->
|
||||
|
||||
<string name="MessageRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">You marked your safety number with %s verified</string>
|
||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device">You marked your safety number with %s verified from another device</string>
|
||||
|
@ -0,0 +1,589 @@
|
||||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.DisappearingMessagesTimer;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedString;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, application = Application.class)
|
||||
public final class GroupsV2UpdateMessageProducerTest {
|
||||
|
||||
private UUID you;
|
||||
private UUID alice;
|
||||
private UUID bob;
|
||||
|
||||
private GroupsV2UpdateMessageProducer producer;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
you = UUID.randomUUID();
|
||||
alice = UUID.randomUUID();
|
||||
bob = UUID.randomUUID();
|
||||
GroupsV2UpdateMessageProducer.DescribeMemberStrategy describeMember = createDescriber(ImmutableMap.of(alice, "Alice", bob, "Bob"));
|
||||
producer = new GroupsV2UpdateMessageProducer(ApplicationProvider.getApplicationContext(), describeMember, you);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty_change() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice updated the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty_change_by_you() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You updated the group.")));
|
||||
}
|
||||
|
||||
// Member additions
|
||||
|
||||
@Test
|
||||
public void member_added_member() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.addMember(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice added Bob.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_added_member() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.addMember(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You added Bob.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_added_you() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.addMember(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice added you to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_added_you() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.addMember(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You joined the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_added_themselves() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.addMember(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob joined the group.")));
|
||||
}
|
||||
|
||||
// Member removals
|
||||
|
||||
@Test
|
||||
public void member_removed_member() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.deleteMember(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice removed Bob.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_removed_member() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.deleteMember(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You removed Bob.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_removed_you() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.deleteMember(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice removed you from the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_removed_you() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.deleteMember(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You left the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_removed_themselves() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.deleteMember(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob left the group.")));
|
||||
}
|
||||
|
||||
// Member role modifications
|
||||
|
||||
@Test
|
||||
public void you_make_member_admin() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.promoteToAdmin(alice)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You made Alice an admin.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_makes_member_admin() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.promoteToAdmin(alice)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob made Alice an admin.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_makes_you_admin() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.promoteToAdmin(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice made you an admin.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_revoked_member_admin() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.demoteToMember(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You revoked admin privileges from Bob.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_revokes_member_admin() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.demoteToMember(alice)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob revoked admin privileges from Alice.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_revokes_your_admin() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.demoteToMember(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice revoked your admin privileges.")));
|
||||
}
|
||||
|
||||
// Member invitation
|
||||
|
||||
@Test
|
||||
public void you_invited_member() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.invite(alice)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You invited Alice to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_invited_you() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.invite(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice invited you to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_invited_1_person() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.invite(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice invited 1 person to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_invited_2_persons() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.invite(bob)
|
||||
.invite(UUID.randomUUID())
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice invited 2 people to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_invited_3_persons_and_you() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.invite(alice)
|
||||
.invite(you)
|
||||
.invite(UUID.randomUUID())
|
||||
.invite(UUID.randomUUID())
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(Arrays.asList("Bob invited you to the group.", "Bob invited 3 people to the group.")));
|
||||
}
|
||||
|
||||
// Member invitation revocation
|
||||
|
||||
@Test
|
||||
public void member_uninvited_1_person() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.uninvite(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice revoked an invitation to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_uninvited_2_people() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.uninvite(bob)
|
||||
.uninvite(UUID.randomUUID())
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice revoked 2 invitations to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_uninvited_1_person() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.uninvite(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You revoked an invitation to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_uninvited_2_people() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.uninvite(bob)
|
||||
.uninvite(UUID.randomUUID())
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You revoked 2 invitations to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pending_member_declines_invite() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.uninvite(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Someone declined an invitation to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_decline_invite() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.uninvite(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You declined the invitation to the group.")));
|
||||
}
|
||||
|
||||
// Promote pending members
|
||||
|
||||
@Test
|
||||
public void member_accepts_invite() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.promote(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob accepted an invitation to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_accept_invite() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.promote(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You accepted the invitation to the group.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_promotes_pending_member() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.promote(alice)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob added invited member Alice.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_promote_pending_member() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.promote(bob)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You added invited member Bob.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_promotes_you() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.promote(you)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob added you to the group.")));
|
||||
}
|
||||
|
||||
// Title change
|
||||
|
||||
@Test
|
||||
public void member_changes_title() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.title("New title")
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice changed the group name to \"New title\".")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_change_title() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.title("Title 2")
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You changed the group name to \"Title 2\".")));
|
||||
}
|
||||
|
||||
// Avatar change
|
||||
|
||||
@Test
|
||||
public void member_changes_avatar() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.avatar("Avatar1")
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice changed the group avatar.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_change_avatar() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.avatar("Avatar2")
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You changed the group avatar.")));
|
||||
}
|
||||
|
||||
// Timer change
|
||||
|
||||
@Test
|
||||
public void member_changes_timer() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.timer(10)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob set the disappearing message timer to 10 seconds.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_change_timer() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.timer(60)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You set the disappearing message timer to 1 minute.")));
|
||||
}
|
||||
|
||||
// Attribute access change
|
||||
|
||||
@Test
|
||||
public void member_changes_attribute_access() {
|
||||
DecryptedGroupChange change = changeBy(bob)
|
||||
.attributeAccess(AccessControl.AccessRequired.MEMBER)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Bob changed who can edit group info to \"All members\".")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_changed_attribute_access() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.attributeAccess(AccessControl.AccessRequired.ADMINISTRATOR)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You changed who can edit group info to \"Only admins\".")));
|
||||
}
|
||||
|
||||
// Membership access change
|
||||
|
||||
@Test
|
||||
public void member_changes_membership_access() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.membershipAccess(AccessControl.AccessRequired.ADMINISTRATOR)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("Alice changed who can edit group membership to \"Only admins\".")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_changed_membership_access() {
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.membershipAccess(AccessControl.AccessRequired.MEMBER)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(singletonList("You changed who can edit group membership to \"All members\".")));
|
||||
}
|
||||
|
||||
// Multiple changes
|
||||
|
||||
@Test
|
||||
public void multiple_changes() {
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.addMember(bob)
|
||||
.membershipAccess(AccessControl.AccessRequired.MEMBER)
|
||||
.title("Title")
|
||||
.timer(300)
|
||||
.build();
|
||||
|
||||
assertThat(producer.describeChange(change), is(Arrays.asList(
|
||||
"Alice added Bob.",
|
||||
"Alice changed the group name to \"Title\".",
|
||||
"Alice set the disappearing message timer to 5 minutes.",
|
||||
"Alice changed who can edit group membership to \"All members\".")));
|
||||
}
|
||||
|
||||
private static class ChangeBuilder {
|
||||
|
||||
private final DecryptedGroupChange.Builder builder;
|
||||
|
||||
ChangeBuilder(@NonNull UUID editor) {
|
||||
builder = DecryptedGroupChange.newBuilder()
|
||||
.setEditor(UuidUtil.toByteString(editor));
|
||||
}
|
||||
|
||||
ChangeBuilder addMember(@NonNull UUID newMember) {
|
||||
builder.addNewMembers(DecryptedMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(newMember)));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder deleteMember(@NonNull UUID removedMember) {
|
||||
builder.addDeleteMembers(UuidUtil.toByteString(removedMember));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder promoteToAdmin(@NonNull UUID member) {
|
||||
builder.addModifyMemberRoles(DecryptedModifyMemberRole.newBuilder()
|
||||
.setRole(Member.Role.ADMINISTRATOR)
|
||||
.setUuid(UuidUtil.toByteString(member)));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder demoteToMember(@NonNull UUID member) {
|
||||
builder.addModifyMemberRoles(DecryptedModifyMemberRole.newBuilder()
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.setUuid(UuidUtil.toByteString(member)));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder invite(@NonNull UUID potentialMember) {
|
||||
builder.addNewPendingMembers(DecryptedPendingMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(potentialMember)));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder uninvite(@NonNull UUID pendingMember) {
|
||||
builder.addDeletePendingMembers(DecryptedPendingMemberRemoval.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(pendingMember)));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder promote(@NonNull UUID pendingMember) {
|
||||
builder.addPromotePendingMembers(UuidUtil.toByteString(pendingMember));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder title(@NonNull String newTitle) {
|
||||
builder.setNewTitle(DecryptedString.newBuilder()
|
||||
.setValue(newTitle));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder avatar(@NonNull String newAvatar) {
|
||||
builder.setNewAvatar(DecryptedString.newBuilder()
|
||||
.setValue(newAvatar));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder timer(int duration) {
|
||||
builder.setNewTimer(DisappearingMessagesTimer.newBuilder()
|
||||
.setDuration(duration));
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder attributeAccess(@NonNull AccessControl.AccessRequired accessRequired) {
|
||||
builder.setNewAttributeAccess(accessRequired);
|
||||
return this;
|
||||
}
|
||||
|
||||
ChangeBuilder membershipAccess(@NonNull AccessControl.AccessRequired accessRequired) {
|
||||
builder.setNewMemberAccess(accessRequired);
|
||||
return this;
|
||||
}
|
||||
|
||||
DecryptedGroupChange build() {
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
private ChangeBuilder changeBy(@NonNull UUID groupEditor) {
|
||||
return new ChangeBuilder(groupEditor);
|
||||
}
|
||||
|
||||
private @NonNull GroupsV2UpdateMessageProducer.DescribeMemberStrategy createDescriber(@NonNull Map<UUID, String> map) {
|
||||
return uuid -> {
|
||||
String name = map.get(uuid);
|
||||
assertNotNull(name);
|
||||
return name;
|
||||
};
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user