GV2 Update message description.
This commit is contained in:
parent
b917cccbee
commit
4c5822ac67
3 changed files with 200 additions and 23 deletions
|
@ -8,6 +8,7 @@ import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
import org.signal.storageservice.protos.groups.AccessControl;
|
import org.signal.storageservice.protos.groups.AccessControl;
|
||||||
import org.signal.storageservice.protos.groups.Member;
|
import org.signal.storageservice.protos.groups.Member;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
|
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
|
||||||
|
@ -16,6 +17,8 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemov
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
|
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -26,18 +29,50 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
|
|
||||||
@NonNull private final Context context;
|
@NonNull private final Context context;
|
||||||
@NonNull private final DescribeMemberStrategy descriptionStrategy;
|
@NonNull private final DescribeMemberStrategy descriptionStrategy;
|
||||||
@NonNull private final ByteString youUuid;
|
@NonNull private final UUID selfUuid;
|
||||||
|
@NonNull private final ByteString selfUuidBytes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param descriptionStrategy Strategy for member description.
|
* @param descriptionStrategy Strategy for member description.
|
||||||
*/
|
*/
|
||||||
GroupsV2UpdateMessageProducer(@NonNull Context context,
|
GroupsV2UpdateMessageProducer(@NonNull Context context,
|
||||||
@NonNull DescribeMemberStrategy descriptionStrategy,
|
@NonNull DescribeMemberStrategy descriptionStrategy,
|
||||||
@NonNull UUID you)
|
@NonNull UUID selfUuid)
|
||||||
{
|
{
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.descriptionStrategy = descriptionStrategy;
|
this.descriptionStrategy = descriptionStrategy;
|
||||||
this.youUuid = UuidUtil.toByteString(you);
|
this.selfUuid = selfUuid;
|
||||||
|
this.selfUuidBytes = UuidUtil.toByteString(selfUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a group that is new to you, use this when there is no available change record.
|
||||||
|
* <p>
|
||||||
|
* Invitation and groups you create are the most common cases where no change is available.
|
||||||
|
*/
|
||||||
|
String describeNewGroup(@NonNull DecryptedGroup group) {
|
||||||
|
Optional<DecryptedPendingMember> selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfUuid);
|
||||||
|
if (selfPending.isPresent()) {
|
||||||
|
return context.getString(R.string.MessageRecord_s_invited_you_to_the_group, describe(selfPending.get().getAddedByUuid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.getVersion() == 0) {
|
||||||
|
Optional<DecryptedMember> foundingMember = DecryptedGroupUtil.firstMember(group.getMembersList());
|
||||||
|
if (foundingMember.isPresent()) {
|
||||||
|
ByteString foundingMemberUuid = foundingMember.get().getUuid();
|
||||||
|
if (selfUuidBytes.equals(foundingMemberUuid)) {
|
||||||
|
return context.getString(R.string.MessageRecord_you_created_the_group);
|
||||||
|
} else {
|
||||||
|
return context.getString(R.string.MessageRecord_s_added_you, describe(foundingMemberUuid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfUuid).isPresent()) {
|
||||||
|
return context.getString(R.string.MessageRecord_you_joined_the_group);
|
||||||
|
} else {
|
||||||
|
return context.getString(R.string.MessageRecord_group_updated);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> describeChange(@NonNull DecryptedGroupChange change) {
|
List<String> describeChange(@NonNull DecryptedGroupChange change) {
|
||||||
|
@ -66,7 +101,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
* Handles case of future protocol versions where we don't know what has changed.
|
* Handles case of future protocol versions where we don't know what has changed.
|
||||||
*/
|
*/
|
||||||
private void describeUnknownChange(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeUnknownChange(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
updates.add(context.getString(R.string.MessageRecord_you_updated_group));
|
updates.add(context.getString(R.string.MessageRecord_you_updated_group));
|
||||||
|
@ -76,10 +111,10 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeMemberAdditions(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeMemberAdditions(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
for (DecryptedMember member : change.getNewMembersList()) {
|
for (DecryptedMember member : change.getNewMembersList()) {
|
||||||
boolean newMemberIsYou = member.getUuid().equals(youUuid);
|
boolean newMemberIsYou = member.getUuid().equals(selfUuidBytes);
|
||||||
|
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
if (newMemberIsYou) {
|
if (newMemberIsYou) {
|
||||||
|
@ -102,10 +137,10 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeMemberRemovals(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeMemberRemovals(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
for (ByteString member : change.getDeleteMembersList()) {
|
for (ByteString member : change.getDeleteMembersList()) {
|
||||||
boolean newMemberIsYou = member.equals(youUuid);
|
boolean newMemberIsYou = member.equals(selfUuidBytes);
|
||||||
|
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
if (newMemberIsYou) {
|
if (newMemberIsYou) {
|
||||||
|
@ -128,11 +163,11 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeModifyMemberRoles(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeModifyMemberRoles(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
for (DecryptedModifyMemberRole roleChange : change.getModifyMemberRolesList()) {
|
for (DecryptedModifyMemberRole roleChange : change.getModifyMemberRolesList()) {
|
||||||
if (roleChange.getRole() == Member.Role.ADMINISTRATOR) {
|
if (roleChange.getRole() == Member.Role.ADMINISTRATOR) {
|
||||||
boolean newMemberIsYou = roleChange.getUuid().equals(youUuid);
|
boolean newMemberIsYou = roleChange.getUuid().equals(selfUuidBytes);
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
updates.add(context.getString(R.string.MessageRecord_you_made_s_an_admin, describe(roleChange.getUuid())));
|
updates.add(context.getString(R.string.MessageRecord_you_made_s_an_admin, describe(roleChange.getUuid())));
|
||||||
} else {
|
} else {
|
||||||
|
@ -144,7 +179,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
boolean newMemberIsYou = roleChange.getUuid().equals(youUuid);
|
boolean newMemberIsYou = roleChange.getUuid().equals(selfUuidBytes);
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
updates.add(context.getString(R.string.MessageRecord_you_revoked_admin_privileges_from_s, describe(roleChange.getUuid())));
|
updates.add(context.getString(R.string.MessageRecord_you_revoked_admin_privileges_from_s, describe(roleChange.getUuid())));
|
||||||
} else {
|
} else {
|
||||||
|
@ -159,11 +194,11 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
int notYouInviteCount = 0;
|
int notYouInviteCount = 0;
|
||||||
|
|
||||||
for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) {
|
for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) {
|
||||||
boolean newMemberIsYou = invitee.getUuid().equals(youUuid);
|
boolean newMemberIsYou = invitee.getUuid().equals(selfUuidBytes);
|
||||||
|
|
||||||
if (newMemberIsYou) {
|
if (newMemberIsYou) {
|
||||||
updates.add(context.getString(R.string.MessageRecord_s_invited_you_to_the_group, describe(change.getEditor())));
|
updates.add(context.getString(R.string.MessageRecord_s_invited_you_to_the_group, describe(change.getEditor())));
|
||||||
|
@ -182,7 +217,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
int notDeclineCount = 0;
|
int notDeclineCount = 0;
|
||||||
|
|
||||||
for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) {
|
for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) {
|
||||||
|
@ -208,11 +243,11 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describePromotePending(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describePromotePending(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
|
for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
|
||||||
ByteString uuid = newMember.getUuid();
|
ByteString uuid = newMember.getUuid();
|
||||||
boolean newMemberIsYou = uuid.equals(youUuid);
|
boolean newMemberIsYou = uuid.equals(selfUuidBytes);
|
||||||
|
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
if (newMemberIsYou) {
|
if (newMemberIsYou) {
|
||||||
|
@ -235,7 +270,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeNewTitle(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeNewTitle(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
if (change.hasNewTitle()) {
|
if (change.hasNewTitle()) {
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
|
@ -247,7 +282,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
if (change.hasNewAvatar()) {
|
if (change.hasNewAvatar()) {
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
|
@ -259,7 +294,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeNewTimer(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeNewTimer(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
if (change.hasNewTimer()) {
|
if (change.hasNewTimer()) {
|
||||||
String time = ExpirationUtil.getExpirationDisplayValue(context, change.getNewTimer().getDuration());
|
String time = ExpirationUtil.getExpirationDisplayValue(context, change.getNewTimer().getDuration());
|
||||||
|
@ -272,7 +307,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeNewAttributeAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeNewAttributeAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
if (change.getNewAttributeAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
if (change.getNewAttributeAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
||||||
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewAttributeAccess());
|
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewAttributeAccess());
|
||||||
|
@ -285,7 +320,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
private void describeNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
|
||||||
boolean editorIsYou = change.getEditor().equals(youUuid);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
|
|
||||||
if (change.getNewMemberAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
if (change.getNewMemberAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
||||||
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewMemberAccess());
|
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewMemberAccess());
|
||||||
|
|
|
@ -17,22 +17,31 @@
|
||||||
package org.thoughtcrime.securesms.database.model;
|
package org.thoughtcrime.securesms.database.model;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.RelativeSizeSpan;
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for message record models that are displayed in
|
* The base class for message record models that are displayed in
|
||||||
|
@ -44,6 +53,8 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public abstract class MessageRecord extends DisplayRecord {
|
public abstract class MessageRecord extends DisplayRecord {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(MessageRecord.class);
|
||||||
|
|
||||||
private final Recipient individualRecipient;
|
private final Recipient individualRecipient;
|
||||||
private final int recipientDeviceId;
|
private final int recipientDeviceId;
|
||||||
private final long id;
|
private final long id;
|
||||||
|
@ -96,7 +107,9 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpannableString getDisplayBody(@NonNull Context context) {
|
public SpannableString getDisplayBody(@NonNull Context context) {
|
||||||
if (isGroupUpdate() && isOutgoing()) {
|
if (isGroupUpdate() && isGroupV2()) {
|
||||||
|
return new SpannableString(getGv2Description(context));
|
||||||
|
} else if (isGroupUpdate() && isOutgoing()) {
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group));
|
return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group));
|
||||||
} else if (isGroupUpdate()) {
|
} else if (isGroupUpdate()) {
|
||||||
return new SpannableString(GroupUtil.getDescription(context, getBody(), false).toString(getIndividualRecipient()));
|
return new SpannableString(GroupUtil.getDescription(context, getBody(), false).toString(getIndividualRecipient()));
|
||||||
|
@ -134,6 +147,56 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
return new SpannableString(getBody());
|
return new SpannableString(getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @NonNull String getGv2Description(@NonNull Context context) {
|
||||||
|
if (!isGroupUpdate() || !isGroupV2()) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ShortStringDescriptionStrategy descriptionStrategy = new ShortStringDescriptionStrategy(context);
|
||||||
|
byte[] decoded = Base64.decode(getBody());
|
||||||
|
DecryptedGroupV2Context decryptedGroupV2Context = DecryptedGroupV2Context.parseFrom(decoded);
|
||||||
|
GroupsV2UpdateMessageProducer updateMessageProducer = new GroupsV2UpdateMessageProducer(context, descriptionStrategy, Recipient.self().getUuid().get());
|
||||||
|
|
||||||
|
if (decryptedGroupV2Context.hasChange() && decryptedGroupV2Context.getGroupState().getVersion() > 0) {
|
||||||
|
DecryptedGroupChange change = decryptedGroupV2Context.getChange();
|
||||||
|
List<String> strings = updateMessageProducer.describeChange(change);
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < strings.size(); i++) {
|
||||||
|
if (i > 0) result.append('\n');
|
||||||
|
result.append(strings.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
} else {
|
||||||
|
return updateMessageProducer.describeNewGroup(decryptedGroupV2Context.getGroupState());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "GV2 Message update detail could not be read", e);
|
||||||
|
return context.getString(R.string.MessageRecord_group_updated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a UUID by it's corresponding recipient's {@link Recipient#toShortString}.
|
||||||
|
*/
|
||||||
|
private static class ShortStringDescriptionStrategy implements GroupsV2UpdateMessageProducer.DescribeMemberStrategy {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
ShortStringDescriptionStrategy(@NonNull Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String describe(@NonNull UUID uuid) {
|
||||||
|
if (UuidUtil.UNKNOWN_UUID.equals(uuid)) {
|
||||||
|
return context.getString(R.string.MessageRecord_unknown);
|
||||||
|
}
|
||||||
|
return Recipient.resolved(RecipientId.from(uuid, null)).toShortString(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.signal.storageservice.protos.groups.AccessControl;
|
import org.signal.storageservice.protos.groups.AccessControl;
|
||||||
import org.signal.storageservice.protos.groups.Member;
|
import org.signal.storageservice.protos.groups.Member;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
|
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
|
||||||
|
@ -491,6 +492,84 @@ public final class GroupsV2UpdateMessageProducerTest {
|
||||||
"Alice changed who can edit group membership to \"All members\".")));
|
"Alice changed who can edit group membership to \"All members\".")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group state without a change record
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void you_created_a_group() {
|
||||||
|
DecryptedGroup group = newGroupBy(you, 0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(producer.describeNewGroup(group), is("You created the group."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void alice_created_a_group() {
|
||||||
|
DecryptedGroup group = newGroupBy(alice, 0)
|
||||||
|
.member(you)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(producer.describeNewGroup(group), is("Alice added you to the group."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void alice_created_a_group_above_zero() {
|
||||||
|
DecryptedGroup group = newGroupBy(alice, 1)
|
||||||
|
.member(you)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(producer.describeNewGroup(group), is("You joined the group."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void you_were_invited_to_a_group() {
|
||||||
|
DecryptedGroup group = newGroupBy(alice, 0)
|
||||||
|
.invite(bob, you)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(producer.describeNewGroup(group), is("Bob invited you to the group."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void describe_a_group_you_are_not_in() {
|
||||||
|
DecryptedGroup group = newGroupBy(alice, 1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(producer.describeNewGroup(group), is("Group updated."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupStateBuilder newGroupBy(UUID foundingMember, int revision) {
|
||||||
|
return new GroupStateBuilder(foundingMember, revision);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GroupStateBuilder {
|
||||||
|
|
||||||
|
private final DecryptedGroup.Builder builder;
|
||||||
|
|
||||||
|
GroupStateBuilder(@NonNull UUID foundingMember, int version) {
|
||||||
|
builder = DecryptedGroup.newBuilder()
|
||||||
|
.setVersion(version)
|
||||||
|
.addMembers(DecryptedMember.newBuilder()
|
||||||
|
.setUuid(UuidUtil.toByteString(foundingMember)));
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupStateBuilder invite(@NonNull UUID inviter, @NonNull UUID invitee) {
|
||||||
|
builder.addPendingMembers(DecryptedPendingMember.newBuilder()
|
||||||
|
.setUuid(UuidUtil.toByteString(invitee))
|
||||||
|
.setAddedByUuid(UuidUtil.toByteString(inviter)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupStateBuilder member(@NonNull UUID member) {
|
||||||
|
builder.addMembers(DecryptedMember.newBuilder()
|
||||||
|
.setUuid(UuidUtil.toByteString(member)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecryptedGroup build() {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class ChangeBuilder {
|
private static class ChangeBuilder {
|
||||||
|
|
||||||
private final DecryptedGroupChange.Builder builder;
|
private final DecryptedGroupChange.Builder builder;
|
||||||
|
|
Loading…
Add table
Reference in a new issue