Merge branch 'specific-group-updates' into data-extraction-2

This commit is contained in:
Brice-W 2021-04-20 15:42:04 +10:00
commit 66e811ed75
27 changed files with 385 additions and 335 deletions

View File

@ -158,7 +158,6 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MmsException; 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.OutgoingMediaMessage;
import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.QuoteId; 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.thoughtcrime.securesms.search.model.MessageResult;
import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; 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.BitmapUtil;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
@ -806,15 +806,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); 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()); message.setSentTimestamp(System.currentTimeMillis());
OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient); ExpiringMessageManager expiringMessageManager = ApplicationContext.getInstance(getApplicationContext()).getExpiringMessageManager();
try { expiringMessageManager.setExpirationTimer(message);
message.setId(DatabaseFactory.getMmsDatabase(ConversationActivity.this).insertMessageOutbox(outgoingMessage, getAllocatedThreadId(ConversationActivity.this), false, null)); MessageSender.send(message, recipient.getAddress());
MessageSender.send(message, recipient.getAddress());
} catch (MmsException e) {
Log.w(TAG, e);
}
return null; return null;
} }

View File

@ -14,12 +14,10 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage;
import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
import org.thoughtcrime.securesms.loki.utilities.GroupDescription;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
@ -193,8 +191,6 @@ public class ConversationUpdateItem extends LinearLayout
private void setGroupRecord(MessageRecord messageRecord) { private void setGroupRecord(MessageRecord messageRecord) {
icon.setImageResource(R.drawable.ic_group_grey600_24dp); icon.setImageResource(R.drawable.ic_group_grey600_24dp);
icon.clearColorFilter(); icon.clearColorFilter();
GroupDescription.Companion.getDescription(getContext(), messageRecord.getBody()).addListener(this);
body.setText(messageRecord.getDisplayBody(getContext())); body.setText(messageRecord.getDisplayBody(getContext()));
title.setVisibility(GONE); title.setVisibility(GONE);

View File

@ -514,15 +514,7 @@ public class MmsDatabase extends MessagingDatabase {
} }
} }
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) { OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts, previews, networkFailures, mismatches);
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);
if (Types.isSecureType(outboxType)) { if (Types.isSecureType(outboxType)) {
return new OutgoingSecureMediaMessage(message); return new OutgoingSecureMediaMessage(message);
@ -532,8 +524,6 @@ public class MmsDatabase extends MessagingDatabase {
} }
throw new NoSuchMessageException("No record found for id: " + messageId); throw new NoSuchMessageException("No record found for id: " + messageId);
} catch (IOException e) {
throw new MmsException(e);
} finally { } finally {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
@ -689,8 +679,19 @@ public class MmsDatabase extends MessagingDatabase {
{ {
if (threadId == -1) { if (threadId == -1) {
if(retrieved.isGroup()) { if(retrieved.isGroup()) {
ByteString decodedGroupId = ((OutgoingGroupMediaMessage)retrieved).getGroupContext().getId(); String decodedGroupId;
String groupId = GroupUtil.doubleEncodeGroupID(decodedGroupId.toByteArray()); 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); Recipient group = Recipient.from(context, Address.fromSerialized(groupId), false);
threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(group); threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(group);
} else { } else {
@ -753,9 +754,8 @@ public class MmsDatabase extends MessagingDatabase {
if (message.isSecure()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT); if (message.isSecure()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT; if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
if (message.isGroup()) { if (message.isGroup() && message instanceof OutgoingGroupMediaMessage) {
if (((OutgoingGroupMediaMessage)message).isGroupUpdate()) type |= Types.GROUP_UPDATE_BIT; if (((OutgoingGroupMediaMessage)message).isUpdateMessage()) type |= Types.GROUP_UPDATE_MESSAGE_BIT;
else if (((OutgoingGroupMediaMessage)message).isGroupQuit()) type |= Types.GROUP_QUIT_BIT;
} }
if (message.isExpirationUpdate()) { if (message.isExpirationUpdate()) {

View File

@ -70,6 +70,7 @@ public interface MmsSmsColumns {
protected static final long GROUP_UPDATE_BIT = 0x10000; protected static final long GROUP_UPDATE_BIT = 0x10000;
protected static final long GROUP_QUIT_BIT = 0x20000; protected static final long GROUP_QUIT_BIT = 0x20000;
protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000; protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000;
protected static final long GROUP_UPDATE_MESSAGE_BIT = 0x80000;
// Data Extraction Information // Data Extraction Information
protected static final long MEDIA_SAVED_EXTRACTION_BIT = 0x01000; protected static final long MEDIA_SAVED_EXTRACTION_BIT = 0x01000;
@ -225,6 +226,8 @@ public interface MmsSmsColumns {
return (type & GROUP_UPDATE_BIT) != 0; 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) { public static boolean isGroupQuit(long type) {
return (type & GROUP_QUIT_BIT) != 0; return (type & GROUP_QUIT_BIT) != 0;
} }

View File

@ -347,8 +347,7 @@ public class SmsDatabase extends MessagingDatabase {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
} else if (message.isGroup()) { } else if (message.isGroup()) {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT; if (((IncomingGroupMessage)message).isUpdateMessage()) type |= Types.GROUP_UPDATE_MESSAGE_BIT;
else if (((IncomingGroupMessage)message).isQuit()) type |= Types.GROUP_QUIT_BIT;
} }
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT; if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;

View File

@ -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.Address.Companion.fromSerialized
import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.GroupRecord
import org.session.libsession.messaging.threads.recipients.Recipient 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.GroupUtil
import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.session.libsession.utilities.TextSecurePreferences 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.get
import org.thoughtcrime.securesms.loki.utilities.getString import org.thoughtcrime.securesms.loki.utilities.getString
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import java.util.*
class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
override fun getUserPublicKey(): String? { override fun getUserPublicKey(): String? {
@ -85,6 +88,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return recipient.profileKey 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) { override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) {
val address = Address.fromSerialized(recipientPublicKey) val address = Address.fromSerialized(recipientPublicKey)
val recipient = Recipient.from(context, address, false) 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) 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<String>, admins: Collection<String>, sentTimestamp: Long) { override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder() val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
.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())
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true) 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) val smsDB = DatabaseFactory.getSmsDatabase(context)
smsDB.insertMessageInbox(infoMessage) smsDB.insertMessageInbox(infoMessage)
} }
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) { override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) {
val userPublicKey = getUserPublicKey() val userPublicKey = getUserPublicKey()
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder()
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID))) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members).toJSON()
.setType(type) val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf())
.setName(name)
.addAllMembers(members)
.addAllAdmins(admins)
val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, sentTimestamp, 0, false, null, listOf(), listOf())
val mmsDB = DatabaseFactory.getMmsDatabase(context) val mmsDB = DatabaseFactory.getMmsDatabase(context)
val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context) val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context)
if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return

View File

@ -109,6 +109,7 @@ public abstract class DisplayRecord {
public boolean isLokiSessionRestoreDone() { return SmsDatabase.Types.isLokiSessionRestoreDoneType(type); } 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() { public boolean isGroupUpdate() {
return SmsDatabase.Types.isGroupUpdate(type); return SmsDatabase.Types.isGroupUpdate(type);
} }
@ -117,8 +118,13 @@ public abstract class DisplayRecord {
return SmsDatabase.Types.isGroupQuit(type); 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() { public boolean isGroupAction() {
return isGroupUpdate() || isGroupQuit(); return isGroupUpdate() || isGroupQuit() || isGroupUpdateMessage();
} }
public boolean isExpirationTimerUpdate() { public boolean isExpirationTimerUpdate() {

View File

@ -24,14 +24,15 @@ import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import network.loki.messenger.R; 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.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.session.libsession.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.session.libsession.database.documents.NetworkFailure; import org.session.libsession.database.documents.NetworkFailure;
import org.session.libsession.messaging.threads.recipients.Recipient; 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; import java.util.List;
@ -90,42 +91,22 @@ public abstract class MessageRecord extends DisplayRecord {
@Override @Override
public SpannableString getDisplayBody(@NonNull Context context) { 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)); return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group));
} else if (isGroupUpdate()) { } 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()) { } 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()) {
return new SpannableString(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString())); 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()); return new SpannableString(getBody());

View File

@ -28,6 +28,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.messaging.threads.recipients.Recipient; 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.session.libsession.utilities.ExpirationUtil;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
@ -73,7 +75,7 @@ public class ThreadRecord extends DisplayRecord {
@Override @Override
public SpannableString getDisplayBody(@NonNull Context context) { public SpannableString getDisplayBody(@NonNull Context context) {
Recipient recipient = getRecipient(); Recipient recipient = getRecipient();
if (isGroupUpdate()) { if (isGroupUpdate() || isGroupUpdateMessage()) {
return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated)); return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated));
} else if (isGroupQuit()) { } else if (isGroupQuit()) {
return emphasisAdded(context.getString(R.string.ThreadRecord_left_the_group)); return emphasisAdded(context.getString(R.string.ThreadRecord_left_the_group));

View File

@ -9,7 +9,6 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos 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.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.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
@ -104,11 +103,11 @@ object ClosedGroupsProtocolV2 {
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
// Notify the user (if we didn't make the group) // Notify the user (if we didn't make the group)
if (userPublicKey != senderPublicKey) { 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) { } else if (prevGroup == null) {
// only notify if we created this group // only notify if we created this group
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) 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 // Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
@ -156,14 +155,14 @@ object ClosedGroupsProtocolV2 {
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers) MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
} }
} }
val (contextType, signalType) = val type =
if (senderLeft) GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT if (senderLeft) SignalServiceGroup.Type.QUIT
else GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE else SignalServiceGroup.Type.UPDATE
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) 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 { } 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) }) groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) 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 { } 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) { if (userPublicKey in admins) {
// send current encryption key to the latest added members // send current encryption key to the latest added members
@ -230,9 +229,9 @@ object ClosedGroupsProtocolV2 {
// Notify the user // Notify the user
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) 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 { } 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 // Notify user
if (userLeft) { if (userLeft) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) 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 { } 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 // Notify the user
val wasSenderRemoved = !members.contains(senderPublicKey) val wasSenderRemoved = !members.contains(senderPublicKey)
val type0 = if (wasSenderRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
val admins = group.admins.map { it.toString() } val admins = group.admins.map { it.toString() }
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) 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 { } 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 // endregion

View File

@ -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<Recipient>
private val removedMembers: MutableList<Recipient>
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<Recipient>): 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)
}
}
}
}

View File

@ -2,22 +2,17 @@ package org.thoughtcrime.securesms.service;
import android.content.Context; import android.content.Context;
import com.google.protobuf.ByteString;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage;
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.session.libsession.messaging.threads.Address; 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.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceGroup; 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.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory; 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 org.thoughtcrime.securesms.mms.MmsException;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -79,8 +73,8 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
String senderPublicKey = message.getSender(); String senderPublicKey = message.getSender();
// Notify the user // Notify the user
if (userPublicKey.equals(senderPublicKey)) { if (senderPublicKey == null || userPublicKey.equals(senderPublicKey)) {
// sender is a linked device // sender is self or a linked device
insertOutgoingExpirationTimerMessage(message); insertOutgoingExpirationTimerMessage(message);
} else { } else {
insertIncomingExpirationTimerMessage(message); insertIncomingExpirationTimerMessage(message);
@ -100,7 +94,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
int duration = message.getDuration(); int duration = message.getDuration();
Optional<SignalServiceGroup> groupInfo = Optional.absent(); Optional<SignalServiceGroup> 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); 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 // 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) { private void insertOutgoingExpirationTimerMessage(ExpirationTimerUpdate message) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
String senderPublicKey = message.getSender();
Long sentTimestamp = message.getSentTimestamp(); Long sentTimestamp = message.getSentTimestamp();
String groupId = message.getGroupPublicKey(); String groupId = message.getGroupPublicKey();
int duration = message.getDuration(); 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); Recipient recipient = Recipient.from(context, address, false);
try { try {
OutgoingExpirationUpdateMessage timerUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, sentTimestamp, duration * 1000L, groupId);
database.insertSecureDecryptedMessageOutbox(timerUpdateMessage, -1, sentTimestamp);
if (groupId != null) { 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 // we need the group ID as recipient for setExpireMessages below
recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupId)), false); 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 //set the timer to the conversation
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration); DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration);

View File

@ -418,6 +418,16 @@
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Vous avez reçu un message chiffré avec une ancienne version de Signal qui nest plus prise en charge. Veuillez demander à lexpéditeur de mettre Session à jour vers la version la plus récente et de renvoyer son message.</string> <string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Vous avez reçu un message chiffré avec une ancienne version de Signal qui nest plus prise en charge. Veuillez demander à lexpéditeur de mettre Session à jour vers la version la plus récente et de renvoyer son message.</string>
<string name="MessageRecord_left_group">Vous avez quitté le groupe</string> <string name="MessageRecord_left_group">Vous avez quitté le groupe</string>
<string name="MessageRecord_you_updated_group">Vous avez mis le groupe à jour.</string> <string name="MessageRecord_you_updated_group">Vous avez mis le groupe à jour.</string>
<string name="MessageRecord_you_created_a_new_group">Vous avez créée un nouveau groupe.</string>
<string name="MessageRecord_s_added_you_to_the_group">%1$s vous a ajouté au groupe.</string>
<string name="MessageRecord_you_renamed_the_group_to_s">Vous avez renommé le groupe en \'%1$s\'</string>
<string name="MessageRecord_s_renamed_the_group_to_s">%1$s a renommé le groupe en \'%2$s\'</string>
<string name="MessageRecord_you_added_s_to_the_group">Vous avez ajouté %1$s au groupe.</string>
<string name="MessageRecord_s_added_s_to_the_group">%1$s a ajouté %2$s au groupe.</string>
<string name="MessageRecord_you_removed_s_from_the_group">Vous avez supprimé %1$s du groupe.</string>
<string name="MessageRecord_s_removed_s_from_the_group">%1$s a supprimé %2$s du groupe.</string>
<string name="MessageRecord_you_were_removed_from_the_group">Vous avez été supprimé du groupe.</string>
<string name="MessageRecord_you">Vous</string>
<string name="MessageRecord_you_called">Vous avez appelé</string> <string name="MessageRecord_you_called">Vous avez appelé</string>
<string name="MessageRecord_called_you">Contact appelé</string> <string name="MessageRecord_called_you">Contact appelé</string>
<string name="MessageRecord_missed_call">Appel manqué</string> <string name="MessageRecord_missed_call">Appel manqué</string>
@ -1483,4 +1493,35 @@ Vous avez reçu un message déchange de clés pour une version de protocole i
<string name="fragment_contact_selection_closed_groups_title">Groupes privés</string> <string name="fragment_contact_selection_closed_groups_title">Groupes privés</string>
<string name="fragment_contact_selection_open_groups_title">Groupes publics</string> <string name="fragment_contact_selection_open_groups_title">Groupes publics</string>
<!-- Next round of translation -->
<string name="menu_apply_button">Appliquer</string>
<string name="menu_done_button">Terminé</string>
<string name="activity_edit_closed_group_title">Modifier le groupe</string>
<string name="activity_edit_closed_group_edit_text_hint">Saisissez un nouveau nom de groupe</string>
<string name="activity_edit_closed_group_edit_members">Membres</string>
<string name="activity_edit_closed_group_add_members">Ajouter des membres</string>
<string name="activity_edit_closed_group_group_name_missing_error">Le nom du groupe ne peut pas être vide</string>
<string name="activity_edit_closed_group_group_name_too_long_error">Veuillez saisir un nom plus court</string>
<string name="activity_edit_closed_group_not_enough_group_members_error">Les groupes doivent avoir au moins un membre</string>
<string name="activity_edit_closed_group_invalid_session_id_error">Un des membres du group a un Session ID invalide</string>
<string name="activity_edit_closed_group_confirm_removal">Êtes vous sûr de vouloir suprimer ce membre du groupe?</string>
<string name="activity_edit_closed_group_member_removed">Membre supprimé du groupe</string>
<string name="fragment_edit_group_bottom_sheet_remove">Supprimer le membre du groupe</string>
<string name="activity_select_contacts_title">Contacts</string>
<string name="dialog_ui_mode_title">Thème</string>
<string name="dialog_ui_mode_option_day">Jour</string>
<string name="dialog_ui_mode_option_night">Nuit</string>
<string name="dialog_ui_mode_option_system_default">Système</string>
<string name="activity_conversation_menu_copy_session_id">Copier le Session ID</string>
<string name="attachment">Pièce jointe</string>
<string name="attachment_type_voice_message">Message vocal</string>
<string name="details">Détails</string>
</resources> </resources>

View File

@ -493,6 +493,16 @@
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">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.</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 Session 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>
<string name="MessageRecord_you_created_a_new_group">You created a new group.</string>
<string name="MessageRecord_s_added_you_to_the_group">%1$s added you to the group.</string>
<string name="MessageRecord_you_renamed_the_group_to_s">You renamed the group to \'%1$s\'</string>
<string name="MessageRecord_s_renamed_the_group_to_s">%1$s renamed the group to \'%2$s\'</string>
<string name="MessageRecord_you_added_s_to_the_group">You added %1$s to the group.</string>
<string name="MessageRecord_s_added_s_to_the_group">%1$s added %2$s to the group.</string>
<string name="MessageRecord_you_removed_s_from_the_group">You removed %1$s from the group.</string>
<string name="MessageRecord_s_removed_s_from_the_group">%1$s removed %2$s from the group.</string>
<string name="MessageRecord_you_were_removed_from_the_group">You were removed from the group.</string>
<string name="MessageRecord_you">You</string>
<string name="MessageRecord_you_called">You called</string> <string name="MessageRecord_you_called">You called</string>
<string name="MessageRecord_called_you">Contact called</string> <string name="MessageRecord_called_you">Contact called</string>
<string name="MessageRecord_missed_call">Missed call</string> <string name="MessageRecord_missed_call">Missed call</string>

View File

@ -35,6 +35,7 @@ interface StorageProtocol {
fun setUserProfilePictureUrl(newProfilePicture: String) fun setUserProfilePictureUrl(newProfilePicture: String)
fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray?
fun getDisplayNameForRecipient(recipientPublicKey: String): String?
fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray)
// Signal Protocol // Signal Protocol
@ -117,9 +118,9 @@ interface StorageProtocol {
fun removeClosedGroupPublicKey(groupPublicKey: String) fun removeClosedGroupPublicKey(groupPublicKey: String)
fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String)
fun removeAllClosedGroupEncryptionKeyPairs(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<String>, admins: Collection<String>, sentTimestamp: Long) name: String, members: Collection<String>, admins: Collection<String>, 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<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long)
fun isClosedGroup(publicKey: String): Boolean fun isClosedGroup(publicKey: String): Boolean
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair> fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair>

View File

@ -33,6 +33,11 @@ class ExpirationTimerUpdate() : ControlMessage() {
this.duration = duration this.duration = duration
} }
internal constructor(duration: Int) : this() {
this.syncTarget = null
this.duration = duration
}
// validation // validation
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false

View File

@ -4,11 +4,13 @@ import static org.session.libsignal.service.internal.push.SignalServiceProtos.Gr
public class IncomingGroupMessage extends IncomingTextMessage { 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); super(base, body);
this.groupContext = groupContext; this.groupID = groupID;
this.updateMessage = updateMessage;
} }
@Override @Override
@ -16,12 +18,6 @@ public class IncomingGroupMessage extends IncomingTextMessage {
return true; return true;
} }
public boolean isUpdate() { public boolean isUpdateMessage() { return updateMessage; }
return groupContext.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
}
public boolean isQuit() {
return groupContext.getType().getNumber() == GroupContext.Type.QUIT_VALUE;
}
} }

View File

@ -1,6 +1,5 @@
package org.session.libsession.messaging.messages.signal; 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.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.threads.DistributionTypes; import org.session.libsession.messaging.threads.DistributionTypes;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
@ -10,15 +9,13 @@ import java.util.LinkedList;
public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage { public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage {
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) { private final String groupId;
super(recipient, "", new LinkedList<Attachment>(), sentTimeMillis,
DistributionTypes.CONVERSATION, expiresIn, true, null, Collections.emptyList(),
Collections.emptyList());
}
public static OutgoingExpirationUpdateMessage from(ExpirationTimerUpdate message, public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn, String groupId) {
Recipient recipient) { super(recipient, "", new LinkedList<Attachment>(), sentTimeMillis,
return new OutgoingExpirationUpdateMessage(recipient, message.getSentTimestamp(), message.getDuration() * 1000); DistributionTypes.CONVERSATION, expiresIn, null, Collections.emptyList(),
Collections.emptyList());
this.groupId = groupId;
} }
@Override @Override
@ -26,4 +23,13 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage
return true; return true;
} }
@Override
public boolean isGroup() {
return groupId != null;
}
public String getGroupId() {
return groupId;
}
} }

View File

@ -19,56 +19,27 @@ import java.util.List;
public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
private final GroupContext group; private final String groupID;
private final boolean isUpdateMessage;
public OutgoingGroupMediaMessage(@NonNull Recipient recipient, public OutgoingGroupMediaMessage(@NonNull Recipient recipient,
@NonNull String encodedGroupContext, @NonNull String body,
@NonNull List<Attachment> avatar, @Nullable String groupId,
long sentTimeMillis,
long expiresIn,
@Nullable QuoteModel quote,
@NonNull List<Contact> contacts,
@NonNull List<LinkPreview> 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<Contact> contacts,
@NonNull List<LinkPreview> previews)
{
super(recipient, Base64.encodeBytes(group.toByteArray()),
new LinkedList<Attachment>() {{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,
@Nullable final Attachment avatar, @Nullable final Attachment avatar,
long sentTime, long sentTime,
long expireIn, long expireIn,
boolean expirationUpdate, boolean updateMessage,
@Nullable QuoteModel quote, @Nullable QuoteModel quote,
@NonNull List<Contact> contacts, @NonNull List<Contact> contacts,
@NonNull List<LinkPreview> previews) @NonNull List<LinkPreview> previews)
{ {
super(recipient, Base64.encodeBytes(group.toByteArray()), super(recipient, body,
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}}, new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
sentTime, sentTime,
DistributionTypes.CONVERSATION, expireIn, expirationUpdate, quote, contacts, previews); DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews);
this.group = group; this.groupID = groupId;
this.isUpdateMessage = updateMessage;
} }
@Override @Override
@ -76,15 +47,11 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
return true; return true;
} }
public boolean isGroupUpdate() { public String getGroupId() {
return group.getType().getNumber() == GroupContext.Type.UPDATE_VALUE; return groupID;
} }
public boolean isGroupQuit() { public boolean isUpdateMessage() {
return group.getType().getNumber() == GroupContext.Type.QUIT_VALUE; return isUpdateMessage;
}
public GroupContext getGroupContext() {
return group;
} }
} }

View File

@ -26,7 +26,6 @@ public class OutgoingMediaMessage {
private final int distributionType; private final int distributionType;
private final int subscriptionId; private final int subscriptionId;
private final long expiresIn; private final long expiresIn;
private final boolean expirationUpdate;
private final QuoteModel outgoingQuote; private final QuoteModel outgoingQuote;
private final List<NetworkFailure> networkFailures = new LinkedList<>(); private final List<NetworkFailure> networkFailures = new LinkedList<>();
@ -37,7 +36,6 @@ public class OutgoingMediaMessage {
public OutgoingMediaMessage(Recipient recipient, String message, public OutgoingMediaMessage(Recipient recipient, String message,
List<Attachment> attachments, long sentTimeMillis, List<Attachment> attachments, long sentTimeMillis,
int subscriptionId, long expiresIn, int subscriptionId, long expiresIn,
boolean expirationUpdate,
int distributionType, int distributionType,
@Nullable QuoteModel outgoingQuote, @Nullable QuoteModel outgoingQuote,
@NonNull List<Contact> contacts, @NonNull List<Contact> contacts,
@ -52,7 +50,6 @@ public class OutgoingMediaMessage {
this.attachments = attachments; this.attachments = attachments;
this.subscriptionId = subscriptionId; this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn; this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate;
this.outgoingQuote = outgoingQuote; this.outgoingQuote = outgoingQuote;
this.contacts.addAll(contacts); this.contacts.addAll(contacts);
@ -69,7 +66,6 @@ public class OutgoingMediaMessage {
this.sentTimeMillis = that.sentTimeMillis; this.sentTimeMillis = that.sentTimeMillis;
this.subscriptionId = that.subscriptionId; this.subscriptionId = that.subscriptionId;
this.expiresIn = that.expiresIn; this.expiresIn = that.expiresIn;
this.expirationUpdate = that.expirationUpdate;
this.outgoingQuote = that.outgoingQuote; this.outgoingQuote = that.outgoingQuote;
this.identityKeyMismatches.addAll(that.identityKeyMismatches); this.identityKeyMismatches.addAll(that.identityKeyMismatches);
@ -89,7 +85,7 @@ public class OutgoingMediaMessage {
previews = Collections.singletonList(linkPreview); previews = Collections.singletonList(linkPreview);
} }
return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1, 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()); previews, Collections.emptyList(), Collections.emptyList());
} }
@ -113,7 +109,7 @@ public class OutgoingMediaMessage {
return false; return false;
} }
public boolean isExpirationUpdate() { return expirationUpdate; } public boolean isExpirationUpdate() { return false; }
public long getSentTimeMillis() { public long getSentTimeMillis() {
return sentTimeMillis; return sentTimeMillis;

View File

@ -19,12 +19,11 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
long sentTimeMillis, long sentTimeMillis,
int distributionType, int distributionType,
long expiresIn, long expiresIn,
boolean expirationUpdate,
@Nullable QuoteModel quote, @Nullable QuoteModel quote,
@NonNull List<Contact> contacts, @NonNull List<Contact> contacts,
@NonNull List<LinkPreview> previews) @NonNull List<LinkPreview> 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) { public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {

View File

@ -264,8 +264,15 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
} else { } else {
storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }), storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }),
null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp) null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp)
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
// Notify the user // 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) storage.setProfileSharing(Address.fromSerialized(groupID), true)
// Add the group to the user's set of public keys to poll for // 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 // Notify the user
val wasSenderRemoved = !members.contains(senderPublicKey) val wasSenderRemoved = !members.contains(senderPublicKey)
val type0 = if (wasSenderRemoved) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.MEMBER_REMOVED
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, group.admins.map { it.toString() }, message.sentTimestamp!!)
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, group.admins.map { it.toString() }, message.sentTimestamp!!)
} }
private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGroupControlMessage) { private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGroupControlMessage) {
@ -399,9 +405,9 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
// sender is a linked device // sender is a linked device
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) 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 { } 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) { if (userPublicKey == senderPublicKey) {
// sender is a linked device // sender is a linked device
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) 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 { } 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) { if (userPublicKey in admins) {
// send current encryption key to the latest added members // send current encryption key to the latest added members
@ -498,17 +504,16 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers) MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
} }
} }
val (contextType, signalType) = val type = if (senderLeft) SignalServiceGroup.Type.QUIT
if (senderLeft) SignalServiceProtos.GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.MEMBER_REMOVED
else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE
// Notify the user // Notify the user
if (userPublicKey == senderPublicKey) { if (userPublicKey == senderPublicKey) {
// sender is a linked device // sender is a linked device
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) 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 { } 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) { if (userLeft) {
//sender is a linked device //sender is a linked device
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) 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 { } 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!!)
} }
} }

View File

@ -15,6 +15,7 @@ import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.ecc.Curve import org.session.libsignal.libsignal.ecc.Curve
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.libsignal.util.guava.Optional 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
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
@ -60,7 +61,7 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
// Notify the user // Notify the user
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, sentTime) storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTime)
// Notify the PN server // Notify the PN server
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
// Fulfill the promise // Fulfill the promise
@ -107,7 +108,7 @@ fun MessageSender.setName(groupPublicKey: String, newName: String) {
// Update the group // Update the group
storage.updateTitle(groupID, newName) storage.updateTitle(groupID, newName)
// Notify the user // Notify the user
val infoType = SignalServiceProtos.GroupContext.Type.UPDATE val infoType = SignalServiceGroup.Type.NAME_CHANGE
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
storage.insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime) storage.insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID, sentTime)
} }
@ -150,9 +151,9 @@ fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List<String>)
send(closedGroupControlMessage, Address.fromSerialized(member)) send(closedGroupControlMessage, Address.fromSerialized(member))
} }
// Notify the user // Notify the user
val infoType = SignalServiceProtos.GroupContext.Type.UPDATE val infoType = SignalServiceGroup.Type.MEMBER_ADDED
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) 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<String>) { fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<String>) {
@ -189,9 +190,9 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers) generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
} }
// Notify the user // Notify the user
val infoType = SignalServiceProtos.GroupContext.Type.UPDATE val infoType = SignalServiceGroup.Type.MEMBER_REMOVED
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) storage.insertOutgoingInfoMessage(context, groupID, infoType, name, membersToRemove, admins, threadID, sentTime)
} }
fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> { fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> {
@ -212,7 +213,7 @@ fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Pro
storage.setActive(groupID, false) storage.setActive(groupID, false)
sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success { sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success {
// Notify the user // Notify the user
val infoType = SignalServiceProtos.GroupContext.Type.QUIT val infoType = SignalServiceGroup.Type.QUIT
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
if (notifyUser) { if (notifyUser) {
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime) storage.insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID, sentTime)

View File

@ -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 ""
}
}

View File

@ -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<String>): 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<String>): 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)
}
}

View File

@ -487,6 +487,16 @@
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">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.</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 Session 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>
<string name="MessageRecord_you_created_a_new_group">You created a new group.</string>
<string name="MessageRecord_s_added_you_to_the_group">%1$s added you to the group.</string>
<string name="MessageRecord_you_renamed_the_group_to_s">You renamed the group to %1$s</string>
<string name="MessageRecord_s_renamed_the_group_to_s">%1$s renamed the group to: %2$s</string>
<string name="MessageRecord_you_added_s_to_the_group">You added %1$s to the group.</string>
<string name="MessageRecord_s_added_s_to_the_group">%1$s added %2$s to the group.</string>
<string name="MessageRecord_you_removed_s_from_the_group">You removed %1$s from the group.</string>
<string name="MessageRecord_s_removed_s_from_the_group">%1$s removed %2$s from the group.</string>
<string name="MessageRecord_you_were_removed_from_the_group">You were removed from the group.</string>
<string name="MessageRecord_you">You</string>
<string name="MessageRecord_you_called">You called</string> <string name="MessageRecord_you_called">You called</string>
<string name="MessageRecord_called_you">Contact called</string> <string name="MessageRecord_called_you">Contact called</string>
<string name="MessageRecord_missed_call">Missed call</string> <string name="MessageRecord_missed_call">Missed call</string>

View File

@ -37,7 +37,11 @@ public class SignalServiceGroup {
UPDATE, UPDATE,
DELIVER, DELIVER,
QUIT, QUIT,
REQUEST_INFO REQUEST_INFO,
CREATION,
NAME_CHANGE,
MEMBER_ADDED,
MEMBER_REMOVED
} }
private final byte[] groupId; private final byte[] groupId;