Skip to content

Commit

Permalink
GV2 update message description.
Browse files Browse the repository at this point in the history
  • Loading branch information
alan-signal committed May 13, 2020
1 parent 007bd75 commit ef7e62e
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import org.signal.storageservice.protos.groups.AccessControl;
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.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
Expand All @@ -16,6 +17,8 @@
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
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 java.util.LinkedList;
Expand All @@ -26,18 +29,50 @@ final class GroupsV2UpdateMessageProducer {

@NonNull private final Context context;
@NonNull private final DescribeMemberStrategy descriptionStrategy;
@NonNull private final ByteString youUuid;
@NonNull private final UUID youUuid;
@NonNull private final ByteString youUuidBytes;

/**
* @param descriptionStrategy Strategy for member description.
*/
GroupsV2UpdateMessageProducer(@NonNull Context context,
@NonNull DescribeMemberStrategy descriptionStrategy,
@NonNull UUID you)
@NonNull UUID youUuid)
{
this.context = context;
this.descriptionStrategy = descriptionStrategy;
this.youUuid = UuidUtil.toByteString(you);
this.youUuid = youUuid;
this.youUuidBytes = UuidUtil.toByteString(youUuid);
}

/**
* 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(), youUuid);
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 (youUuidBytes.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(), youUuid).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) {
Expand Down Expand Up @@ -66,7 +101,7 @@ List<String> describeChange(@NonNull DecryptedGroupChange change) {
* 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);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

if (editorIsYou) {
updates.add(context.getString(R.string.MessageRecord_you_updated_group));
Expand All @@ -76,10 +111,10 @@ private void describeUnknownChange(@NonNull DecryptedGroupChange change, @NonNul
}

private void describeMemberAdditions(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

for (DecryptedMember member : change.getNewMembersList()) {
boolean newMemberIsYou = member.getUuid().equals(youUuid);
boolean newMemberIsYou = member.getUuid().equals(youUuidBytes);

if (editorIsYou) {
if (newMemberIsYou) {
Expand All @@ -102,10 +137,10 @@ private void describeMemberAdditions(@NonNull DecryptedGroupChange change, @NonN
}

private void describeMemberRemovals(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

for (ByteString member : change.getDeleteMembersList()) {
boolean newMemberIsYou = member.equals(youUuid);
boolean newMemberIsYou = member.equals(youUuidBytes);

if (editorIsYou) {
if (newMemberIsYou) {
Expand All @@ -128,11 +163,11 @@ private void describeMemberRemovals(@NonNull DecryptedGroupChange change, @NonNu
}

private void describeModifyMemberRoles(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

for (DecryptedModifyMemberRole roleChange : change.getModifyMemberRolesList()) {
if (roleChange.getRole() == Member.Role.ADMINISTRATOR) {
boolean newMemberIsYou = roleChange.getUuid().equals(youUuid);
boolean newMemberIsYou = roleChange.getUuid().equals(youUuidBytes);
if (editorIsYou) {
updates.add(context.getString(R.string.MessageRecord_you_made_s_an_admin, describe(roleChange.getUuid())));
} else {
Expand All @@ -144,7 +179,7 @@ private void describeModifyMemberRoles(@NonNull DecryptedGroupChange change, @No
}
}
} else {
boolean newMemberIsYou = roleChange.getUuid().equals(youUuid);
boolean newMemberIsYou = roleChange.getUuid().equals(youUuidBytes);
if (editorIsYou) {
updates.add(context.getString(R.string.MessageRecord_you_revoked_admin_privileges_from_s, describe(roleChange.getUuid())));
} else {
Expand All @@ -159,11 +194,11 @@ private void describeModifyMemberRoles(@NonNull DecryptedGroupChange change, @No
}

private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);
int notYouInviteCount = 0;

for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) {
boolean newMemberIsYou = invitee.getUuid().equals(youUuid);
boolean newMemberIsYou = invitee.getUuid().equals(youUuidBytes);

if (newMemberIsYou) {
updates.add(context.getString(R.string.MessageRecord_s_invited_you_to_the_group, describe(change.getEditor())));
Expand All @@ -182,7 +217,7 @@ private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull
}

private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);
int notDeclineCount = 0;

for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) {
Expand All @@ -208,11 +243,11 @@ private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @N
}

private void describePromotePending(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
ByteString uuid = newMember.getUuid();
boolean newMemberIsYou = uuid.equals(youUuid);
boolean newMemberIsYou = uuid.equals(youUuidBytes);

if (editorIsYou) {
if (newMemberIsYou) {
Expand All @@ -235,7 +270,7 @@ private void describePromotePending(@NonNull DecryptedGroupChange change, @NonNu
}

private void describeNewTitle(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

if (change.hasNewTitle()) {
if (editorIsYou) {
Expand All @@ -247,7 +282,7 @@ private void describeNewTitle(@NonNull DecryptedGroupChange change, @NonNull Lis
}

private void describeNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

if (change.hasNewAvatar()) {
if (editorIsYou) {
Expand All @@ -259,7 +294,7 @@ private void describeNewAvatar(@NonNull DecryptedGroupChange change, @NonNull Li
}

private void describeNewTimer(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

if (change.hasNewTimer()) {
String time = ExpirationUtil.getExpirationDisplayValue(context, change.getNewTimer().getDuration());
Expand All @@ -272,7 +307,7 @@ private void describeNewTimer(@NonNull DecryptedGroupChange change, @NonNull Lis
}

private void describeNewAttributeAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

if (change.getNewAttributeAccess() != AccessControl.AccessRequired.UNKNOWN) {
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewAttributeAccess());
Expand All @@ -285,7 +320,7 @@ private void describeNewAttributeAccess(@NonNull DecryptedGroupChange change, @N
}

private void describeNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
boolean editorIsYou = change.getEditor().equals(youUuidBytes);

if (change.getNewMemberAccess() != AccessControl.AccessRequired.UNKNOWN) {
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewMemberAccess());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,31 @@
package org.thoughtcrime.securesms.database.model;

import android.content.Context;
import androidx.annotation.NonNull;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
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.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
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.util.Base64;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.GroupUtil;

import java.io.IOException;
import java.util.List;
import java.util.UUID;

/**
* The base class for message record models that are displayed in
Expand All @@ -44,6 +53,8 @@
*/
public abstract class MessageRecord extends DisplayRecord {

private static final String TAG = Log.tag(MessageRecord.class);

private final Recipient individualRecipient;
private final int recipientDeviceId;
private final long id;
Expand Down Expand Up @@ -96,7 +107,9 @@ public boolean isLegacyMessage() {

@Override
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));
} else if (isGroupUpdate()) {
return new SpannableString(GroupUtil.getDescription(context, getBody(), false).toString(getIndividualRecipient()));
Expand Down Expand Up @@ -134,6 +147,54 @@ public SpannableString getDisplayBody(@NonNull Context context) {
return new SpannableString(getBody());
}

private @NonNull String getGv2Description(@NonNull Context context) {
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;
private final RecipientDatabase recipientDatabase;

ShortStringDescriptionStrategy(@NonNull Context context) {
this.context = context;
this.recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
}

@Override
public @NonNull String describe(@NonNull UUID uuid) {
return recipientDatabase.getByUuid(uuid)
.transform(rc -> Recipient.resolved(rc).toShortString(context))
.or(context.getString(R.string.MessageRecord_unknown));
}
}

public long getId() {
return id;
}
Expand Down

0 comments on commit ef7e62e

Please sign in to comment.