diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 3cceeb089c..1582f36227 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -158,7 +158,6 @@ import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.MmsException; -import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage; import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage; import org.thoughtcrime.securesms.mms.QuoteId; @@ -175,6 +174,7 @@ import org.session.libsession.messaging.threads.recipients.RecipientModifiedList import org.thoughtcrime.securesms.search.model.MessageResult; import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; +import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.MediaUtil; @@ -806,15 +806,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); - ExpirationTimerUpdate message = new ExpirationTimerUpdate(null, expirationTime); + ExpirationTimerUpdate message = new ExpirationTimerUpdate(expirationTime); + message.setRecipient(recipient.getAddress().serialize()); // we need the recipient in ExpiringMessageManager.insertOutgoingExpirationTimerMessage message.setSentTimestamp(System.currentTimeMillis()); - OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient); - try { - message.setId(DatabaseFactory.getMmsDatabase(ConversationActivity.this).insertMessageOutbox(outgoingMessage, getAllocatedThreadId(ConversationActivity.this), false, null)); - MessageSender.send(message, recipient.getAddress()); - } catch (MmsException e) { - Log.w(TAG, e); - } + ExpiringMessageManager expiringMessageManager = ApplicationContext.getInstance(getApplicationContext()).getExpiringMessageManager(); + expiringMessageManager.setExpirationTimer(message); + MessageSender.send(message, recipient.getAddress()); + return null; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index 0bdb8ba3cf..7218006755 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -14,12 +14,10 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage; import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; -import org.thoughtcrime.securesms.loki.utilities.GroupDescription; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.util.DateUtils; import org.session.libsignal.libsignal.util.guava.Optional; @@ -193,8 +191,6 @@ public class ConversationUpdateItem extends LinearLayout private void setGroupRecord(MessageRecord messageRecord) { icon.setImageResource(R.drawable.ic_group_grey600_24dp); icon.clearColorFilter(); - - GroupDescription.Companion.getDescription(getContext(), messageRecord.getBody()).addListener(this); body.setText(messageRecord.getDisplayBody(getContext())); title.setVisibility(GONE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index ea5444c7d5..fd3c793d1f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -514,15 +514,7 @@ public class MmsDatabase extends MessagingDatabase { } } - if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) { - return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, quote, contacts, previews); - } else if (Types.isExpirationTimerUpdate(outboxType)) { - return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn); - } - - boolean expirationTimer = (outboxType & Types.EXPIRATION_TIMER_UPDATE_BIT) != 0; - - OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, expirationTimer, distributionType, quote, contacts, previews, networkFailures, mismatches); + OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts, previews, networkFailures, mismatches); if (Types.isSecureType(outboxType)) { return new OutgoingSecureMediaMessage(message); @@ -532,8 +524,6 @@ public class MmsDatabase extends MessagingDatabase { } throw new NoSuchMessageException("No record found for id: " + messageId); - } catch (IOException e) { - throw new MmsException(e); } finally { if (cursor != null) cursor.close(); @@ -689,8 +679,19 @@ public class MmsDatabase extends MessagingDatabase { { if (threadId == -1) { if(retrieved.isGroup()) { - ByteString decodedGroupId = ((OutgoingGroupMediaMessage)retrieved).getGroupContext().getId(); - String groupId = GroupUtil.doubleEncodeGroupID(decodedGroupId.toByteArray()); + String decodedGroupId; + if (retrieved instanceof OutgoingExpirationUpdateMessage) { + decodedGroupId = ((OutgoingExpirationUpdateMessage)retrieved).getGroupId(); + } else { + decodedGroupId = ((OutgoingGroupMediaMessage)retrieved).getGroupId(); + } + String groupId; + try { + groupId = GroupUtil.doubleEncodeGroupID(decodedGroupId); + } catch (IOException e) { + Log.e(TAG, "Couldn't encrypt group ID"); + throw new MmsException(e); + } Recipient group = Recipient.from(context, Address.fromSerialized(groupId), false); threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(group); } else { @@ -753,9 +754,8 @@ public class MmsDatabase extends MessagingDatabase { if (message.isSecure()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT); if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT; - if (message.isGroup()) { - if (((OutgoingGroupMediaMessage)message).isGroupUpdate()) type |= Types.GROUP_UPDATE_BIT; - else if (((OutgoingGroupMediaMessage)message).isGroupQuit()) type |= Types.GROUP_QUIT_BIT; + if (message.isGroup() && message instanceof OutgoingGroupMediaMessage) { + if (((OutgoingGroupMediaMessage)message).isUpdateMessage()) type |= Types.GROUP_UPDATE_MESSAGE_BIT; } if (message.isExpirationUpdate()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index aefd1af808..024d1f51dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -70,6 +70,7 @@ public interface MmsSmsColumns { protected static final long GROUP_UPDATE_BIT = 0x10000; protected static final long GROUP_QUIT_BIT = 0x20000; protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000; + protected static final long GROUP_UPDATE_MESSAGE_BIT = 0x80000; // Data Extraction Information protected static final long MEDIA_SAVED_EXTRACTION_BIT = 0x01000; @@ -225,6 +226,8 @@ public interface MmsSmsColumns { return (type & GROUP_UPDATE_BIT) != 0; } + public static boolean isGroupUpdateMessage(long type) { return (type & GROUP_UPDATE_MESSAGE_BIT) != 0; } + public static boolean isGroupQuit(long type) { return (type & GROUP_QUIT_BIT) != 0; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index d5c34c5c54..226645b39c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -347,8 +347,7 @@ public class SmsDatabase extends MessagingDatabase { type |= Types.SECURE_MESSAGE_BIT; } else if (message.isGroup()) { type |= Types.SECURE_MESSAGE_BIT; - if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT; - else if (((IncomingGroupMessage)message).isQuit()) type |= Types.GROUP_QUIT_BIT; + if (((IncomingGroupMessage)message).isUpdateMessage()) type |= Types.GROUP_UPDATE_MESSAGE_BIT; } if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 21787f6a63..5e36e986b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -23,6 +23,8 @@ import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address.Companion.fromSerialized import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.recipients.Recipient +import org.session.libsession.messaging.utilities.UpdateMessageBuilder +import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.TextSecurePreferences @@ -42,6 +44,7 @@ import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.getString import org.thoughtcrime.securesms.mms.PartAuthority +import java.util.* class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { @@ -85,6 +88,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return recipient.profileKey } + override fun getDisplayNameForRecipient(recipientPublicKey: String): String? { + val database = DatabaseFactory.getLokiUserDatabase(context) + return database.getDisplayName(recipientPublicKey) + } + override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) { val address = Address.fromSerialized(recipientPublicKey) val recipient = Recipient.from(context, address, false) @@ -395,30 +403,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members) } - override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long) { - val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder() - .setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID))) - .setType(type0) - .setName(name) - .addAllMembers(members) - .addAllAdmins(admins) - val group = SignalServiceGroup(type1, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) + override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long) { + val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true) - val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "") + val updateData = UpdateMessageData.buildGroupUpdate(type, name, members).toJSON() + val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val smsDB = DatabaseFactory.getSmsDatabase(context) smsDB.insertMessageInbox(infoMessage) } - override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) { + override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) { val userPublicKey = getUserPublicKey() val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) - val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder() - .setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID))) - .setType(type) - .setName(name) - .addAllMembers(members) - .addAllAdmins(admins) - val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, sentTimestamp, 0, false, null, listOf(), listOf()) + + val updateData = UpdateMessageData.buildGroupUpdate(type, name, members).toJSON() + val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf()) val mmsDB = DatabaseFactory.getMmsDatabase(context) val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context) if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 28eae16f16..f98dcda5d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -109,6 +109,7 @@ public abstract class DisplayRecord { public boolean isLokiSessionRestoreDone() { return SmsDatabase.Types.isLokiSessionRestoreDoneType(type); } + // TODO isGroupUpdate and isGroupQuit are kept for compatibility with old update messages, they can be removed later on public boolean isGroupUpdate() { return SmsDatabase.Types.isGroupUpdate(type); } @@ -117,8 +118,13 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isGroupQuit(type); } + public boolean isGroupUpdateMessage() { + return SmsDatabase.Types.isGroupUpdateMessage(type); + } + + //TODO isGroupAction can be replaced by isGroupUpdateMessage in the code when the 2 functions above are removed public boolean isGroupAction() { - return isGroupUpdate() || isGroupQuit(); + return isGroupUpdate() || isGroupQuit() || isGroupUpdateMessage(); } public boolean isExpirationTimerUpdate() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 64229e95b5..af293d3833 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -24,14 +24,15 @@ import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import network.loki.messenger.R; + +import org.session.libsession.messaging.utilities.UpdateMessageBuilder; +import org.session.libsession.messaging.utilities.UpdateMessageData; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; import org.session.libsession.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.NetworkFailure; import org.session.libsession.messaging.threads.recipients.Recipient; -import org.session.libsession.utilities.ExpirationUtil; -import org.thoughtcrime.securesms.loki.utilities.GroupDescription; import java.util.List; @@ -90,42 +91,22 @@ public abstract class MessageRecord extends DisplayRecord { @Override public SpannableString getDisplayBody(@NonNull Context context) { - if (isGroupUpdate() && isOutgoing()) { + if(isGroupUpdateMessage()) { + UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(getBody()); + return new SpannableString(UpdateMessageBuilder.INSTANCE.buildGroupUpdateMessage(context, updateMessageData, getIndividualRecipient().getAddress().serialize(), isOutgoing())); + } else if (isExpirationTimerUpdate()) { + int seconds = (int) (getExpiresIn() / 1000); + return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, getIndividualRecipient().getAddress().serialize(), isOutgoing())); + } + // TODO below lines are left here for compatibility with older group update messages, it can be deleted later on + else if (isGroupUpdate() && isOutgoing()) { return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group)); } else if (isGroupUpdate()) { - return new SpannableString(GroupDescription.Companion.getDescription(context, getBody()).toString(getIndividualRecipient())); + return new SpannableString(context.getString(R.string.MessageRecord_s_updated_group, getIndividualRecipient().toShortString())); } else if (isGroupQuit() && isOutgoing()) { return new SpannableString(context.getString(R.string.MessageRecord_left_group)); } else if (isGroupQuit()) { return new SpannableString(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString())); - } else if (isDataExtraction()) { - if (isMediaSavedExtraction()) return new SpannableString(context.getString(R.string.MessageRecord_media_saved_by_s, getIndividualRecipient().toShortString())); - if (isScreenshotExtraction()) return new SpannableString(context.getString(R.string.MessageRecord_s_took_a_screenshot, getIndividualRecipient().toShortString())); - } else if (isIncomingCall()) { - return new SpannableString(context.getString(R.string.MessageRecord_s_called_you, getIndividualRecipient().toShortString())); - } else if (isOutgoingCall()) { - return new SpannableString(context.getString(R.string.MessageRecord_you_called)); - } else if (isMissedCall()) { - return new SpannableString(context.getString(R.string.MessageRecord_missed_call)); - } else if (isJoined()) { - return new SpannableString(context.getString(R.string.MessageRecord_s_joined_signal, getIndividualRecipient().toShortString())); - } else if (isExpirationTimerUpdate()) { - int seconds = (int)(getExpiresIn() / 1000); - if (seconds <= 0) { - return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_disabled_disappearing_messages)) - : new SpannableString(context.getString(R.string.MessageRecord_s_disabled_disappearing_messages, getIndividualRecipient().toShortString())); - } - String time = ExpirationUtil.getExpirationDisplayValue(context, seconds); - return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time)) - : new SpannableString(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, getIndividualRecipient().toShortString(), time)); - } else if (isIdentityUpdate()) { - return new SpannableString(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().toShortString())); - } else if (isIdentityVerified()) { - if (isOutgoing()) return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified, getIndividualRecipient().toShortString())); - else return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device, getIndividualRecipient().toShortString())); - } else if (isIdentityDefault()) { - if (isOutgoing()) return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified, getIndividualRecipient().toShortString())); - else return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified_from_another_device, getIndividualRecipient().toShortString())); } return new SpannableString(getBody()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 42c8a20ede..3315ba94f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -28,6 +28,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.session.libsession.messaging.threads.recipients.Recipient; +import org.session.libsession.messaging.utilities.UpdateMessageBuilder; +import org.session.libsession.messaging.utilities.UpdateMessageData; import org.session.libsession.utilities.ExpirationUtil; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; @@ -73,7 +75,7 @@ public class ThreadRecord extends DisplayRecord { @Override public SpannableString getDisplayBody(@NonNull Context context) { Recipient recipient = getRecipient(); - if (isGroupUpdate()) { + if (isGroupUpdate() || isGroupUpdateMessage()) { return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated)); } else if (isGroupQuit()) { return emphasisAdded(context.getString(R.string.ThreadRecord_left_the_group)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index d2b0ad11aa..df1d7c0b52 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -9,7 +9,6 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage -import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.toHexString import org.thoughtcrime.securesms.database.DatabaseFactory @@ -104,11 +103,11 @@ object ClosedGroupsProtocolV2 { apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) // Notify the user (if we didn't make the group) if (userPublicKey != senderPublicKey) { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) + DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) } else if (prevGroup == null) { // only notify if we created this group val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) + DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp) } // Notify the PN server LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) @@ -156,14 +155,14 @@ object ClosedGroupsProtocolV2 { MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers) } } - val (contextType, signalType) = - if (senderLeft) GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT - else GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE + val type = + if (senderLeft) SignalServiceGroup.Type.QUIT + else SignalServiceGroup.Type.UPDATE if (userPublicKey == senderPublicKey) { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, sentTimestamp) + DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type, name, members, admins, threadID, sentTimestamp) } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, sentTimestamp) + DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, admins, sentTimestamp) } } @@ -191,9 +190,9 @@ object ClosedGroupsProtocolV2 { groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) if (userPublicKey == senderPublicKey) { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) + DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp) } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) + DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) } if (userPublicKey in admins) { // send current encryption key to the latest added members @@ -230,9 +229,9 @@ object ClosedGroupsProtocolV2 { // Notify the user if (userPublicKey == senderPublicKey) { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) + DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp) } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) + DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) } } @@ -272,9 +271,9 @@ object ClosedGroupsProtocolV2 { // Notify user if (userLeft) { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID, sentTimestamp) + DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, threadID, sentTimestamp) } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins, sentTimestamp) + DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, sentTimestamp) } } @@ -385,14 +384,13 @@ object ClosedGroupsProtocolV2 { } // Notify the user val wasSenderRemoved = !members.contains(senderPublicKey) - val type0 = if (wasSenderRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE - val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE + val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE val admins = group.admins.map { it.toString() } if (userPublicKey == senderPublicKey) { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type0, name, members, admins, threadID, sentTimestamp) + DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type, name, members, admins, threadID, sentTimestamp) } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, admins, sentTimestamp) + DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, admins, sentTimestamp) } } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt deleted file mode 100644 index 62d48aa524..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt +++ /dev/null @@ -1,103 +0,0 @@ -package org.thoughtcrime.securesms.loki.utilities - -import android.content.Context -import org.session.libsession.messaging.threads.Address -import org.session.libsession.messaging.threads.recipients.Recipient -import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Base64 -import org.session.libsignal.service.internal.push.SignalServiceProtos -import java.util.* - -import network.loki.messenger.R -import org.session.libsignal.utilities.logging.Log -import java.io.IOException - -class GroupDescription(context: Context, groupContext: SignalServiceProtos.GroupContext?) { - private val context: Context - private val groupContext: SignalServiceProtos.GroupContext? - private val newMembers: MutableList - private val removedMembers: MutableList - private var wasCurrentUserRemoved: Boolean = false - private fun toRecipient(hexEncodedPublicKey: String): Recipient { - val address = Address.fromSerialized(hexEncodedPublicKey) - return Recipient.from(context, address, false) - } - - fun toString(sender: Recipient): String { - if (wasCurrentUserRemoved) { - return context.getString(R.string.GroupUtil_you_were_removed_from_group) - } - val description = StringBuilder() - description.append(context.getString(R.string.MessageRecord_s_updated_group, sender.toShortString())) - if (groupContext == null) { - return description.toString() - } - val title = groupContext.name - if (!newMembers.isEmpty()) { - description.append("\n") - description.append(context.resources.getQuantityString(R.plurals.GroupUtil_joined_the_group, - newMembers.size, toString(newMembers))) - } - if (!removedMembers.isEmpty()) { - description.append("\n") - description.append(context.resources.getQuantityString(R.plurals.GroupUtil_removed_from_the_group, - removedMembers.size, toString(removedMembers))) - } - if (title != null && !title.trim { it <= ' ' }.isEmpty()) { - val separator = if (!newMembers.isEmpty() || !removedMembers.isEmpty()) " " else "\n" - description.append(separator) - description.append(context.getString(R.string.GroupUtil_group_name_is_now, title)) - } - return description.toString() - } - - fun addListener(listener: RecipientModifiedListener?) { - if (!newMembers.isEmpty()) { - for (member in newMembers) { - member.addListener(listener) - } - } - } - - private fun toString(recipients: List): String { - var result = "" - for (i in recipients.indices) { - result += recipients[i].toShortString() - if (i != recipients.size - 1) result += ", " - } - return result - } - - init { - this.context = context.applicationContext - this.groupContext = groupContext - newMembers = LinkedList() - removedMembers = LinkedList() - if (groupContext != null) { - val newMembers = groupContext.newMembersList - for (member in newMembers) { - this.newMembers.add(toRecipient(member)) - } - val removedMembers = groupContext.removedMembersList - for (member in removedMembers) { - this.removedMembers.add(toRecipient(member)) - } - wasCurrentUserRemoved = removedMembers.contains(TextSecurePreferences.getLocalNumber(context)) - } - } - - companion object { - fun getDescription(context: Context, encodedGroup: String?): GroupDescription { - return if (encodedGroup == null) { - GroupDescription(context, null) - } else try { - val groupContext = SignalServiceProtos.GroupContext.parseFrom(Base64.decode(encodedGroup)) - GroupDescription(context, groupContext) - } catch (e: IOException) { - Log.w("Loki", e) - GroupDescription(context, null) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index 267c53ec26..05fed4b9d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -2,22 +2,17 @@ package org.thoughtcrime.securesms.service; import android.content.Context; -import com.google.protobuf.ByteString; import org.jetbrains.annotations.NotNull; import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; -import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage; -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; +import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage; import org.session.libsession.messaging.threads.Address; -import org.session.libsession.messaging.threads.DistributionTypes; import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.service.api.messages.SignalServiceGroup; -import org.session.libsignal.service.internal.push.SignalServiceProtos; -import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext; import org.session.libsignal.utilities.logging.Log; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -28,7 +23,6 @@ import org.session.libsession.messaging.messages.signal.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; import java.io.IOException; -import java.util.Collections; import java.util.Comparator; import java.util.TreeSet; import java.util.concurrent.Executor; @@ -79,8 +73,8 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM String senderPublicKey = message.getSender(); // Notify the user - if (userPublicKey.equals(senderPublicKey)) { - // sender is a linked device + if (senderPublicKey == null || userPublicKey.equals(senderPublicKey)) { + // sender is self or a linked device insertOutgoingExpirationTimerMessage(message); } else { insertIncomingExpirationTimerMessage(message); @@ -100,7 +94,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM int duration = message.getDuration(); Optional groupInfo = Optional.absent(); - Address address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : senderPublicKey); + Address address = Address.fromSerialized(senderPublicKey); Recipient recipient = Recipient.from(context, address, false); // if the sender is blocked, we don't display the update, except if it's in a closed group @@ -139,41 +133,21 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM private void insertOutgoingExpirationTimerMessage(ExpirationTimerUpdate message) { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - String senderPublicKey = message.getSender(); Long sentTimestamp = message.getSentTimestamp(); String groupId = message.getGroupPublicKey(); int duration = message.getDuration(); - Address address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : senderPublicKey); + Address address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : message.getRecipient()); Recipient recipient = Recipient.from(context, address, false); try { + OutgoingExpirationUpdateMessage timerUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, sentTimestamp, duration * 1000L, groupId); + database.insertSecureDecryptedMessageOutbox(timerUpdateMessage, -1, sentTimestamp); + if (groupId != null) { - // conversation is a closed group - GroupContext groupContext = SignalServiceProtos.GroupContext.newBuilder() - .setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupId))).build(); - OutgoingGroupMediaMessage infoMessage = new OutgoingGroupMediaMessage(recipient, groupContext, null, sentTimestamp, duration * 1000L, true, null, Collections.emptyList(), Collections.emptyList()); - database.insertSecureDecryptedMessageOutbox(infoMessage, -1, sentTimestamp); // we need the group ID as recipient for setExpireMessages below recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupId)), false); - } else { - // conversation is a 1-1 - OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipient, - null, - Collections.emptyList(), - message.getSentTimestamp(), - -1, - duration * 1000L, - true, - DistributionTypes.DEFAULT, - null, - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList()); - database.insertSecureDecryptedMessageOutbox(mediaMessage, -1, sentTimestamp); } - //set the timer to the conversation DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration); diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d562e0c62a..f156dad77b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -418,6 +418,16 @@ Vous avez reçu un message chiffré avec une ancienne version de Signal qui n’est plus prise en charge. Veuillez demander à l’expéditeur de mettre Session à jour vers la version la plus récente et de renvoyer son message. Vous avez quitté le groupe Vous avez mis le groupe à jour. + Vous avez créée un nouveau groupe. + %1$s vous a ajouté au groupe. + Vous avez renommé le groupe en \'%1$s\' + %1$s a renommé le groupe en \'%2$s\' + Vous avez ajouté %1$s au groupe. + %1$s a ajouté %2$s au groupe. + Vous avez supprimé %1$s du groupe. + %1$s a supprimé %2$s du groupe. + Vous avez été supprimé du groupe. + Vous Vous avez appelé Contact appelé Appel manqué @@ -1483,4 +1493,35 @@ Vous avez reçu un message d’échange de clés pour une version de protocole i Groupes privés Groupes publics + + + Appliquer + Terminé + + Modifier le groupe + Saisissez un nouveau nom de groupe + Membres + Ajouter des membres + Le nom du groupe ne peut pas être vide + Veuillez saisir un nom plus court + Les groupes doivent avoir au moins un membre + Un des membres du group a un Session ID invalide + Êtes vous sûr de vouloir suprimer ce membre du groupe? + Membre supprimé du groupe + + Supprimer le membre du groupe + + Contacts + + Thème + Jour + Nuit + Système + + Copier le Session ID + + Pièce jointe + Message vocal + Détails + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b0693645a..697b85ff20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -493,6 +493,16 @@ Received a message encrypted using an old version of Session that is no longer supported. Please ask the sender to update to the most recent version and resend the message. You have left the group. You updated the group. + You created a new group. + %1$s added you to the group. + You renamed the group to \'%1$s\' + %1$s renamed the group to \'%2$s\' + You added %1$s to the group. + %1$s added %2$s to the group. + You removed %1$s from the group. + %1$s removed %2$s from the group. + You were removed from the group. + You You called Contact called Missed call diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index b390637ca2..da84c8f6e8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -35,6 +35,7 @@ interface StorageProtocol { fun setUserProfilePictureUrl(newProfilePicture: String) fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? + fun getDisplayNameForRecipient(recipientPublicKey: String): String? fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) // Signal Protocol @@ -117,9 +118,9 @@ interface StorageProtocol { fun removeClosedGroupPublicKey(groupPublicKey: String) fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String) - fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type, + fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long) - fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String, + fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) fun isClosedGroup(publicKey: String): Boolean fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt index 85f34eed51..5dff39072c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt @@ -33,6 +33,11 @@ class ExpirationTimerUpdate() : ControlMessage() { this.duration = duration } + internal constructor(duration: Int) : this() { + this.syncTarget = null + this.duration = duration + } + // validation override fun isValid(): Boolean { if (!super.isValid()) return false diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingGroupMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingGroupMessage.java index b0a7de08ee..125267afb7 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingGroupMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingGroupMessage.java @@ -4,11 +4,13 @@ import static org.session.libsignal.service.internal.push.SignalServiceProtos.Gr public class IncomingGroupMessage extends IncomingTextMessage { - private final GroupContext groupContext; + private final String groupID; + private final boolean updateMessage; - public IncomingGroupMessage(IncomingTextMessage base, GroupContext groupContext, String body) { + public IncomingGroupMessage(IncomingTextMessage base, String groupID, String body, boolean updateMessage) { super(base, body); - this.groupContext = groupContext; + this.groupID = groupID; + this.updateMessage = updateMessage; } @Override @@ -16,12 +18,6 @@ public class IncomingGroupMessage extends IncomingTextMessage { return true; } - public boolean isUpdate() { - return groupContext.getType().getNumber() == GroupContext.Type.UPDATE_VALUE; - } - - public boolean isQuit() { - return groupContext.getType().getNumber() == GroupContext.Type.QUIT_VALUE; - } + public boolean isUpdateMessage() { return updateMessage; } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java index 74f72c4925..b45f33c78f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java @@ -1,6 +1,5 @@ package org.session.libsession.messaging.messages.signal; -import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.threads.DistributionTypes; import org.session.libsession.messaging.threads.recipients.Recipient; @@ -10,15 +9,13 @@ import java.util.LinkedList; public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage { - public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) { - super(recipient, "", new LinkedList(), sentTimeMillis, - DistributionTypes.CONVERSATION, expiresIn, true, null, Collections.emptyList(), - Collections.emptyList()); - } + private final String groupId; - public static OutgoingExpirationUpdateMessage from(ExpirationTimerUpdate message, - Recipient recipient) { - return new OutgoingExpirationUpdateMessage(recipient, message.getSentTimestamp(), message.getDuration() * 1000); + public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn, String groupId) { + super(recipient, "", new LinkedList(), sentTimeMillis, + DistributionTypes.CONVERSATION, expiresIn, null, Collections.emptyList(), + Collections.emptyList()); + this.groupId = groupId; } @Override @@ -26,4 +23,13 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage return true; } + @Override + public boolean isGroup() { + return groupId != null; + } + + public String getGroupId() { + return groupId; + } + } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java index 7f151ae7ee..574c47cd19 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java @@ -19,56 +19,27 @@ import java.util.List; public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { - private final GroupContext group; + private final String groupID; + private final boolean isUpdateMessage; public OutgoingGroupMediaMessage(@NonNull Recipient recipient, - @NonNull String encodedGroupContext, - @NonNull List avatar, - long sentTimeMillis, - long expiresIn, - @Nullable QuoteModel quote, - @NonNull List contacts, - @NonNull List previews) - throws IOException - { - super(recipient, encodedGroupContext, avatar, sentTimeMillis, - DistributionTypes.CONVERSATION, expiresIn, false, quote, contacts, previews); - - this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext)); - } - - public OutgoingGroupMediaMessage(@NonNull Recipient recipient, - @NonNull GroupContext group, - @Nullable final Attachment avatar, - long expireIn, - @Nullable QuoteModel quote, - @NonNull List contacts, - @NonNull List previews) - { - super(recipient, Base64.encodeBytes(group.toByteArray()), - new LinkedList() {{if (avatar != null) add(avatar);}}, - System.currentTimeMillis(), - DistributionTypes.CONVERSATION, expireIn, false, quote, contacts, previews); - - this.group = group; - } - - public OutgoingGroupMediaMessage(@NonNull Recipient recipient, - @NonNull GroupContext group, + @NonNull String body, + @Nullable String groupId, @Nullable final Attachment avatar, long sentTime, long expireIn, - boolean expirationUpdate, + boolean updateMessage, @Nullable QuoteModel quote, @NonNull List contacts, @NonNull List previews) { - super(recipient, Base64.encodeBytes(group.toByteArray()), + super(recipient, body, new LinkedList() {{if (avatar != null) add(avatar);}}, sentTime, - DistributionTypes.CONVERSATION, expireIn, expirationUpdate, quote, contacts, previews); + DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews); - this.group = group; + this.groupID = groupId; + this.isUpdateMessage = updateMessage; } @Override @@ -76,15 +47,11 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { return true; } - public boolean isGroupUpdate() { - return group.getType().getNumber() == GroupContext.Type.UPDATE_VALUE; + public String getGroupId() { + return groupID; } - public boolean isGroupQuit() { - return group.getType().getNumber() == GroupContext.Type.QUIT_VALUE; - } - - public GroupContext getGroupContext() { - return group; + public boolean isUpdateMessage() { + return isUpdateMessage; } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java index 9a6618dc4a..d8d4dff3c5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java @@ -26,7 +26,6 @@ public class OutgoingMediaMessage { private final int distributionType; private final int subscriptionId; private final long expiresIn; - private final boolean expirationUpdate; private final QuoteModel outgoingQuote; private final List networkFailures = new LinkedList<>(); @@ -37,7 +36,6 @@ public class OutgoingMediaMessage { public OutgoingMediaMessage(Recipient recipient, String message, List attachments, long sentTimeMillis, int subscriptionId, long expiresIn, - boolean expirationUpdate, int distributionType, @Nullable QuoteModel outgoingQuote, @NonNull List contacts, @@ -52,7 +50,6 @@ public class OutgoingMediaMessage { this.attachments = attachments; this.subscriptionId = subscriptionId; this.expiresIn = expiresIn; - this.expirationUpdate = expirationUpdate; this.outgoingQuote = outgoingQuote; this.contacts.addAll(contacts); @@ -69,7 +66,6 @@ public class OutgoingMediaMessage { this.sentTimeMillis = that.sentTimeMillis; this.subscriptionId = that.subscriptionId; this.expiresIn = that.expiresIn; - this.expirationUpdate = that.expirationUpdate; this.outgoingQuote = that.outgoingQuote; this.identityKeyMismatches.addAll(that.identityKeyMismatches); @@ -89,7 +85,7 @@ public class OutgoingMediaMessage { previews = Collections.singletonList(linkPreview); } return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1, - recipient.getExpireMessages() * 1000, false, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(), + recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList()); } @@ -113,7 +109,7 @@ public class OutgoingMediaMessage { return false; } - public boolean isExpirationUpdate() { return expirationUpdate; } + public boolean isExpirationUpdate() { return false; } public long getSentTimeMillis() { return sentTimeMillis; diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java index c7822d8b90..8b5e7ddef0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java @@ -19,12 +19,11 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage { long sentTimeMillis, int distributionType, long expiresIn, - boolean expirationUpdate, @Nullable QuoteModel quote, @NonNull List contacts, @NonNull List previews) { - super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expirationUpdate, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList()); + super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList()); } public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 190a55195f..e5d0c8e278 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -264,8 +264,15 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli } else { storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }), null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp) + val userPublicKey = TextSecurePreferences.getLocalNumber(context) // Notify the user - storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) + if (userPublicKey == sender) { + // sender is a linked device + val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) + storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTimestamp) + } else { + storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, sentTimestamp) + } } storage.setProfileSharing(Address.fromSerialized(groupID), true) // Add the group to the user's set of public keys to poll for @@ -325,9 +332,8 @@ private fun MessageReceiver.handleClosedGroupUpdated(message: ClosedGroupControl } // Notify the user val wasSenderRemoved = !members.contains(senderPublicKey) - val type0 = if (wasSenderRemoved) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE - val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, group.admins.map { it.toString() }, message.sentTimestamp!!) + val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.MEMBER_REMOVED + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, group.admins.map { it.toString() }, message.sentTimestamp!!) } private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGroupControlMessage) { @@ -399,9 +405,9 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon if (userPublicKey == senderPublicKey) { // sender is a linked device val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, message.sentTimestamp!!) + storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins, threadID, message.sentTimestamp!!) } else { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins, message.sentTimestamp!!) } } @@ -434,9 +440,9 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo if (userPublicKey == senderPublicKey) { // sender is a linked device val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, message.sentTimestamp!!) + storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.MEMBER_ADDED, name, updateMembers, admins, threadID, message.sentTimestamp!!) } else { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.MEMBER_ADDED, name, updateMembers, admins, message.sentTimestamp!!) } if (userPublicKey in admins) { // send current encryption key to the latest added members @@ -498,17 +504,16 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers) } } - val (contextType, signalType) = - if (senderLeft) SignalServiceProtos.GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT - else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE + val type = if (senderLeft) SignalServiceGroup.Type.QUIT + else SignalServiceGroup.Type.MEMBER_REMOVED // Notify the user if (userPublicKey == senderPublicKey) { // sender is a linked device val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, message.sentTimestamp!!) + storage.insertOutgoingInfoMessage(context, groupID, type, name, updateMembers, admins, threadID, message.sentTimestamp!!) } else { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, message.sentTimestamp!!) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, updateMembers, admins, message.sentTimestamp!!) } } @@ -554,9 +559,9 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont if (userLeft) { //sender is a linked device val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.QUIT, name, members, admins, threadID, message.sentTimestamp!!) + storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, threadID, message.sentTimestamp!!) } else { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins, message.sentTimestamp!!) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, message.sentTimestamp!!) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt index 58f8ffe64c..143fa7fb3f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt @@ -15,6 +15,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.libsignal.ecc.Curve import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.guava.Optional +import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded @@ -60,7 +61,7 @@ fun MessageSender.create(name: String, members: Collection): Promise) send(closedGroupControlMessage, Address.fromSerialized(member)) } // Notify the user - val infoType = SignalServiceProtos.GroupContext.Type.UPDATE + val infoType = SignalServiceGroup.Type.MEMBER_ADDED val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) - storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) + storage.insertOutgoingInfoMessage(context, groupID, infoType, name, membersToAdd, admins, threadID, sentTime) } fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List) { @@ -189,9 +190,9 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List { @@ -212,7 +213,7 @@ fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Pro storage.setActive(groupID, false) sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success { // Notify the user - val infoType = SignalServiceProtos.GroupContext.Type.QUIT + val infoType = SignalServiceGroup.Type.QUIT val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) if (notifyUser) { storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt new file mode 100644 index 0000000000..d6f446e364 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -0,0 +1,100 @@ +package org.session.libsession.messaging.utilities + +import android.content.Context +import org.session.libsession.R +import org.session.libsession.messaging.MessagingConfiguration +import org.session.libsession.utilities.ExpirationUtil +import org.session.libsignal.service.api.messages.SignalServiceGroup + +object UpdateMessageBuilder { + + fun buildGroupUpdateMessage(context: Context, updateMessageData: UpdateMessageData, sender: String? = null, isOutgoing: Boolean = false): String { + var message: String = "" + val updateData = updateMessageData.kind as? UpdateMessageData.Kind.GroupUpdate ?: return message + val updateType = updateData.type + if (!isOutgoing && sender == null) return message + val senderName: String? = if (!isOutgoing) { + MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender + } else { context.getString(R.string.MessageRecord_you) } + + when (updateType) { + SignalServiceGroup.Type.CREATION -> { + message = if (isOutgoing) { + context.getString(R.string.MessageRecord_you_created_a_new_group) + } else { + context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName) + } + } + SignalServiceGroup.Type.NAME_CHANGE -> { + message = if (isOutgoing) { + context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.groupName) + } else { + context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.groupName) + } + } + SignalServiceGroup.Type.MEMBER_ADDED -> { + val members = updateData.updatedMembers.joinToString(", ") { + MessagingConfiguration.shared.storage.getDisplayNameForRecipient(it) ?: it + } + message = if (isOutgoing) { + context.getString(R.string.MessageRecord_you_added_s_to_the_group, members) + } else { + context.getString(R.string.MessageRecord_s_added_s_to_the_group, senderName, members) + } + } + SignalServiceGroup.Type.MEMBER_REMOVED -> { + val storage = MessagingConfiguration.shared.storage + val userPublicKey = storage.getUserPublicKey()!! + // 1st case: you are part of the removed members + message = if (userPublicKey in updateData.updatedMembers) { + if (isOutgoing) { + context.getString(R.string.MessageRecord_left_group) + } else { + context.getString(R.string.MessageRecord_you_were_removed_from_the_group) + } + } else { + // 2nd case: you are not part of the removed members + val members = updateData.updatedMembers.joinToString(", ") { + storage.getDisplayNameForRecipient(it) ?: it + } + if (isOutgoing) { + context.getString(R.string.MessageRecord_you_removed_s_from_the_group, members) + } else { + context.getString(R.string.MessageRecord_s_removed_s_from_the_group, senderName, members) + } + } + } + SignalServiceGroup.Type.QUIT -> { + message = if (isOutgoing) { + context.getString(R.string.MessageRecord_left_group) + } else { + context.getString(R.string.ConversationItem_group_action_left, senderName) + } + } + else -> { + message = context.getString(R.string.MessageRecord_s_updated_group, senderName) + } + } + return message + } + + fun buildExpirationTimerMessage(context: Context, duration: Long, sender: String? = null, isOutgoing: Boolean = false): String { + if (!isOutgoing && sender == null) return "" + val senderName: String? = if (!isOutgoing) { + MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender + } else { sender } + return if (duration <= 0) { + if (isOutgoing) context.getString(R.string.MessageRecord_you_disabled_disappearing_messages) + else context.getString(R.string.MessageRecord_s_disabled_disappearing_messages, senderName) + } else { + val time = ExpirationUtil.getExpirationDisplayValue(context, duration.toInt()) + if (isOutgoing)context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time) + else context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, senderName, time) + } + } + + //TODO do this when the current update is merged + fun buildDataExtractionMessage(): String { + return "" + } +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt new file mode 100644 index 0000000000..5ca4bbbf9c --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt @@ -0,0 +1,56 @@ +package org.session.libsession.messaging.utilities + +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.core.JsonParseException +import org.session.libsignal.service.api.messages.SignalServiceGroup +import org.session.libsignal.utilities.JsonUtil +import org.session.libsignal.utilities.logging.Log +import java.util.* + +// class used to save update messages details +class UpdateMessageData () { + + var kind: Kind? = null + + //the annotations below are required for serialization. Any new Kind class MUST be declared as JsonSubTypes as well + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) + @JsonSubTypes( + JsonSubTypes.Type(Kind.GroupUpdate::class, name = "GroupUpdate") + ) + sealed class Kind { + class GroupUpdate( var type: SignalServiceGroup.Type, var groupName: String?, var updatedMembers: Collection): Kind() { + constructor(): this(SignalServiceGroup.Type.UNKNOWN, null, Collections.emptyList()) //default constructor required for json serialization + } + } + + constructor(kind: Kind): this() { + this.kind = kind + } + + companion object { + val TAG = UpdateMessageData::class.simpleName + + fun buildGroupUpdate(type: SignalServiceGroup.Type, name: String, members: Collection): UpdateMessageData { + return when(type) { + SignalServiceGroup.Type.NAME_CHANGE -> UpdateMessageData(Kind.GroupUpdate(type, name, Collections.emptyList())) + SignalServiceGroup.Type.MEMBER_ADDED -> UpdateMessageData(Kind.GroupUpdate(type,null, members)) + SignalServiceGroup.Type.MEMBER_REMOVED -> UpdateMessageData(Kind.GroupUpdate(type,null, members)) + else -> UpdateMessageData(Kind.GroupUpdate(type,null, Collections.emptyList())) + } + } + + fun fromJSON(json: String): UpdateMessageData { + return try { + JsonUtil.fromJson(json, UpdateMessageData::class.java) + } catch (e: JsonParseException) { + Log.e(TAG, "${e.message}") + UpdateMessageData(Kind.GroupUpdate(SignalServiceGroup.Type.UNKNOWN, null, Collections.emptyList())) + } + } + } + + fun toJSON(): String { + return JsonUtil.toJson(this) + } +} diff --git a/libsession/src/main/res/values/strings.xml b/libsession/src/main/res/values/strings.xml index 70b424bd22..116b106017 100644 --- a/libsession/src/main/res/values/strings.xml +++ b/libsession/src/main/res/values/strings.xml @@ -487,6 +487,16 @@ Received a message encrypted using an old version of Session that is no longer supported. Please ask the sender to update to the most recent version and resend the message. You have left the group. You updated the group. + You created a new group. + %1$s added you to the group. + You renamed the group to %1$s + %1$s renamed the group to: %2$s + You added %1$s to the group. + %1$s added %2$s to the group. + You removed %1$s from the group. + %1$s removed %2$s from the group. + You were removed from the group. + You You called Contact called Missed call diff --git a/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceGroup.java b/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceGroup.java index b503531097..5115844a12 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceGroup.java +++ b/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceGroup.java @@ -37,7 +37,11 @@ public class SignalServiceGroup { UPDATE, DELIVER, QUIT, - REQUEST_INFO + REQUEST_INFO, + CREATION, + NAME_CHANGE, + MEMBER_ADDED, + MEMBER_REMOVED } private final byte[] groupId;