mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-08 22:28:33 +00:00
GV2 message contexts.
This commit is contained in:
parent
f099c3591c
commit
b8df90531f
@ -103,7 +103,7 @@ public class ConversationUpdateItem extends LinearLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.messageRecord != null && messageRecord.isGroupAction()) {
|
if (this.messageRecord != null && messageRecord.isGroupAction()) {
|
||||||
GroupUtil.getDescription(getContext(), messageRecord.getBody()).removeObserver(this);
|
GroupUtil.getDescription(getContext(), messageRecord.getBody(), messageRecord.isGroupV2()).removeObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageRecord = messageRecord;
|
this.messageRecord = messageRecord;
|
||||||
@ -113,7 +113,7 @@ public class ConversationUpdateItem extends LinearLayout
|
|||||||
this.sender.observeForever(this);
|
this.sender.observeForever(this);
|
||||||
|
|
||||||
if (this.messageRecord != null && messageRecord.isGroupAction()) {
|
if (this.messageRecord != null && messageRecord.isGroupAction()) {
|
||||||
GroupUtil.getDescription(getContext(), messageRecord.getBody()).addObserver(this);
|
GroupUtil.getDescription(getContext(), messageRecord.getBody(), messageRecord.isGroupV2()).addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
present(messageRecord);
|
present(messageRecord);
|
||||||
@ -236,7 +236,7 @@ public class ConversationUpdateItem extends LinearLayout
|
|||||||
sender.removeForeverObserver(this);
|
sender.removeForeverObserver(this);
|
||||||
}
|
}
|
||||||
if (this.messageRecord != null && messageRecord.isGroupAction()) {
|
if (this.messageRecord != null && messageRecord.isGroupAction()) {
|
||||||
GroupUtil.getDescription(getContext(), messageRecord.getBody()).removeObserver(this);
|
GroupUtil.getDescription(getContext(), messageRecord.getBody(), messageRecord.isGroupV2()).removeObserver(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1137,7 +1137,11 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
MessageGroupContext.GroupV2Properties groupV2Properties = outgoingGroupUpdateMessage.requireGroupV2Properties();
|
MessageGroupContext.GroupV2Properties groupV2Properties = outgoingGroupUpdateMessage.requireGroupV2Properties();
|
||||||
members.addAll(Stream.of(groupV2Properties.getActiveMembers()).map(recipientDatabase::getOrInsertFromUuid).toList());
|
members.addAll(Stream.of(groupV2Properties.getActiveMembers()).map(recipientDatabase::getOrInsertFromUuid).toList());
|
||||||
if (groupV2Properties.isUpdate()) {
|
if (groupV2Properties.isUpdate()) {
|
||||||
members.addAll(Stream.of(groupV2Properties.getPendingMembers()).map(recipientDatabase::getOrInsertFromUuid).toList());
|
members.addAll(Stream.concat(Stream.of(groupV2Properties.getPendingMembers()),
|
||||||
|
Stream.of(groupV2Properties.getRemovedMembers()))
|
||||||
|
.distinct()
|
||||||
|
.map(recipientDatabase::getOrInsertFromUuid)
|
||||||
|
.toList());
|
||||||
}
|
}
|
||||||
members.remove(Recipient.self().getId());
|
members.remove(Recipient.self().getId());
|
||||||
} else {
|
} else {
|
||||||
|
@ -616,9 +616,14 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
} else if (message.isSecureMessage()) {
|
} else if (message.isSecureMessage()) {
|
||||||
type |= Types.SECURE_MESSAGE_BIT;
|
type |= Types.SECURE_MESSAGE_BIT;
|
||||||
} else if (message.isGroup()) {
|
} else if (message.isGroup()) {
|
||||||
|
IncomingGroupUpdateMessage incomingGroupUpdateMessage = (IncomingGroupUpdateMessage) message;
|
||||||
|
|
||||||
type |= Types.SECURE_MESSAGE_BIT;
|
type |= Types.SECURE_MESSAGE_BIT;
|
||||||
if (((IncomingGroupUpdateMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
|
|
||||||
else if (((IncomingGroupUpdateMessage)message).isQuit()) type |= Types.GROUP_QUIT_BIT;
|
if (incomingGroupUpdateMessage.isGroupV2()) type |= Types.GROUP_V2_BIT | Types.GROUP_UPDATE_BIT;
|
||||||
|
else if (incomingGroupUpdateMessage.isUpdate()) type |= Types.GROUP_UPDATE_BIT;
|
||||||
|
else if (incomingGroupUpdateMessage.isQuit()) type |= Types.GROUP_QUIT_BIT;
|
||||||
|
|
||||||
} else if (message.isEndSession()) {
|
} else if (message.isEndSession()) {
|
||||||
type |= Types.SECURE_MESSAGE_BIT;
|
type |= Types.SECURE_MESSAGE_BIT;
|
||||||
type |= Types.END_SESSION_BIT;
|
type |= Types.END_SESSION_BIT;
|
||||||
|
@ -17,9 +17,10 @@
|
|||||||
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.SpannableString;
|
import android.text.SpannableString;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
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.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
@ -111,6 +112,10 @@ public abstract class DisplayRecord {
|
|||||||
return SmsDatabase.Types.isGroupUpdate(type);
|
return SmsDatabase.Types.isGroupUpdate(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGroupV2() {
|
||||||
|
return SmsDatabase.Types.isGroupV2(type);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isGroupQuit() {
|
public boolean isGroupQuit() {
|
||||||
return SmsDatabase.Types.isGroupQuit(type);
|
return SmsDatabase.Types.isGroupQuit(type);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||||||
if (isGroupUpdate() && isOutgoing()) {
|
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()).toString(getIndividualRecipient()));
|
return new SpannableString(GroupUtil.getDescription(context, getBody(), false).toString(getIndividualRecipient()));
|
||||||
} else if (isGroupQuit() && isOutgoing()) {
|
} else if (isGroupQuit() && isOutgoing()) {
|
||||||
return new SpannableString(context.getString(R.string.MessageRecord_left_group));
|
return new SpannableString(context.getString(R.string.MessageRecord_left_group));
|
||||||
} else if (isGroupQuit()) {
|
} else if (isGroupQuit()) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.groups;
|
package org.thoughtcrime.securesms.groups;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -30,8 +29,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||||
@ -154,7 +151,7 @@ final class GroupManagerV1 {
|
|||||||
static boolean leaveGroup(@NonNull Context context, @NonNull GroupId.V1 groupId) {
|
static boolean leaveGroup(@NonNull Context context, @NonNull GroupId.V1 groupId) {
|
||||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||||
Optional<OutgoingGroupUpdateMessage> leaveMessage = GroupUtil.createGroupLeaveMessage(context, groupRecipient);
|
Optional<OutgoingGroupUpdateMessage> leaveMessage = createGroupLeaveMessage(context, groupId, groupRecipient);
|
||||||
|
|
||||||
if (threadId != -1 && leaveMessage.isPresent()) {
|
if (threadId != -1 && leaveMessage.isPresent()) {
|
||||||
try {
|
try {
|
||||||
@ -180,7 +177,7 @@ final class GroupManagerV1 {
|
|||||||
if (DatabaseFactory.getGroupDatabase(context).isActive(groupId)) {
|
if (DatabaseFactory.getGroupDatabase(context).isActive(groupId)) {
|
||||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||||
Optional<OutgoingGroupUpdateMessage> leaveMessage = GroupUtil.createGroupLeaveMessage(context, groupRecipient);
|
Optional<OutgoingGroupUpdateMessage> leaveMessage = createGroupLeaveMessage(context, groupId, groupRecipient);
|
||||||
|
|
||||||
if (threadId != -1 && leaveMessage.isPresent()) {
|
if (threadId != -1 && leaveMessage.isPresent()) {
|
||||||
ApplicationDependencies.getJobManager().add(LeaveGroupJob.create(groupRecipient));
|
ApplicationDependencies.getJobManager().add(LeaveGroupJob.create(groupRecipient));
|
||||||
@ -210,4 +207,32 @@ final class GroupManagerV1 {
|
|||||||
OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(recipient, System.currentTimeMillis(), expirationTime * 1000L);
|
OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(recipient, System.currentTimeMillis(), expirationTime * 1000L);
|
||||||
MessageSender.send(context, outgoingMessage, threadId, false, null);
|
MessageSender.send(context, outgoingMessage, threadId, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private static Optional<OutgoingGroupUpdateMessage> createGroupLeaveMessage(@NonNull Context context,
|
||||||
|
@NonNull GroupId.V1 groupId,
|
||||||
|
@NonNull Recipient groupRecipient)
|
||||||
|
{
|
||||||
|
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||||
|
|
||||||
|
if (!groupDatabase.isActive(groupId)) {
|
||||||
|
Log.w(TAG, "Group has already been left.");
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupContext groupContext = GroupContext.newBuilder()
|
||||||
|
.setId(ByteString.copyFrom(groupId.getDecodedId()))
|
||||||
|
.setType(GroupContext.Type.QUIT)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return Optional.of(new OutgoingGroupUpdateMessage(groupRecipient,
|
||||||
|
groupContext,
|
||||||
|
null,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
Collections.emptyList(),
|
||||||
|
Collections.emptyList()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,16 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||||
import org.signal.zkgroup.VerificationFailedException;
|
import org.signal.zkgroup.VerificationFailedException;
|
||||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||||
import org.signal.zkgroup.groups.GroupSecretParams;
|
import org.signal.zkgroup.groups.GroupSecretParams;
|
||||||
|
import org.signal.zkgroup.util.UUIDUtil;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
@ -29,10 +32,14 @@ import org.thoughtcrime.securesms.mms.MmsException;
|
|||||||
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage;
|
||||||
|
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupHistoryEntry;
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupHistoryEntry;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
|
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
|
||||||
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException;
|
import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -132,7 +139,14 @@ public final class GroupsV2StateProcessor {
|
|||||||
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
|
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalGroupState inputGroupState = queryServer();
|
GlobalGroupState inputGroupState;
|
||||||
|
try {
|
||||||
|
inputGroupState = queryServer();
|
||||||
|
} catch (GroupNotAMemberException e) {
|
||||||
|
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message");
|
||||||
|
insertGroupLeave();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision);
|
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision);
|
||||||
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
|
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
|
||||||
|
|
||||||
@ -152,6 +166,49 @@ public final class GroupsV2StateProcessor {
|
|||||||
return new GroupUpdateResult(GroupState.GROUP_UPDATED, newLocalState);
|
return new GroupUpdateResult(GroupState.GROUP_UPDATED, newLocalState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void insertGroupLeave() {
|
||||||
|
if (!groupDatabase.isActive(groupId)) {
|
||||||
|
Log.w(TAG, "Group has already been left.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||||
|
UUID selfUuid = Recipient.self().getUuid().get();
|
||||||
|
DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId)
|
||||||
|
.requireV2GroupProperties()
|
||||||
|
.getDecryptedGroup();
|
||||||
|
|
||||||
|
DecryptedGroup simulatedGroupState = DecryptedGroupUtil.removeMember(decryptedGroup, selfUuid, decryptedGroup.getVersion() + 1);
|
||||||
|
DecryptedGroupChange simulatedGroupChange = DecryptedGroupChange.newBuilder()
|
||||||
|
.setEditor(UuidUtil.toByteString(UuidUtil.UNKNOWN_UUID))
|
||||||
|
.setVersion(simulatedGroupState.getVersion())
|
||||||
|
.addDeleteMembers(UuidUtil.toByteString(selfUuid))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, simulatedGroupState, simulatedGroupChange);
|
||||||
|
OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient,
|
||||||
|
decryptedGroupV2Context,
|
||||||
|
null,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
Collections.emptyList(),
|
||||||
|
Collections.emptyList());
|
||||||
|
|
||||||
|
try {
|
||||||
|
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||||
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||||
|
long id = mmsDatabase.insertMessageOutbox(leaveMessage, threadId, false, null);
|
||||||
|
mmsDatabase.markAsSent(id, true);
|
||||||
|
} catch (MmsException e) {
|
||||||
|
Log.w(TAG, "Failed to insert leave message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupDatabase.setActive(groupId, false);
|
||||||
|
groupDatabase.remove(groupId, Recipient.self().getId());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true iff group exists locally and is at least the specified revision.
|
* @return true iff group exists locally and is at least the specified revision.
|
||||||
*/
|
*/
|
||||||
@ -252,17 +309,29 @@ public final class GroupsV2StateProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessage(@NonNull DecryptedGroupV2Context decryptedGroupV2Context, long timestamp) {
|
private void storeMessage(@NonNull DecryptedGroupV2Context decryptedGroupV2Context, long timestamp) {
|
||||||
try {
|
UUID editor = DecryptedGroupUtil.editorUuid(decryptedGroupV2Context.getChange());
|
||||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
boolean outgoing = Recipient.self().getUuid().get().equals(editor);
|
||||||
RecipientId recipientId = recipientDatabase.getOrInsertFromGroupId(groupId);
|
|
||||||
Recipient recipient = Recipient.resolved(recipientId);
|
|
||||||
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(recipient, decryptedGroupV2Context, null, timestamp, 0, false, null, Collections.emptyList(), Collections.emptyList());
|
|
||||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
|
||||||
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
|
|
||||||
|
|
||||||
mmsDatabase.markAsSent(messageId, true);
|
if (outgoing) {
|
||||||
} catch (MmsException e) {
|
try {
|
||||||
Log.w(TAG, e);
|
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||||
|
RecipientId recipientId = recipientDatabase.getOrInsertFromGroupId(groupId);
|
||||||
|
Recipient recipient = Recipient.resolved(recipientId);
|
||||||
|
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(recipient, decryptedGroupV2Context, null, timestamp, 0, false, null, Collections.emptyList(), Collections.emptyList());
|
||||||
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||||
|
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
|
||||||
|
|
||||||
|
mmsDatabase.markAsSent(messageId, true);
|
||||||
|
} catch (MmsException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||||
|
RecipientId sender = Recipient.externalPush(context, editor, null).getId();
|
||||||
|
IncomingTextMessage incoming = new IncomingTextMessage(sender, -1, timestamp, timestamp, "", Optional.of(groupId), 0, false);
|
||||||
|
IncomingGroupUpdateMessage groupMessage = new IncomingGroupUpdateMessage(incoming, decryptedGroupV2Context);
|
||||||
|
|
||||||
|
smsDatabase.insertMessageInbox(groupMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,6 @@ import java.io.IOException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class PushGroupSendJob extends PushSendJob {
|
public class PushGroupSendJob extends PushSendJob {
|
||||||
@ -167,10 +166,9 @@ public class PushGroupSendJob extends PushSendJob {
|
|||||||
|
|
||||||
List<RecipientId> target;
|
List<RecipientId> target;
|
||||||
|
|
||||||
if (filterRecipient != null) target = Collections.singletonList(Recipient.resolved(filterRecipient).getId());
|
if (filterRecipient != null) target = Collections.singletonList(Recipient.resolved(filterRecipient).getId());
|
||||||
else if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).toList();
|
else if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).toList();
|
||||||
else if (groupRecipient.isPushV2Group() && message instanceof OutgoingGroupUpdateMessage) target = getGroupMessageV2Recipients((OutgoingGroupUpdateMessage) message);
|
else target = getGroupMessageRecipients(groupRecipient.requireGroupId(), messageId);
|
||||||
else target = getGroupMessageRecipients(groupRecipient.requireGroupId(), messageId);
|
|
||||||
|
|
||||||
List<SendMessageResult> results = deliver(message, groupRecipient, target);
|
List<SendMessageResult> results = deliver(message, groupRecipient, target);
|
||||||
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Recipient.externalPush(context, result.getAddress()).getId())).toList();
|
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Recipient.externalPush(context, result.getAddress()).getId())).toList();
|
||||||
@ -330,24 +328,20 @@ public class PushGroupSendJob extends PushSendJob {
|
|||||||
private @NonNull List<RecipientId> getGroupMessageRecipients(@NonNull GroupId groupId, long messageId) {
|
private @NonNull List<RecipientId> getGroupMessageRecipients(@NonNull GroupId groupId, long messageId) {
|
||||||
List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId);
|
List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId);
|
||||||
|
|
||||||
if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getRecipientId).toList();
|
if (!destinations.isEmpty()) {
|
||||||
|
return Stream.of(destinations).map(GroupReceiptInfo::getRecipientId).toList();
|
||||||
|
}
|
||||||
|
|
||||||
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
|
List<RecipientId> members = Stream.of(DatabaseFactory.getGroupDatabase(context)
|
||||||
return Stream.of(members).map(Recipient::getId).toList();
|
.getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF))
|
||||||
}
|
.map(Recipient::getId)
|
||||||
|
.toList();
|
||||||
|
|
||||||
private @NonNull List<RecipientId> getGroupMessageV2Recipients(@NonNull OutgoingGroupUpdateMessage message) {
|
if (members.size() > 0) {
|
||||||
UUID selfUUId = Recipient.self().getUuid().get();
|
Log.w(TAG, "No destinations found for group message " + groupId + " using current group membership");
|
||||||
MessageGroupContext.GroupV2Properties groupV2Properties = message.requireGroupV2Properties();
|
}
|
||||||
boolean includePending = groupV2Properties.isUpdate();
|
|
||||||
|
return members;
|
||||||
return Stream.concat(Stream.of(groupV2Properties.getActiveMembers()),
|
|
||||||
includePending ? Stream.of(groupV2Properties.getPendingMembers()) : Stream.empty())
|
|
||||||
.filterNot(selfUUId::equals)
|
|
||||||
.distinct()
|
|
||||||
.map(uuid -> Recipient.externalPush(context, uuid, null))
|
|
||||||
.map(Recipient::getId)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Factory implements Job.Factory<PushGroupSendJob> {
|
public static class Factory implements Job.Factory<PushGroupSendJob> {
|
||||||
|
@ -1,19 +1,24 @@
|
|||||||
package org.thoughtcrime.securesms.mms;
|
package org.thoughtcrime.securesms.mms;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
|
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||||
import org.signal.zkgroup.util.UUIDUtil;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||||
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -22,9 +27,10 @@ import java.util.UUID;
|
|||||||
*/
|
*/
|
||||||
public final class MessageGroupContext {
|
public final class MessageGroupContext {
|
||||||
|
|
||||||
private final String encodedGroupContext;
|
@NonNull private final String encodedGroupContext;
|
||||||
private final GroupV1Properties groupV1;
|
@NonNull private final GroupProperties group;
|
||||||
private final GroupV2Properties groupV2;
|
@Nullable private final GroupV1Properties groupV1;
|
||||||
|
@Nullable private final GroupV2Properties groupV2;
|
||||||
|
|
||||||
public MessageGroupContext(@NonNull String encodedGroupContext, boolean v2)
|
public MessageGroupContext(@NonNull String encodedGroupContext, boolean v2)
|
||||||
throws IOException
|
throws IOException
|
||||||
@ -33,9 +39,11 @@ public final class MessageGroupContext {
|
|||||||
if (v2) {
|
if (v2) {
|
||||||
this.groupV1 = null;
|
this.groupV1 = null;
|
||||||
this.groupV2 = new GroupV2Properties(DecryptedGroupV2Context.parseFrom(Base64.decode(encodedGroupContext)));
|
this.groupV2 = new GroupV2Properties(DecryptedGroupV2Context.parseFrom(Base64.decode(encodedGroupContext)));
|
||||||
|
this.group = groupV2;
|
||||||
} else {
|
} else {
|
||||||
this.groupV1 = new GroupV1Properties(GroupContext.parseFrom(Base64.decode(encodedGroupContext)));
|
this.groupV1 = new GroupV1Properties(GroupContext.parseFrom(Base64.decode(encodedGroupContext)));
|
||||||
this.groupV2 = null;
|
this.groupV2 = null;
|
||||||
|
this.group = groupV1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,12 +51,14 @@ public final class MessageGroupContext {
|
|||||||
this.encodedGroupContext = Base64.encodeBytes(group.toByteArray());
|
this.encodedGroupContext = Base64.encodeBytes(group.toByteArray());
|
||||||
this.groupV1 = new GroupV1Properties(group);
|
this.groupV1 = new GroupV1Properties(group);
|
||||||
this.groupV2 = null;
|
this.groupV2 = null;
|
||||||
|
this.group = groupV1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageGroupContext(@NonNull DecryptedGroupV2Context group) {
|
public MessageGroupContext(@NonNull DecryptedGroupV2Context group) {
|
||||||
this.encodedGroupContext = Base64.encodeBytes(group.toByteArray());
|
this.encodedGroupContext = Base64.encodeBytes(group.toByteArray());
|
||||||
this.groupV1 = null;
|
this.groupV1 = null;
|
||||||
this.groupV2 = new GroupV2Properties(group);
|
this.groupV2 = new GroupV2Properties(group);
|
||||||
|
this.group = groupV2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull GroupV1Properties requireGroupV1Properties() {
|
public @NonNull GroupV1Properties requireGroupV1Properties() {
|
||||||
@ -73,7 +83,20 @@ public final class MessageGroupContext {
|
|||||||
return encodedGroupContext;
|
return encodedGroupContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GroupV1Properties {
|
public String getName() {
|
||||||
|
return group.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RecipientId> getMembersListExcludingSelf() {
|
||||||
|
return group.getMembersListExcludingSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupProperties {
|
||||||
|
@NonNull String getName();
|
||||||
|
@NonNull List<RecipientId> getMembersListExcludingSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GroupV1Properties implements GroupProperties {
|
||||||
|
|
||||||
private final GroupContext groupContext;
|
private final GroupContext groupContext;
|
||||||
|
|
||||||
@ -92,9 +115,32 @@ public final class MessageGroupContext {
|
|||||||
public boolean isUpdate() {
|
public boolean isUpdate() {
|
||||||
return groupContext.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
|
return groupContext.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getName() {
|
||||||
|
return groupContext.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull List<RecipientId> getMembersListExcludingSelf() {
|
||||||
|
List<GroupContext.Member> membersList = groupContext.getMembersList();
|
||||||
|
if (membersList.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
LinkedList<RecipientId> members = new LinkedList<>();
|
||||||
|
|
||||||
|
for (GroupContext.Member member : membersList) {
|
||||||
|
RecipientId recipient = RecipientId.from(UuidUtil.parseOrNull(member.getUuid()), member.getE164());
|
||||||
|
if (!Recipient.self().getId().equals(recipient)) {
|
||||||
|
members.add(recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GroupV2Properties {
|
public static class GroupV2Properties implements GroupProperties {
|
||||||
|
|
||||||
private final DecryptedGroupV2Context decryptedGroupV2Context;
|
private final DecryptedGroupV2Context decryptedGroupV2Context;
|
||||||
private final GroupContextV2 groupContext;
|
private final GroupContextV2 groupContext;
|
||||||
@ -126,9 +172,32 @@ public final class MessageGroupContext {
|
|||||||
return DecryptedGroupUtil.pendingToUuidList(decryptedGroupV2Context.getGroupState().getPendingMembersList());
|
return DecryptedGroupUtil.pendingToUuidList(decryptedGroupV2Context.getGroupState().getPendingMembersList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull List<UUID> getRemovedMembers() {
|
||||||
|
return DecryptedGroupUtil.removedMembersUuidList(decryptedGroupV2Context.getChange());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUpdate() {
|
public boolean isUpdate() {
|
||||||
// The group context is only stored on update messages.
|
// The group context is only stored on update messages.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getName() {
|
||||||
|
return decryptedGroupV2Context.getGroupState().getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull List<RecipientId> getMembersListExcludingSelf() {
|
||||||
|
List<RecipientId> members = new ArrayList<>(decryptedGroupV2Context.getGroupState().getMembersCount());
|
||||||
|
|
||||||
|
for (DecryptedMember member : decryptedGroupV2Context.getGroupState().getMembersList()) {
|
||||||
|
RecipientId recipient = RecipientId.from(UuidUtil.fromByteString(member.getUuid()), null);
|
||||||
|
if (!Recipient.self().getId().equals(recipient)) {
|
||||||
|
members.add(recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return members;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,13 +42,11 @@ final class RecipientDialogRepository {
|
|||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull RecipientId getRecipientId() {
|
||||||
RecipientId getRecipientId() {
|
|
||||||
return recipientId;
|
return recipientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable GroupId getGroupId() {
|
||||||
GroupId getGroupId() {
|
|
||||||
return groupId;
|
return groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
package org.thoughtcrime.securesms.sms;
|
package org.thoughtcrime.securesms.sms;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||||
|
import org.thoughtcrime.securesms.mms.MessageGroupContext;
|
||||||
|
|
||||||
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||||
|
|
||||||
public final class IncomingGroupUpdateMessage extends IncomingTextMessage {
|
public final class IncomingGroupUpdateMessage extends IncomingTextMessage {
|
||||||
|
|
||||||
private final GroupContext groupContext;
|
private final MessageGroupContext groupContext;
|
||||||
|
|
||||||
public IncomingGroupUpdateMessage(IncomingTextMessage base, GroupContext groupContext, String body) {
|
public IncomingGroupUpdateMessage(IncomingTextMessage base, GroupContext groupContext, String body) {
|
||||||
super(base, body);
|
this(base, new MessageGroupContext(groupContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingGroupUpdateMessage(IncomingTextMessage base, DecryptedGroupV2Context groupV2Context) {
|
||||||
|
this(base, new MessageGroupContext(groupV2Context));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingGroupUpdateMessage(IncomingTextMessage base, MessageGroupContext groupContext) {
|
||||||
|
super(base, groupContext.getEncodedGroupContext());
|
||||||
this.groupContext = groupContext;
|
this.groupContext = groupContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,11 +28,15 @@ public final class IncomingGroupUpdateMessage extends IncomingTextMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpdate() {
|
public boolean isUpdate() {
|
||||||
return groupContext.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
|
return groupContext.isV2Group() || groupContext.requireGroupV1Properties().isUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGroupV2() {
|
||||||
|
return groupContext.isV2Group();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isQuit() {
|
public boolean isQuit() {
|
||||||
return groupContext.getType().getNumber() == GroupContext.Type.QUIT_VALUE;
|
return !groupContext.isV2Group() && groupContext.requireGroupV1Properties().isQuit();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,13 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.groups.BadGroupIdException;
|
import org.thoughtcrime.securesms.groups.BadGroupIdException;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
|
import org.thoughtcrime.securesms.mms.MessageGroupContext;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
@ -23,15 +21,10 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
|||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
|
||||||
|
|
||||||
public final class GroupUtil {
|
public final class GroupUtil {
|
||||||
|
|
||||||
private GroupUtil() {
|
private GroupUtil() {
|
||||||
@ -74,34 +67,13 @@ public final class GroupUtil {
|
|||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
public static @NonNull GroupDescription getDescription(@NonNull Context context, @Nullable String encodedGroup, boolean isV2) {
|
||||||
public static Optional<OutgoingGroupUpdateMessage> createGroupLeaveMessage(@NonNull Context context, @NonNull Recipient groupRecipient) {
|
|
||||||
GroupId encodedGroupId = groupRecipient.requireGroupId();
|
|
||||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
|
||||||
|
|
||||||
if (!groupDatabase.isActive(encodedGroupId)) {
|
|
||||||
Log.w(TAG, "Group has already been left.");
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteString decodedGroupId = ByteString.copyFrom(encodedGroupId.getDecodedId());
|
|
||||||
|
|
||||||
GroupContext groupContext = GroupContext.newBuilder()
|
|
||||||
.setId(decodedGroupId)
|
|
||||||
.setType(GroupContext.Type.QUIT)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return Optional.of(new OutgoingGroupUpdateMessage(groupRecipient, groupContext, null, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static @NonNull GroupDescription getDescription(@NonNull Context context, @Nullable String encodedGroup) {
|
|
||||||
if (encodedGroup == null) {
|
if (encodedGroup == null) {
|
||||||
return new GroupDescription(context, null);
|
return new GroupDescription(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
GroupContext groupContext = GroupContext.parseFrom(Base64.decode(encodedGroup));
|
MessageGroupContext groupContext = new MessageGroupContext(encodedGroup, isV2);
|
||||||
return new GroupDescription(context, groupContext);
|
return new GroupDescription(context, groupContext);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
@ -129,25 +101,19 @@ public final class GroupUtil {
|
|||||||
|
|
||||||
public static class GroupDescription {
|
public static class GroupDescription {
|
||||||
|
|
||||||
@NonNull private final Context context;
|
@NonNull private final Context context;
|
||||||
@Nullable private final GroupContext groupContext;
|
@Nullable private final MessageGroupContext groupContext;
|
||||||
@Nullable private final List<RecipientId> members;
|
@Nullable private final List<RecipientId> members;
|
||||||
|
|
||||||
GroupDescription(@NonNull Context context, @Nullable GroupContext groupContext) {
|
GroupDescription(@NonNull Context context, @Nullable MessageGroupContext groupContext) {
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
this.groupContext = groupContext;
|
this.groupContext = groupContext;
|
||||||
|
|
||||||
if (groupContext == null || groupContext.getMembersList().isEmpty()) {
|
if (groupContext == null) {
|
||||||
this.members = null;
|
this.members = null;
|
||||||
} else {
|
} else {
|
||||||
this.members = new LinkedList<>();
|
List<RecipientId> membersList = groupContext.getMembersListExcludingSelf();
|
||||||
|
this.members = membersList.isEmpty() ? null : membersList;
|
||||||
for (GroupContext.Member member : groupContext.getMembersList()) {
|
|
||||||
RecipientId recipientId = RecipientId.from(UuidUtil.parseOrNull(member.getUuid()), member.getE164());
|
|
||||||
if (!recipientId.equals(Recipient.self().getId())) {
|
|
||||||
this.members.add(recipientId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -678,6 +678,7 @@
|
|||||||
<string name="MediaRepository_all_media">All media</string>
|
<string name="MediaRepository_all_media">All media</string>
|
||||||
|
|
||||||
<!-- MessageRecord -->
|
<!-- MessageRecord -->
|
||||||
|
<string name="MessageRecord_unknown">Unknown</string>
|
||||||
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">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.</string>
|
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">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.</string>
|
||||||
<string name="MessageRecord_left_group">You have left the group.</string>
|
<string name="MessageRecord_left_group">You have left the group.</string>
|
||||||
<string name="MessageRecord_you_updated_group">You updated the group.</string>
|
<string name="MessageRecord_you_updated_group">You updated the group.</string>
|
||||||
@ -695,6 +696,9 @@
|
|||||||
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set the disappearing message timer to %2$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 specific -->
|
||||||
|
<string name="MessageRecord_you_created_the_group">You created the group.</string>
|
||||||
|
<string name="MessageRecord_group_updated">Group updated.</string>
|
||||||
|
|
||||||
<!-- GV2 member additions -->
|
<!-- GV2 member additions -->
|
||||||
<string name="MessageRecord_you_added_s">You added %1$s.</string>
|
<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_s">%1$s added %2$s.</string>
|
||||||
|
@ -63,12 +63,26 @@ public final class DecryptedGroupUtil {
|
|||||||
return uuidList;
|
return uuidList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ArrayList<UUID> removedMembersUuidList(DecryptedGroupChange groupChange) {
|
||||||
|
ArrayList<UUID> uuidList = new ArrayList<>(groupChange.getDeleteMembersCount());
|
||||||
|
|
||||||
|
for (ByteString member : groupChange.getDeleteMembersList()) {
|
||||||
|
uuidList.add(toUuid(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuidList;
|
||||||
|
}
|
||||||
|
|
||||||
public static UUID toUuid(DecryptedMember member) {
|
public static UUID toUuid(DecryptedMember member) {
|
||||||
return UUIDUtil.deserialize(member.getUuid().toByteArray());
|
return toUuid(member.getUuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UUID toUuid(DecryptedPendingMember member) {
|
public static UUID toUuid(DecryptedPendingMember member) {
|
||||||
return UUIDUtil.deserialize(member.getUuid().toByteArray());
|
return toUuid(member.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UUID toUuid(ByteString member) {
|
||||||
|
return UUIDUtil.deserialize(member.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,6 +104,16 @@ public final class DecryptedGroupUtil {
|
|||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Optional<DecryptedMember> firstMember(Collection<DecryptedMember> members) {
|
||||||
|
Iterator<DecryptedMember> iterator = members.iterator();
|
||||||
|
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
return Optional.of(iterator.next());
|
||||||
|
} else {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Optional<DecryptedPendingMember> findPendingByUuid(Collection<DecryptedPendingMember> members, UUID uuid) {
|
public static Optional<DecryptedPendingMember> findPendingByUuid(Collection<DecryptedPendingMember> members, UUID uuid) {
|
||||||
ByteString uuidBytes = UuidUtil.toByteString(uuid);
|
ByteString uuidBytes = UuidUtil.toByteString(uuid);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user