From cd6c838953b9b593bdbc0533e5f8be4b3b0e2e2c Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 15 Mar 2021 16:22:25 +1100 Subject: [PATCH 01/99] clean --- .../sskenvironment/DataExtractionNotificationManager.kt | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/sskenvironment/DataExtractionNotificationManager.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/DataExtractionNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/DataExtractionNotificationManager.kt deleted file mode 100644 index 0c8fa779d..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/DataExtractionNotificationManager.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.thoughtcrime.securesms.sskenvironment - -class DataExtractionNotificationManager { -} \ No newline at end of file From 471e028cf31dd5ac4e1cd00a5d5e982500c8b4d4 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 16 Mar 2021 14:56:47 +1100 Subject: [PATCH 02/99] implementation of the receiving side of Data Extraction notifications & explicit group updates notifications --- .../securesms/database/MmsSmsColumns.java | 36 ++++++++++++ .../securesms/database/Storage.kt | 56 ++++++++++++++----- .../database/model/DisplayRecord.java | 16 ++++++ .../database/model/MessageRecord.java | 30 ++++++++-- .../securesms/jobs/PushDecryptJob.java | 4 +- .../service/ExpiringMessageManager.java | 1 + app/src/main/res/values/strings.xml | 12 ++++ .../libsession/messaging/StorageProtocol.kt | 4 ++ .../control/DataExtractionNotification.kt | 6 +- .../messages/signal/IncomingMediaMessage.java | 34 ++++++----- .../MessageReceiverHandler.kt | 25 +++++++-- .../DataExtractionNotificationInfoMessage.kt | 16 ++++++ .../api/messages/SignalServiceGroup.java | 6 +- 13 files changed, 206 insertions(+), 40 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt 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 36b86023e..d26fc12b2 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,10 @@ 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_CREATION_BIT = 0x80000; + protected static final long GROUP_NAME_UPDATE_BIT = 0x16000; + protected static final long GROUP_MEMBER_ADDED_BIT = 0x32000; + protected static final long GROUP_MEMBER_REMOVED_BIT = 0x64000; // Encrypted Storage Information XXX public static final long ENCRYPTION_MASK = 0xFF000000; @@ -84,6 +88,8 @@ public interface MmsSmsColumns { // Loki protected static final long ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT = 0x01000000; protected static final long ENCRYPTION_LOKI_SESSION_RESTORE_DONE_BIT = 0x00100000; + protected static final long DATA_EXTRACTION_SCREENSHOT_BIT = 0x00010000; + protected static final long DATA_EXTRACTION_MEDIA_SAVED_BIT = 0x00001000; public static boolean isDraftMessageType(long type) { return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE; @@ -197,6 +203,16 @@ public interface MmsSmsColumns { return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0; } + // DATA EXTRACTION NOTIFICATION + + public static boolean isDataExtractionScreenshotUpdate(long type) { + return (type & DATA_EXTRACTION_SCREENSHOT_BIT) != 0; + } + + public static boolean isDataExtractionMediaSavedUpdate(long type) { + return (type & DATA_EXTRACTION_MEDIA_SAVED_BIT) != 0; + } + public static boolean isIncomingCall(long type) { return type == INCOMING_CALL_TYPE; } @@ -209,14 +225,34 @@ public interface MmsSmsColumns { return type == MISSED_CALL_TYPE; } + // GROUPS EXPLICIT UPDATES + public static boolean isGroupUpdate(long type) { return (type & GROUP_UPDATE_BIT) != 0; } + public static boolean isGroupCreation(long type) { + return (type & GROUP_CREATION_BIT) != 0; + } + + public static boolean isGroupNameUpdate(long type) { + return (type & GROUP_NAME_UPDATE_BIT) != 0; + } + + public static boolean isGroupMemberAdded(long type) { + return (type & GROUP_MEMBER_ADDED_BIT) != 0; + } + + public static boolean isGroupMemberRemoved(long type) { + return (type & GROUP_MEMBER_REMOVED_BIT) != 0; + } + public static boolean isGroupQuit(long type) { return (type & GROUP_QUIT_BIT) != 0; } + + public static boolean isFailedDecryptType(long type) { return (type & ENCRYPTION_REMOTE_FAILED_BIT) != 0; } 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 6a0d42777..57c6a3410 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -8,18 +8,23 @@ import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment +import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel 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.utilities.GroupUtil +import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.guava.Optional @@ -29,21 +34,13 @@ import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.session.libsignal.utilities.logging.Log -import org.session.libsession.utilities.IdentityKeyUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.getString -import org.session.libsession.messaging.messages.signal.IncomingMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.thoughtcrime.securesms.mms.PartAuthority -import org.session.libsession.messaging.messages.signal.IncomingGroupMessage -import org.session.libsession.messaging.messages.signal.IncomingTextMessage -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage -import org.session.libsession.utilities.preferences.ProfileKeyUtil class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { @@ -125,17 +122,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, PointerAttachment.forPointer(Optional.of(it)).orNull() } val mediaMessage = OutgoingMediaMessage.from(message, Recipient.from(context, targetAddress, false), attachments, quote.orNull(), linkPreviews.orNull().firstOrNull()) - mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) + mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID + ?: -1, message.sentTimestamp!!) } else { // It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment val attachments: Optional> = Optional.of(message.attachmentIDs.mapNotNull { DatabaseFactory.getAttachmentProvider(context).getSignalAttachmentPointer(it) }) - val mediaMessage = IncomingMediaMessage.from(message, senderAddress, senderRecipient.expireMessages * 1000L, group, attachments, quote, linkPreviews) + // FIXME deal with DataExtraction parameter + val mediaMessage = IncomingMediaMessage.from(message, senderAddress, senderRecipient.expireMessages * 1000L, group, attachments, quote, linkPreviews, Optional.absent()) if (group.isPresent) { - mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) + mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID + ?: -1, message.sentTimestamp!!) } else { - mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1) + mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID + ?: -1) } } if (insertResult.isPresent) { @@ -157,7 +158,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } val textMessage = OutgoingTextMessage.from(message, Recipient.from(context, targetAddress, false)) - smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!) + smsDatabase.insertMessageOutbox(message.threadID + ?: -1, textMessage, message.sentTimestamp!!) } else { val textMessage = IncomingTextMessage.from(message, senderAddress, group, senderRecipient.expireMessages * 1000L) if (group.isPresent) { @@ -417,7 +419,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val infoMessage = OutgoingGroupMediaMessage(recipient, groupContextBuilder.build(), null, sentTimestamp, 0, null, listOf(), listOf()) val mmsDB = DatabaseFactory.getMmsDatabase(context) val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context) - if (mmsSmsDB.getMessageFor(sentTimestamp,userPublicKey) != null) return + if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null) mmsDB.markAsSent(infoMessageID, true) } @@ -538,4 +540,30 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun getAttachmentThumbnailUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentThumbnailUri(attachmentId) } + + // Data Extraction Notification + override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, groupID: String?, sentTimestamp: Long) { + val database = DatabaseFactory.getMmsDatabase(context) + val address = fromSerialized(senderPublicKey) + val recipient = Recipient.from(context, address, false) + + if (recipient.isBlocked) return + + var groupInfo = Optional.absent() + if (groupID != null) { + groupInfo = Optional.of(SignalServiceGroup(groupID.toByteArray(), SignalServiceGroup.GroupType.SIGNAL)) + } + val mediaMessage = IncomingMediaMessage(address, sentTimestamp, -1, + 0, false, + false, + Optional.absent(), + groupInfo, + Optional.absent(), + Optional.absent(), + Optional.absent(), + Optional.absent(), + Optional.of(message)) + + database.insertSecureDecryptedMessageInbox(mediaMessage, -1) + } } \ No newline at end of file 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 ab40f3381..cf712351e 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 @@ -113,6 +113,14 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isGroupUpdate(type); } + public boolean isGroupCreation() { return MmsSmsColumns.Types.isGroupCreation(type); } + + public boolean isGroupNameUpdate() { return MmsSmsColumns.Types.isGroupNameUpdate(type); } + + public boolean isGroupMemberAdded() { return MmsSmsColumns.Types.isGroupMemberAdded(type); } + + public boolean isGroupMemberRemoved() { return MmsSmsColumns.Types.isGroupMemberRemoved(type); } + public boolean isGroupQuit() { return SmsDatabase.Types.isGroupQuit(type); } @@ -125,6 +133,14 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isExpirationTimerUpdate(type); } + public boolean isDataExtractionScreenshot() { + return MmsSmsColumns.Types.isDataExtractionScreenshotUpdate(type); + } + + public boolean isDataExtractionMediaSaved() { + return MmsSmsColumns.Types.isDataExtractionMediaSavedUpdate(type); + } + public boolean isCallLog() { return SmsDatabase.Types.isCallLog(type); } 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 b41e4ae78..a043e7483 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 @@ -90,10 +90,26 @@ public abstract class MessageRecord extends DisplayRecord { @Override public SpannableString getDisplayBody(@NonNull Context context) { - if (isGroupUpdate() && isOutgoing()) { + if (isGroupCreation() && isOutgoing()) { + return new SpannableString(context.getString(R.string.MessageRecord_you_created_a_new_group)); + } else if (isGroupCreation()) { + return new SpannableString(context.getString(R.string.MessageRecord_s_created_a_new_group, getIndividualRecipient().toShortString())); + } 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())); + } else if (isGroupNameUpdate() && isOutgoing()) { + return new SpannableString(context.getString(R.string.MessageRecord_you_renamed_the_group)); + } else if (isGroupNameUpdate()) { + return new SpannableString(context.getString(R.string.MessageRecord_s_renamed_the_group, getIndividualRecipient().toShortString())); + } else if (isGroupMemberAdded() && isOutgoing()) { + return new SpannableString(context.getString(R.string.MessageRecord_you_added_new_members_to_the_group)); + } else if (isGroupMemberAdded()) { + return new SpannableString(context.getString(R.string.MessageRecord_s_added_new_members_to_the_group, getIndividualRecipient().toShortString())); + } else if (isGroupMemberRemoved() && isOutgoing()) { + return new SpannableString(context.getString(R.string.MessageRecord_you_added_new_members_to_the_group)); + } else if (isGroupMemberRemoved()) { + return new SpannableString(context.getString(R.string.MessageRecord_s_removed_members_from_the_group, getIndividualRecipient().toShortString())); } else if (isGroupQuit() && isOutgoing()) { return new SpannableString(context.getString(R.string.MessageRecord_left_group)); } else if (isGroupQuit()) { @@ -107,14 +123,20 @@ public abstract class MessageRecord extends DisplayRecord { } else if (isJoined()) { return new SpannableString(context.getString(R.string.MessageRecord_s_joined_signal, getIndividualRecipient().toShortString())); } else if (isExpirationTimerUpdate()) { - int seconds = (int)(getExpiresIn() / 1000); + 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())); + : 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)); + : new SpannableString(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, getIndividualRecipient().toShortString(), time)); + } else if (isDataExtractionScreenshot()) { + return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_took_a_screenshot)) + : new SpannableString(context.getString(R.string.MessageRecord_s_took_a_screenshot, getIndividualRecipient().toShortString())); + } else if (isDataExtractionMediaSaved()) { + return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_media_saved_by_you)) + : new SpannableString(context.getString(R.string.MessageRecord_media_saved_by_s, getIndividualRecipient().toShortString())); } else if (isIdentityUpdate()) { return new SpannableString(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().toShortString())); } else if (isIdentityVerified()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 6ab83939b..46f99eae9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -272,6 +272,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Optional.absent(), Optional.absent(), Optional.absent(), + Optional.absent(), Optional.absent()); database.insertSecureDecryptedMessageInbox(mediaMessage, -1); @@ -371,9 +372,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } else { + // FIXME handle DataExtraction parameter below where Optional.absent() is IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(), - quote, sharedContacts, linkPreviews); + quote, sharedContacts, linkPreviews, Optional.absent()); MmsDatabase database = DatabaseFactory.getMmsDatabase(context); database.beginTransaction(); 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 77eee247c..646f6756e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -86,6 +86,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Optional.absent(), Optional.absent(), Optional.absent(), + Optional.absent(), Optional.absent()); database.insertSecureDecryptedMessageInbox(mediaMessage, -1); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 34ab7d89b..2d3b0ff67 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -493,6 +493,14 @@ 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 created a new group. + You renamed the group. + %1$s renamed the group. + You added new members to the group. + %1$s added new members to the group. + You removed members from the group. + %1$s removed members from the group. You called Contact called Missed call @@ -505,6 +513,10 @@ %1$s disabled disappearing messages. You set the disappearing message timer to %1$s %1$s set the disappearing message timer to %2$s + You took a screenshot. + %1$s took a screenshot. + Media saved by you. + Media saved by %1$s. Your safety number with %s has changed. You marked your safety number with %s verified You marked your safety number with %s verified from another device 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 2c54c3838..dceee60ac 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -11,6 +11,7 @@ import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId +import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.threads.Address @@ -152,4 +153,7 @@ interface StorageProtocol { // Message Handling /// Returns the ID of the `TSIncomingMessage` that was constructed. fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?): Long? + + // Data Extraction Notification + fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, groupID: String?, sentTimestamp: Long) } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt index a612584af..e963a8e1e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt @@ -12,7 +12,7 @@ class DataExtractionNotification(): ControlMessage() { // Kind enum sealed class Kind { class Screenshot() : Kind() - class MediaSaved(val timestanp: Long) : Kind() + class MediaSaved(val timestamp: Long) : Kind() val description: String = run { when(this) { @@ -50,7 +50,7 @@ class DataExtractionNotification(): ControlMessage() { val kind = kind ?: return false return when(kind) { is Kind.Screenshot -> true - is Kind.MediaSaved -> kind.timestanp > 0 + is Kind.MediaSaved -> kind.timestamp > 0 } } @@ -66,7 +66,7 @@ class DataExtractionNotification(): ControlMessage() { is Kind.Screenshot -> dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT is Kind.MediaSaved -> { dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED - dataExtractionNotification.timestamp = kind.timestanp + dataExtractionNotification.timestamp = kind.timestamp } } val contentProto = SignalServiceProtos.Content.newBuilder() diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index aa330ce22..8fbc0ef88 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -3,6 +3,7 @@ package org.session.libsession.messaging.messages.signal; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment; +import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; @@ -26,9 +27,11 @@ public class IncomingMediaMessage { private final int subscriptionId; private final long expiresIn; private final boolean expirationUpdate; - private final QuoteModel quote; private final boolean unidentified; + private final DataExtractionNotificationInfoMessage dataExtractionNotification; + private final QuoteModel quote; + private final List attachments = new LinkedList<>(); private final List sharedContacts = new LinkedList<>(); private final List linkPreviews = new LinkedList<>(); @@ -44,17 +47,19 @@ public class IncomingMediaMessage { Optional> attachments, Optional quote, Optional> sharedContacts, - Optional> linkPreviews) + Optional> linkPreviews, + Optional dataExtractionNotification) { - this.push = true; - this.from = from; - this.sentTimeMillis = sentTimeMillis; - this.body = body.orNull(); - this.subscriptionId = subscriptionId; - this.expiresIn = expiresIn; - this.expirationUpdate = expirationUpdate; - this.quote = quote.orNull(); - this.unidentified = unidentified; + this.push = true; + this.from = from; + this.sentTimeMillis = sentTimeMillis; + this.body = body.orNull(); + this.subscriptionId = subscriptionId; + this.expiresIn = expiresIn; + this.expirationUpdate = expirationUpdate; + this.dataExtractionNotification = dataExtractionNotification.orNull(); + this.quote = quote.orNull(); + this.unidentified = unidentified; if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.INSTANCE.getEncodedId(group.get())); else this.groupId = null; @@ -70,10 +75,11 @@ public class IncomingMediaMessage { Optional group, Optional> attachments, Optional quote, - Optional> linkPreviews) + Optional> linkPreviews, + Optional dataExtractionNotification) { return new IncomingMediaMessage(from, message.getReceivedTimestamp(), -1, expiresIn, false, - false, Optional.fromNullable(message.getText()), group, attachments, quote, Optional.absent(), linkPreviews); + false, Optional.fromNullable(message.getText()), group, attachments, quote, Optional.absent(), linkPreviews, dataExtractionNotification); } public int getSubscriptionId() { @@ -128,6 +134,8 @@ public class IncomingMediaMessage { return linkPreviews; } + public DataExtractionNotificationInfoMessage getDataExtractionNotification() { return dataExtractionNotification; } + public boolean isUnidentified() { return unidentified; } 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 114b0ba32..1f0a130d0 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 @@ -9,6 +9,7 @@ import org.session.libsession.messaging.messages.control.* import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment +import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel @@ -43,6 +44,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, is TypingIndicator -> handleTypingIndicator(message) is ClosedGroupControlMessage -> handleClosedGroupControlMessage(message) is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message, proto) + is DataExtractionNotification -> handleDataExtractionNotification(message) is ConfigurationMessage -> handleConfigurationMessage(message) is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID) } @@ -102,6 +104,21 @@ fun MessageReceiver.disableExpirationTimer(message: ExpirationTimerUpdate, proto SSKEnvironment.shared.messageExpirationManager.disableExpirationTimer(id, senderPublicKey, proto) } +// Data Extraction Notification handling + +private fun MessageReceiver.handleDataExtractionNotification(message: DataExtractionNotification) { + val storage = MessagingConfiguration.shared.storage + val senderPublicKey = message.sender!! + val notification: DataExtractionNotificationInfoMessage = when(message.kind) { + is DataExtractionNotification.Kind.Screenshot -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.SCREENSHOT) + is DataExtractionNotification.Kind.MediaSaved -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.MEDIASAVED) + else -> return + } + storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.groupPublicKey, message.sentTimestamp!!) +} + +// Configuration message handling + private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMessage) { val context = MessagingConfiguration.shared.context val storage = MessagingConfiguration.shared.storage @@ -239,7 +256,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }), null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp) // Notify the user - storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) + storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.NEW, name, members, admins, sentTimestamp) } storage.setProfileSharing(Address.fromSerialized(groupID), true) // Add the group to the user's set of public keys to poll for @@ -356,7 +373,7 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon val name = kind.name storage.updateTitle(groupID, name) - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.NAME_UPDATE, name, members, admins, message.sentTimestamp!!) } private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupControlMessage) { @@ -384,7 +401,7 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, 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, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.MEMBER_ADDED, name, members, admins, message.sentTimestamp!!) } if (userPublicKey in admins) { // send current encryption key to the latest added members @@ -445,7 +462,7 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup } val (contextType, signalType) = if (senderLeft) SignalServiceProtos.GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT - else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE + else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.MEMBER_REMOVED storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, message.sentTimestamp!!) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt new file mode 100644 index 000000000..64d7af7ff --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt @@ -0,0 +1,16 @@ +package org.session.libsession.messaging.sending_receiving.dataextraction + +class DataExtractionNotificationInfoMessage { + + enum class Kind { + SCREENSHOT, + MEDIASAVED + } + + var kind: Kind? = null + + constructor(kind: Kind?) { + this.kind = kind + } + +} \ No newline at end of file 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 b50353109..fa84a389f 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, + NEW, + NAME_UPDATE, + MEMBER_ADDED, + MEMBER_REMOVED } private final byte[] groupId; From 9d82b4b7a90905d60634fb3e98df44acb8e081e1 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Wed, 17 Mar 2021 14:26:29 +1100 Subject: [PATCH 03/99] sending media saved notification --- .../org/thoughtcrime/securesms/MediaPreviewActivity.java | 9 +++++++++ .../securesms/conversation/ConversationFragment.java | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 6399dbc02..3308b0f99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -53,6 +53,8 @@ import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; +import org.session.libsession.messaging.messages.control.DataExtractionNotification; +import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.recipients.Recipient; @@ -353,11 +355,18 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im saveTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null)); + // Sending a Data extraction notification + sendMediaSavedNotificationIfNeeded(); }) .execute(); }); } + private void sendMediaSavedNotificationIfNeeded() { + DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); + MessageSender.send(message, conversationRecipient.getAddress()); + } + @SuppressLint("StaticFieldLeak") private void deleteMedia() { MediaItem mediaItem = getCurrentMediaItem(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 0d30b8ce9..4bf0cf905 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -57,6 +57,7 @@ import androidx.recyclerview.widget.RecyclerView.OnScrollListener; import com.annimon.stream.Stream; +import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.messages.visible.Quote; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.opengroups.OpenGroupAPI; @@ -745,6 +746,8 @@ public class ConversationFragment extends Fragment if (!Util.isEmpty(attachments)) { SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity()); saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0])); + // Sending a Data extraction notification + sendMediaSavedNotificationIfNeeded(); return; } @@ -757,6 +760,11 @@ public class ConversationFragment extends Fragment }); } + private void sendMediaSavedNotificationIfNeeded() { + DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); + MessageSender.send(message, recipient.getAddress()); + } + @Override public @NonNull Loader onCreateLoader(int id, Bundle args) { Log.i(TAG, "onCreateLoader"); From 2ca8b1acb1b34a181a23a524b0a7d0f88b6b5f87 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Wed, 17 Mar 2021 15:08:09 +1100 Subject: [PATCH 04/99] missed case for sending media saved notification --- .../securesms/MediaOverviewActivity.java | 12 ++++++++++++ .../thoughtcrime/securesms/MediaPreviewActivity.java | 3 +++ .../securesms/conversation/ConversationFragment.java | 3 +++ 3 files changed, 18 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java index 0056718e3..39e817037 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -52,6 +52,8 @@ import androidx.viewpager.widget.ViewPager; import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager; import com.google.android.material.tabs.TabLayout; +import org.session.libsession.messaging.messages.control.DataExtractionNotification; +import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.threads.Address; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.MediaDatabase; @@ -351,6 +353,8 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { saveTask.executeOnExecutor(THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[attachments.size()])); actionMode.finish(); + // Sending a Data extraction notification + sendMediaSavedNotificationIfNeeded(); } }.execute(); }) @@ -358,6 +362,14 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { }, mediaRecords.size()); } + /** + * Send a MediaSaved notification to the recipient + */ + private void sendMediaSavedNotificationIfNeeded() { + DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); + MessageSender.send(message, recipient.getAddress()); + } + @SuppressLint("StaticFieldLeak") private void handleDeleteMedia(@NonNull Collection mediaRecords) { int recordCount = mediaRecords.size(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 3308b0f99..5775ce936 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -362,6 +362,9 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im }); } + /** + * Send a MediaSaved notification to the recipient + */ private void sendMediaSavedNotificationIfNeeded() { DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, conversationRecipient.getAddress()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 4bf0cf905..b833ad6c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -760,6 +760,9 @@ public class ConversationFragment extends Fragment }); } + /** + * Send a MediaSaved notification to the recipient + */ private void sendMediaSavedNotificationIfNeeded() { DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, recipient.getAddress()); From ddede475b4b3e6d0a55eb27fc889813283694579 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 19 Mar 2021 16:25:00 +1100 Subject: [PATCH 05/99] Start of Update messages builder class implementation & usage for Input group updates --- .../securesms/database/Storage.kt | 9 ++- .../loki/protocol/ClosedGroupsProtocolV2.kt | 14 ++-- app/src/main/res/values/strings.xml | 8 ++ .../libsession/messaging/StorageProtocol.kt | 1 + .../MessageReceiverHandler.kt | 10 +-- .../utilities/UpdateMessageBuilder.kt | 74 +++++++++++++++++++ libsession/src/main/res/values/strings.xml | 8 ++ .../api/messages/SignalServiceGroup.java | 6 +- 8 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt 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 3a5fe5147..03d86dd62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -19,6 +19,7 @@ import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.threads.Address 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.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.libsignal.ecc.ECKeyPair @@ -79,6 +80,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 getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == null) { @@ -397,7 +403,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, .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, System.currentTimeMillis(), "", Optional.of(group), 0, true) - val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "") + val messageBody = UpdateMessageBuilder.buildGroupUpdateMessage(context, group, senderPublicKey) + val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), messageBody) val smsDB = DatabaseFactory.getSmsDatabase(context) smsDB.insertMessageInbox(infoMessage) } 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 9fba4b66e..9aa4ddd38 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 @@ -33,6 +33,7 @@ import org.session.libsignal.utilities.Hex import org.session.libsession.messaging.threads.Address 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.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences @@ -361,7 +362,7 @@ object ClosedGroupsProtocolV2 { apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) // Notify the user (if we didn't make the group) if (userPublicKey != senderPublicKey) { - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) + insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.NEW_GROUP, name, members, admins, sentTimestamp) } else if (prevGroup == null) { // only notify if we created this group val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) @@ -415,12 +416,12 @@ object ClosedGroupsProtocolV2 { } val (contextType, signalType) = if (senderLeft) GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT - else GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE + else GroupContext.Type.UPDATE to SignalServiceGroup.Type.MEMBER_REMOVED if (userPublicKey == senderPublicKey) { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, sentTimestamp) } else { - insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, sentTimestamp) + insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, updateMembers, admins, sentTimestamp) } } @@ -450,7 +451,7 @@ object ClosedGroupsProtocolV2 { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) } else { - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) + insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.MEMBER_ADDED, name, updateMembers, admins, sentTimestamp) } if (userPublicKey in admins) { // send current encryption key to the latest added members @@ -489,7 +490,7 @@ object ClosedGroupsProtocolV2 { val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID, sentTimestamp) } else { - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) + insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins, sentTimestamp) } } @@ -608,7 +609,8 @@ object ClosedGroupsProtocolV2 { .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 infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "") + val messageBody = UpdateMessageBuilder.buildGroupUpdateMessage(context, group, senderPublicKey) + val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), messageBody) val smsDB = DatabaseFactory.getSmsDatabase(context) smsDB.insertMessageInbox(infoMessage) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ff4e7f61..09c7b0f1b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -493,6 +493,14 @@ 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 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 e3062f73e..5c66b2bd0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -34,6 +34,7 @@ interface StorageProtocol { fun getUserProfilePictureURL(): String? fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? + fun getDisplayNameForRecipient(recipientPublicKey: String): String? // Signal Protocol 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 530badd32..740c18179 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 @@ -239,7 +239,7 @@ private fun handleNewClosedGroup(sender: String, groupPublicKey: String, name: S storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }), null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp) // Notify the user - storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.NEW_GROUP, name, members, admins) } storage.setProfileSharing(Address.fromSerialized(groupID), true) // Add the group to the user's set of public keys to poll for @@ -356,7 +356,7 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon val name = kind.name storage.updateTitle(groupID, name) - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins) } private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupControlMessage) { @@ -388,7 +388,7 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo MessageSender.sendLatestEncryptionKeyPair(member, groupPublicKey) } } - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.MEMBER_ADDED, name, updateMembers, admins) } private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroupControlMessage) { @@ -435,9 +435,9 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup } val (contextType, signalType) = if (senderLeft) SignalServiceProtos.GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT - else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE + else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.MEMBER_REMOVED - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, updateMembers, admins) } private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupControlMessage) { 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 000000000..bcc0d4761 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -0,0 +1,74 @@ +package org.session.libsession.messaging.utilities + +import android.content.Context +import org.session.libsession.R +import org.session.libsession.messaging.MessagingConfiguration +import org.session.libsignal.service.api.messages.SignalServiceGroup +import org.session.libsignal.service.internal.push.SignalServiceProtos + +object UpdateMessageBuilder { + + fun buildGroupUpdateMessage(context: Context, groupInfo: SignalServiceGroup, sender: String, isOutgoing: Boolean = false): String { + val updateType = groupInfo.type + val senderName: String = if (!isOutgoing) { + MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender) ?: sender + } else { sender } + var message: String = "" + when (updateType) { + SignalServiceGroup.Type.NEW_GROUP -> { + 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, groupInfo.name.get()) + } else { + context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, groupInfo.name.get()) + } + } + SignalServiceGroup.Type.MEMBER_ADDED -> { + val members = groupInfo.members.get().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 members = groupInfo.members.get().joinToString(", ") { + MessagingConfiguration.shared.storage.getDisplayNameForRecipient(it) ?: it + } + message = 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(): String { + return "" + } + + fun buildDataExtractionMessage(): String { + return "" + } + +} \ No newline at end of file diff --git a/libsession/src/main/res/values/strings.xml b/libsession/src/main/res/values/strings.xml index 70b424bd2..ea6a33b70 100644 --- a/libsession/src/main/res/values/strings.xml +++ b/libsession/src/main/res/values/strings.xml @@ -487,6 +487,14 @@ 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 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 b50353109..68856a87c 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, + NEW_GROUP, + NAME_CHANGE, + MEMBER_ADDED, + MEMBER_REMOVED } private final byte[] groupId; From 5dcb3d77d4468be50b571641a1d939f5c31a13ff Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 19 Mar 2021 16:39:41 +1100 Subject: [PATCH 06/99] revert changes regarding explicit group updates as it's now in a separate PR --- .../securesms/database/MmsSmsColumns.java | 36 ------------------- .../database/model/DisplayRecord.java | 16 --------- .../database/model/MessageRecord.java | 26 +------------- app/src/main/res/values/strings.xml | 8 ----- .../MessageReceiverHandler.kt | 8 ++--- .../api/messages/SignalServiceGroup.java | 6 +--- 6 files changed, 6 insertions(+), 94 deletions(-) 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 d26fc12b2..36b86023e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -70,10 +70,6 @@ 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_CREATION_BIT = 0x80000; - protected static final long GROUP_NAME_UPDATE_BIT = 0x16000; - protected static final long GROUP_MEMBER_ADDED_BIT = 0x32000; - protected static final long GROUP_MEMBER_REMOVED_BIT = 0x64000; // Encrypted Storage Information XXX public static final long ENCRYPTION_MASK = 0xFF000000; @@ -88,8 +84,6 @@ public interface MmsSmsColumns { // Loki protected static final long ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT = 0x01000000; protected static final long ENCRYPTION_LOKI_SESSION_RESTORE_DONE_BIT = 0x00100000; - protected static final long DATA_EXTRACTION_SCREENSHOT_BIT = 0x00010000; - protected static final long DATA_EXTRACTION_MEDIA_SAVED_BIT = 0x00001000; public static boolean isDraftMessageType(long type) { return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE; @@ -203,16 +197,6 @@ public interface MmsSmsColumns { return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0; } - // DATA EXTRACTION NOTIFICATION - - public static boolean isDataExtractionScreenshotUpdate(long type) { - return (type & DATA_EXTRACTION_SCREENSHOT_BIT) != 0; - } - - public static boolean isDataExtractionMediaSavedUpdate(long type) { - return (type & DATA_EXTRACTION_MEDIA_SAVED_BIT) != 0; - } - public static boolean isIncomingCall(long type) { return type == INCOMING_CALL_TYPE; } @@ -225,34 +209,14 @@ public interface MmsSmsColumns { return type == MISSED_CALL_TYPE; } - // GROUPS EXPLICIT UPDATES - public static boolean isGroupUpdate(long type) { return (type & GROUP_UPDATE_BIT) != 0; } - public static boolean isGroupCreation(long type) { - return (type & GROUP_CREATION_BIT) != 0; - } - - public static boolean isGroupNameUpdate(long type) { - return (type & GROUP_NAME_UPDATE_BIT) != 0; - } - - public static boolean isGroupMemberAdded(long type) { - return (type & GROUP_MEMBER_ADDED_BIT) != 0; - } - - public static boolean isGroupMemberRemoved(long type) { - return (type & GROUP_MEMBER_REMOVED_BIT) != 0; - } - public static boolean isGroupQuit(long type) { return (type & GROUP_QUIT_BIT) != 0; } - - public static boolean isFailedDecryptType(long type) { return (type & ENCRYPTION_REMOTE_FAILED_BIT) != 0; } 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 cf712351e..ab40f3381 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 @@ -113,14 +113,6 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isGroupUpdate(type); } - public boolean isGroupCreation() { return MmsSmsColumns.Types.isGroupCreation(type); } - - public boolean isGroupNameUpdate() { return MmsSmsColumns.Types.isGroupNameUpdate(type); } - - public boolean isGroupMemberAdded() { return MmsSmsColumns.Types.isGroupMemberAdded(type); } - - public boolean isGroupMemberRemoved() { return MmsSmsColumns.Types.isGroupMemberRemoved(type); } - public boolean isGroupQuit() { return SmsDatabase.Types.isGroupQuit(type); } @@ -133,14 +125,6 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isExpirationTimerUpdate(type); } - public boolean isDataExtractionScreenshot() { - return MmsSmsColumns.Types.isDataExtractionScreenshotUpdate(type); - } - - public boolean isDataExtractionMediaSaved() { - return MmsSmsColumns.Types.isDataExtractionMediaSavedUpdate(type); - } - public boolean isCallLog() { return SmsDatabase.Types.isCallLog(type); } 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 a043e7483..8ac7fac42 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 @@ -90,26 +90,8 @@ public abstract class MessageRecord extends DisplayRecord { @Override public SpannableString getDisplayBody(@NonNull Context context) { - if (isGroupCreation() && isOutgoing()) { - return new SpannableString(context.getString(R.string.MessageRecord_you_created_a_new_group)); - } else if (isGroupCreation()) { - return new SpannableString(context.getString(R.string.MessageRecord_s_created_a_new_group, getIndividualRecipient().toShortString())); - } else if (isGroupUpdate() && isOutgoing()) { - return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group)); - } else if (isGroupUpdate()) { + if (isGroupUpdate() && isOutgoing()) { return new SpannableString(GroupDescription.Companion.getDescription(context, getBody()).toString(getIndividualRecipient())); - } else if (isGroupNameUpdate() && isOutgoing()) { - return new SpannableString(context.getString(R.string.MessageRecord_you_renamed_the_group)); - } else if (isGroupNameUpdate()) { - return new SpannableString(context.getString(R.string.MessageRecord_s_renamed_the_group, getIndividualRecipient().toShortString())); - } else if (isGroupMemberAdded() && isOutgoing()) { - return new SpannableString(context.getString(R.string.MessageRecord_you_added_new_members_to_the_group)); - } else if (isGroupMemberAdded()) { - return new SpannableString(context.getString(R.string.MessageRecord_s_added_new_members_to_the_group, getIndividualRecipient().toShortString())); - } else if (isGroupMemberRemoved() && isOutgoing()) { - return new SpannableString(context.getString(R.string.MessageRecord_you_added_new_members_to_the_group)); - } else if (isGroupMemberRemoved()) { - return new SpannableString(context.getString(R.string.MessageRecord_s_removed_members_from_the_group, getIndividualRecipient().toShortString())); } else if (isGroupQuit() && isOutgoing()) { return new SpannableString(context.getString(R.string.MessageRecord_left_group)); } else if (isGroupQuit()) { @@ -131,12 +113,6 @@ public abstract class MessageRecord extends DisplayRecord { 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 (isDataExtractionScreenshot()) { - return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_took_a_screenshot)) - : new SpannableString(context.getString(R.string.MessageRecord_s_took_a_screenshot, getIndividualRecipient().toShortString())); - } else if (isDataExtractionMediaSaved()) { - return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_media_saved_by_you)) - : new SpannableString(context.getString(R.string.MessageRecord_media_saved_by_s, getIndividualRecipient().toShortString())); } else if (isIdentityUpdate()) { return new SpannableString(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().toShortString())); } else if (isIdentityVerified()) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2d3b0ff67..166f8c42d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -493,14 +493,6 @@ 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 created a new group. - You renamed the group. - %1$s renamed the group. - You added new members to the group. - %1$s added new members to the group. - You removed members from the group. - %1$s removed members from the group. You called Contact called Missed call 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 1f0a130d0..64a4e39c6 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 @@ -256,7 +256,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }), null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp) // Notify the user - storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.NEW, name, members, admins, sentTimestamp) + storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) } storage.setProfileSharing(Address.fromSerialized(groupID), true) // Add the group to the user's set of public keys to poll for @@ -373,7 +373,7 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon val name = kind.name storage.updateTitle(groupID, name) - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.NAME_UPDATE, name, members, admins, message.sentTimestamp!!) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!) } private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupControlMessage) { @@ -401,7 +401,7 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, message.sentTimestamp!!) } else { - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.MEMBER_ADDED, name, members, admins, message.sentTimestamp!!) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!) } if (userPublicKey in admins) { // send current encryption key to the latest added members @@ -462,7 +462,7 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup } val (contextType, signalType) = if (senderLeft) SignalServiceProtos.GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT - else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.MEMBER_REMOVED + else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, message.sentTimestamp!!) } 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 fa84a389f..b50353109 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,11 +37,7 @@ public class SignalServiceGroup { UPDATE, DELIVER, QUIT, - REQUEST_INFO, - NEW, - NAME_UPDATE, - MEMBER_ADDED, - MEMBER_REMOVED + REQUEST_INFO } private final byte[] groupId; From a47113f2c5e0ff3c378452397b56c4b860c5bde8 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 19 Mar 2021 16:57:00 +1100 Subject: [PATCH 07/99] revert changes --- .../securesms/database/model/MessageRecord.java | 8 +++++--- .../libsession/messaging/jobs/MessageReceiveJob.kt | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) 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 8ac7fac42..b41e4ae78 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 @@ -91,6 +91,8 @@ public abstract class MessageRecord extends DisplayRecord { @Override public SpannableString getDisplayBody(@NonNull Context context) { 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())); } else if (isGroupQuit() && isOutgoing()) { return new SpannableString(context.getString(R.string.MessageRecord_left_group)); @@ -105,14 +107,14 @@ public abstract class MessageRecord extends DisplayRecord { } else if (isJoined()) { return new SpannableString(context.getString(R.string.MessageRecord_s_joined_signal, getIndividualRecipient().toShortString())); } else if (isExpirationTimerUpdate()) { - int seconds = (int) (getExpiresIn() / 1000); + 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())); + : 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)); + : 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()) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index 014b66a0c..4ce46b457 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -15,7 +15,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val // Settings override val maxFailureCount: Int = 10 companion object { - val TAG = MessageReceiveJob::class.qualifiedName + val TAG = MessageReceiveJob::class.simpleName val KEY: String = "MessageReceiveJob" //keys used for database storage purpose From 28cecc02363f2cb9bcefcd9570e72c16b35d42fc Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 23 Mar 2021 09:58:17 +1100 Subject: [PATCH 08/99] expiration timer messages generation updated --- .../conversation/ConversationActivity.java | 4 +- .../conversation/ConversationUpdateItem.java | 2 +- .../securesms/database/SmsDatabase.java | 2 +- .../loki/utilities/GroupDescription.kt | 1 + .../service/ExpiringMessageManager.java | 4 +- app/src/main/res/values/strings.xml | 1 + .../OutgoingExpirationUpdateMessage.java | 8 +-- .../utilities/UpdateMessageBuilder.kt | 54 ++++++++++++++----- libsession/src/main/res/values/strings.xml | 1 + 9 files changed, 56 insertions(+), 21 deletions(-) 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 ca14153ed..e5bb1b85b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -89,6 +89,7 @@ import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.threads.DistributionTypes; +import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.MediaTypes; import org.session.libsignal.libsignal.InvalidMessageException; @@ -808,7 +809,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); ExpirationTimerUpdate message = new ExpirationTimerUpdate(expirationTime); message.setSentTimestamp(System.currentTimeMillis()); - OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient); + String displayedMessage = UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(getApplicationContext(), expirationTime, null, false); + OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient, displayedMessage); try { message.setId(DatabaseFactory.getMmsDatabase(ConversationActivity.this).insertMessageOutbox(outgoingMessage, getAllocatedThreadId(ConversationActivity.this), false, null)); MessageSender.send(message, recipient.getAddress()); 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 6b76c0572..7efd5ca4e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -175,7 +175,7 @@ public class ConversationUpdateItem extends LinearLayout icon.setImageResource(R.drawable.ic_group_grey600_24dp); icon.clearColorFilter(); - GroupDescription.Companion.getDescription(getContext(), messageRecord.getBody()).addListener(this); + GroupDescription.Companion.getDescription(getContext(), messageRecord.getBody()).addListener(this); //TODO Brice: could be removed if GroupDescription is removed body.setText(messageRecord.getDisplayBody(getContext())); title.setVisibility(GONE); 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 300a5c55f..28dbbbe42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -390,7 +390,7 @@ public class SmsDatabase extends MessagingDatabase { values.put(REPLY_PATH_PRESENT, message.isReplyPathPresent()); values.put(SERVICE_CENTER, message.getServiceCenterAddress()); - values.put(BODY, message.getMessageBody()); + values.put(BODY, message.getMessageBody()); //TODO Brice: is that encoded? values.put(TYPE, type); values.put(THREAD_ID, threadId); 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 index 62d48aa52..dd642beb5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt @@ -13,6 +13,7 @@ import network.loki.messenger.R import org.session.libsignal.utilities.logging.Log import java.io.IOException +//TODO Brice: that class should be deprecated class GroupDescription(context: Context, groupContext: SignalServiceProtos.GroupContext?) { private val context: Context private val groupContext: SignalServiceProtos.GroupContext? 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 77eee247c..5a60e291c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.recipients.Recipient; +import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.utilities.SSKEnvironment; import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.service.api.messages.SignalServiceGroup; @@ -78,10 +79,11 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM GroupContext groupContext = content.getDataMessage().getGroup(); groupInfo = Optional.of(new SignalServiceGroup(groupContext.getId().toByteArray(), SignalServiceGroup.GroupType.SIGNAL)); } + String updateMessage = UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, duration, senderPublicKey, false); IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, content.getDataMessage().getTimestamp(), -1, duration * 1000L, true, false, - Optional.absent(), + Optional.of(updateMessage), groupInfo, Optional.absent(), Optional.absent(), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3c707cd5c..ac5121c1d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -501,6 +501,7 @@ %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 called Contact called Missed call 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 77996ba11..3fac6292f 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 @@ -10,15 +10,15 @@ import java.util.LinkedList; public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage { - public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) { - super(recipient, "", new LinkedList(), sentTimeMillis, + public OutgoingExpirationUpdateMessage(Recipient recipient, String body, long sentTimeMillis, long expiresIn) { + super(recipient, body, new LinkedList(), sentTimeMillis, DistributionTypes.CONVERSATION, expiresIn, null, Collections.emptyList(), Collections.emptyList()); } public static OutgoingExpirationUpdateMessage from(ExpirationTimerUpdate message, - Recipient recipient) { - return new OutgoingExpirationUpdateMessage(recipient, message.getSentTimestamp(), message.getDuration() * 1000); + Recipient recipient, String body) { + return new OutgoingExpirationUpdateMessage(recipient, body, message.getSentTimestamp(), message.getDuration() * 1000); } @Override 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 index bcc0d4761..367e549c0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -1,19 +1,23 @@ package org.session.libsession.messaging.utilities import android.content.Context +import android.text.SpannableString import org.session.libsession.R import org.session.libsession.messaging.MessagingConfiguration +import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate +import org.session.libsession.utilities.ExpirationUtil import org.session.libsignal.service.api.messages.SignalServiceGroup -import org.session.libsignal.service.internal.push.SignalServiceProtos object UpdateMessageBuilder { - fun buildGroupUpdateMessage(context: Context, groupInfo: SignalServiceGroup, sender: String, isOutgoing: Boolean = false): String { + fun buildGroupUpdateMessage(context: Context, groupInfo: SignalServiceGroup, sender: String? = null, isOutgoing: Boolean = false): String { val updateType = groupInfo.type - val senderName: String = if (!isOutgoing) { - MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender) ?: sender - } else { sender } var message: String = "" + if (!isOutgoing && sender == null) return message + val senderName: String? = if (!isOutgoing) { + MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender + } else { sender } + when (updateType) { SignalServiceGroup.Type.NEW_GROUP -> { message = if (isOutgoing) { @@ -40,13 +44,25 @@ object UpdateMessageBuilder { } } SignalServiceGroup.Type.MEMBER_REMOVED -> { - val members = groupInfo.members.get().joinToString(", ") { - MessagingConfiguration.shared.storage.getDisplayNameForRecipient(it) ?: it - } - message = if (isOutgoing) { - context.getString(R.string.MessageRecord_you_removed_s_from_the_group, members) + val storage = MessagingConfiguration.shared.storage + val userPublicKey = storage.getUserPublicKey()!! + // 1st case: you are part of the removed members + message = if (userPublicKey in groupInfo.members.get()) { + if (isOutgoing) { + context.getString(R.string.MessageRecord_left_group) + } else { + context.getString(R.string.MessageRecord_you_were_removed_from_the_group) + } } else { - context.getString(R.string.MessageRecord_s_removed_s_from_the_group, senderName, members) + // 2nd case: you are not part of the removed members + val members = groupInfo.members.get().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 -> { @@ -63,8 +79,20 @@ object UpdateMessageBuilder { return message } - fun buildExpirationTimerMessage(): String { - return "" + fun buildExpirationTimerMessage(context: Context, duration: Int, sender: String? = null, isOutgoing: Boolean = false): String { + val seconds = (duration!! / 1000) + if (!isOutgoing && sender == null) return "" + val senderName: String? = if (!isOutgoing) { + MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender + } else { sender } + return if (seconds <= 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, seconds) + 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) + } } fun buildDataExtractionMessage(): String { diff --git a/libsession/src/main/res/values/strings.xml b/libsession/src/main/res/values/strings.xml index ea6a33b70..90cbb9286 100644 --- a/libsession/src/main/res/values/strings.xml +++ b/libsession/src/main/res/values/strings.xml @@ -495,6 +495,7 @@ %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 called Contact called Missed call From 68a3c73ab1e672057a27c600fa768568d850797b Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 23 Mar 2021 10:59:03 +1100 Subject: [PATCH 09/99] clean --- .../main/java/org/thoughtcrime/securesms/database/Storage.kt | 2 -- 1 file changed, 2 deletions(-) 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 edffc8cac..cb7c8b7ae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -44,8 +44,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority import org.session.libsession.messaging.messages.signal.IncomingGroupMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage -import org.session.libsession.utilities.preferences.ProfileKeyUtil -import org.session.libsignal.service.loki.utilities.prettifiedDescription class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { From 1b01d0afd47ef4f64dee97cc9868393c1277f14b Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 23 Mar 2021 11:03:09 +1100 Subject: [PATCH 10/99] clean --- .../main/java/org/thoughtcrime/securesms/database/Storage.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 cb7c8b7ae..d6515ae80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -24,7 +24,6 @@ import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.guava.Optional @@ -44,6 +43,8 @@ import org.thoughtcrime.securesms.mms.PartAuthority import org.session.libsession.messaging.messages.signal.IncomingGroupMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage +import org.session.libsession.utilities.preferences.ProfileKeyUtil +import org.session.libsignal.service.loki.utilities.prettifiedDescription class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { From cc24e29cb2d5cb3cad4f1817f18cc734bdf383ab Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 8 Apr 2021 15:11:46 +1000 Subject: [PATCH 11/99] clean --- .../java/org/session/libsession/messaging/StorageProtocol.kt | 2 +- .../messaging/messages/control/DataExtractionNotification.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) 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 5b88e362a..332795230 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -162,4 +162,4 @@ interface StorageProtocol { // Data Extraction Notification fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, groupID: String?, sentTimestamp: Long) -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt index 040ea66d0..538b0b000 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt @@ -73,5 +73,4 @@ class DataExtractionNotification(): ControlMessage() { return null } } - -} \ No newline at end of file +} From 8df7d2bb49137e3f45b23f455653c67905577616 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 8 Apr 2021 15:27:25 +1000 Subject: [PATCH 12/99] clean --- .../java/org/thoughtcrime/securesms/database/SmsDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1a7326f4c..d5c34c5c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -390,7 +390,7 @@ public class SmsDatabase extends MessagingDatabase { values.put(REPLY_PATH_PRESENT, message.isReplyPathPresent()); values.put(SERVICE_CENTER, message.getServiceCenterAddress()); - values.put(BODY, message.getMessageBody()); //TODO Brice: is that encoded? + values.put(BODY, message.getMessageBody()); values.put(TYPE, type); values.put(THREAD_ID, threadId); From fce1d60d7d31d352bce6415b982a12c17ffa8aa8 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 8 Apr 2021 15:40:00 +1000 Subject: [PATCH 13/99] clean --- .../main/java/org/thoughtcrime/securesms/database/Storage.kt | 3 +-- .../messaging/messages/signal/IncomingMediaMessage.java | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) 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 ab88d16ce..57aad5b10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -148,8 +148,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val signalServiceAttachments = attachments.mapNotNull { it.toSignalPointer() } - //TODO deal with data extraction instead of Optional.absent() - val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews, Optional.absent()) + val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index dff60354c..ad110964a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -75,11 +75,10 @@ public class IncomingMediaMessage { Optional group, List attachments, Optional quote, - Optional> linkPreviews, - Optional dataExtractionNotification) + Optional> linkPreviews) { return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false, - false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, dataExtractionNotification); + false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent()); } public int getSubscriptionId() { From c03b49eeb732697f0b8f637b8aaaa441de03419d Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 8 Apr 2021 15:48:00 +1000 Subject: [PATCH 14/99] clean --- .../java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 0bf78d38c..ad2e55daf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -373,7 +373,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } else { - // FIXME handle DataExtraction parameter below where Optional.absent() is IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(), quote, sharedContacts, linkPreviews, Optional.absent()); From e57c697eca8193349fc34560c27d5e5ce01935a5 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 9 Apr 2021 16:06:12 +1000 Subject: [PATCH 15/99] finishing data extraction handling --- .../securesms/MediaOverviewActivity.java | 2 ++ .../securesms/MediaPreviewActivity.java | 2 ++ .../conversation/ConversationFragment.java | 2 ++ .../conversation/ConversationUpdateItem.java | 19 +++++++++++++++++++ .../securesms/database/MmsDatabase.java | 8 ++++++++ .../securesms/database/MmsSmsColumns.java | 12 ++++++++++++ .../securesms/database/Storage.kt | 8 ++------ .../database/model/DisplayRecord.java | 14 ++++++++++++++ .../database/model/MessageRecord.java | 5 ++++- .../database/model/ThreadRecord.java | 6 +++++- app/src/main/res/values/strings.xml | 4 ++-- .../libsession/messaging/StorageProtocol.kt | 2 +- .../messages/signal/IncomingMediaMessage.java | 14 ++++++++++++++ .../MessageReceiverHandler.kt | 10 +++++++--- 14 files changed, 94 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java index 39e817037..762d88b81 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -366,6 +366,8 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { * Send a MediaSaved notification to the recipient */ private void sendMediaSavedNotificationIfNeeded() { + // we don't send media saved notification for groups + if (recipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, recipient.getAddress()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 39a67d762..8bc53250a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -368,6 +368,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im * Send a MediaSaved notification to the recipient */ private void sendMediaSavedNotificationIfNeeded() { + // we don't send media saved notification for groups + if (conversationRecipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, conversationRecipient.getAddress()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index b833ad6c3..d69574c4d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -764,6 +764,8 @@ public class ConversationFragment extends Fragment * Send a MediaSaved notification to the recipient */ private void sendMediaSavedNotificationIfNeeded() { + // we don't send media saved notification for groups + if (recipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, recipient.getAddress()); } 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 6b76c0572..0bdb8ba3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -15,6 +15,7 @@ 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; @@ -106,6 +107,8 @@ public class ConversationUpdateItem extends LinearLayout else if (messageRecord.isCallLog()) setCallRecord(messageRecord); else if (messageRecord.isJoined()) setJoinedRecord(messageRecord); else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord); + else if (messageRecord.isScreenshotExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT); + else if (messageRecord.isMediaSavedExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.MEDIASAVED); else if (messageRecord.isEndSession()) setEndSessionRecord(messageRecord); else if (messageRecord.isIdentityUpdate()) setIdentityRecord(messageRecord); else if (messageRecord.isIdentityVerified() || @@ -149,6 +152,22 @@ public class ConversationUpdateItem extends LinearLayout date.setVisibility(GONE); } + private void setDataExtractionRecord(final MessageRecord messageRecord, DataExtractionNotificationInfoMessage.Kind kind) { + @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getContext().getTheme()); + if (kind == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT) { + icon.setImageResource(R.drawable.quick_camera_dark); + } else if (kind == DataExtractionNotificationInfoMessage.Kind.MEDIASAVED) { + icon.setImageResource(R.drawable.ic_file_download_white_36dp); + } + icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + + body.setText(messageRecord.getDisplayBody(getContext())); + + title.setVisibility(VISIBLE); + body.setVisibility(VISIBLE); + date.setVisibility(GONE); + } + private void setIdentityRecord(final MessageRecord messageRecord) { icon.setImageResource(R.drawable.ic_security_white_24dp); icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY)); 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 62de9499a..12e5ba2d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -709,6 +709,14 @@ public class MmsDatabase extends MessagingDatabase { type |= Types.EXPIRATION_TIMER_UPDATE_BIT; } + if (retrieved.isScreenshotDataExtraction()) { + type |= Types.SCREENSHOT_EXTRACTION_BIT; + } + + if (retrieved.isMediaSavedDataExtraction()) { + type |= Types.MEDIA_SAVED_EXTRACTION_BIT; + } + return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp); } 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 36b86023e..aefd1af80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -71,6 +71,10 @@ public interface MmsSmsColumns { protected static final long GROUP_QUIT_BIT = 0x20000; protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000; + // Data Extraction Information + protected static final long MEDIA_SAVED_EXTRACTION_BIT = 0x01000; + protected static final long SCREENSHOT_EXTRACTION_BIT = 0x02000; + // Encrypted Storage Information XXX public static final long ENCRYPTION_MASK = 0xFF000000; // public static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000; Deprecated @@ -197,6 +201,14 @@ public interface MmsSmsColumns { return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0; } + public static boolean isMediaSavedExtraction(long type) { + return (type & MEDIA_SAVED_EXTRACTION_BIT) != 0; + } + + public static boolean isScreenshotExtraction(long type) { + return (type & SCREENSHOT_EXTRACTION_BIT) != 0; + } + public static boolean isIncomingCall(long type) { return type == INCOMING_CALL_TYPE; } 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 57aad5b10..7722840a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -581,22 +581,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } // Data Extraction Notification - override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, groupID: String?, sentTimestamp: Long) { + override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) { val database = DatabaseFactory.getMmsDatabase(context) val address = fromSerialized(senderPublicKey) val recipient = Recipient.from(context, address, false) if (recipient.isBlocked) return - var groupInfo = Optional.absent() - if (groupID != null) { - groupInfo = Optional.of(SignalServiceGroup(groupID.toByteArray(), SignalServiceGroup.GroupType.SIGNAL)) - } val mediaMessage = IncomingMediaMessage(address, sentTimestamp, -1, 0, false, false, Optional.absent(), - groupInfo, + Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), 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 ab40f3381..28eae16f1 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 @@ -125,6 +125,20 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isExpirationTimerUpdate(type); } + // Data extraction + + public boolean isMediaSavedExtraction() { + return MmsSmsColumns.Types.isMediaSavedExtraction(type); + } + + public boolean isScreenshotExtraction() { + return MmsSmsColumns.Types.isScreenshotExtraction(type); + } + + public boolean isDataExtraction() { + return isMediaSavedExtraction() || isScreenshotExtraction(); + } + public boolean isCallLog() { return SmsDatabase.Types.isCallLog(type); } 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 b41e4ae78..64229e95b 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 @@ -98,6 +98,9 @@ public abstract class MessageRecord extends DisplayRecord { 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()) { @@ -175,7 +178,7 @@ public abstract class MessageRecord extends DisplayRecord { } public boolean isUpdate() { - return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || + return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || isDataExtraction() || isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isLokiSessionRestoreSent() || isLokiSessionRestoreDone(); } 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 e2a286c85..42c8a20ed 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 @@ -103,12 +103,16 @@ public class ThreadRecord extends DisplayRecord { } else if (SmsDatabase.Types.isJoinedType(type)) { return emphasisAdded(context.getString(R.string.ThreadRecord_s_is_on_signal, getRecipient().toShortString())); } else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) { - int seconds = (int)(getExpiresIn() / 1000); + int seconds = (int) (getExpiresIn() / 1000); if (seconds <= 0) { return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_messages_disabled)); } String time = ExpirationUtil.getExpirationDisplayValue(context, seconds); return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time)); + } else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) { + return emphasisAdded(context.getString(R.string.ThreadRecord_media_saved_by_s, getRecipient().toShortString())); + } else if (MmsSmsColumns.Types.isScreenshotExtraction(type)) { + return emphasisAdded(context.getString(R.string.ThreadRecord_s_took_a_screenshot, getRecipient().toShortString())); } else if (SmsDatabase.Types.isIdentityUpdate(type)) { if (getRecipient().isGroupRecipient()) return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed)); else return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, getRecipient().toShortString())); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 166f8c42d..3b0693645 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -505,9 +505,7 @@ %1$s disabled disappearing messages. You set the disappearing message timer to %1$s %1$s set the disappearing message timer to %2$s - You took a screenshot. %1$s took a screenshot. - Media saved by you. Media saved by %1$s. Your safety number with %s has changed. You marked your safety number with %s verified @@ -707,6 +705,8 @@ %s is on Session! Disappearing messages disabled Disappearing message time set to %s + %s took a screenshot. + Media saved by %s. Safety number changed Your safety number with %s has changed. You marked verified 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 332795230..b390637ca 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -161,5 +161,5 @@ interface StorageProtocol { fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? // Data Extraction Notification - fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, groupID: String?, sentTimestamp: Long) + fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index ad110964a..53e48a589 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -121,6 +121,20 @@ public class IncomingMediaMessage { return groupId != null; } + public boolean isScreenshotDataExtraction() { + if (dataExtractionNotification == null) return false; + else { + return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT; + } + } + + public boolean isMediaSavedDataExtraction() { + if (dataExtractionNotification == null) return false; + else { + return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.MEDIASAVED; + } + } + public QuoteModel getQuote() { return quote; } 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 2c0e2269c..190a55195 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 @@ -96,6 +96,9 @@ private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimer // Data Extraction Notification handling private fun MessageReceiver.handleDataExtractionNotification(message: DataExtractionNotification) { + // we don't handle data extraction messages for groups (they shouldn't be sent, but in case we filter them here too) + if (message.groupPublicKey != null) return + val storage = MessagingConfiguration.shared.storage val senderPublicKey = message.sender!! val notification: DataExtractionNotificationInfoMessage = when(message.kind) { @@ -103,7 +106,7 @@ private fun MessageReceiver.handleDataExtractionNotification(message: DataExtrac is DataExtractionNotification.Kind.MediaSaved -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.MEDIASAVED) else -> return } - storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.groupPublicKey, message.sentTimestamp!!) + storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.sentTimestamp!!) } // Configuration message handling @@ -170,7 +173,8 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS } } // Get or create thread - val threadID = storage.getOrCreateThreadIdFor(message.syncTarget ?: message.sender!!, message.groupPublicKey, openGroupID) + val threadID = storage.getOrCreateThreadIdFor(message.syncTarget + ?: message.sender!!, message.groupPublicKey, openGroupID) // Parse quote if needed var quoteModel: QuoteModel? = null if (message.quote != null && proto.dataMessage.hasQuote()) { @@ -259,7 +263,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli storage.updateMembers(groupID, members.map { Address.fromSerialized(it) }) } else { 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) // Notify the user storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) } From 40015c2898a7ecac819db877d96e9973fd8a62e0 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 9 Apr 2021 16:36:18 +1000 Subject: [PATCH 16/99] clean --- .../messaging/messages/signal/IncomingMediaMessage.java | 2 -- .../dataextraction/DataExtractionNotificationInfoMessage.kt | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index 53e48a589..1d359a6c6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -147,8 +147,6 @@ public class IncomingMediaMessage { return linkPreviews; } - public DataExtractionNotificationInfoMessage getDataExtractionNotification() { return dataExtractionNotification; } - public boolean isUnidentified() { return unidentified; } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt index 64d7af7ff..108b51c7a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt @@ -13,4 +13,4 @@ class DataExtractionNotificationInfoMessage { this.kind = kind } -} \ No newline at end of file +} From 2bce2738b30b3d15670f325ad6eb501836dead80 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 12 Apr 2021 12:02:41 +1000 Subject: [PATCH 17/99] media saved notification is sent only when incoming media are saved --- .../org/thoughtcrime/securesms/MediaOverviewActivity.java | 7 +++++-- .../org/thoughtcrime/securesms/MediaPreviewActivity.java | 6 ++++-- .../securesms/conversation/ConversationFragment.java | 6 ++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java index 762d88b81..fe6f249b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -353,8 +353,11 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { saveTask.executeOnExecutor(THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[attachments.size()])); actionMode.finish(); - // Sending a Data extraction notification - sendMediaSavedNotificationIfNeeded(); + // Sending a Data extraction notification (for incoming attachments only) + boolean containsIncoming = mediaRecords.parallelStream().anyMatch(m -> !m.isOutgoing()); + if (containsIncoming) { + sendMediaSavedNotificationIfNeeded(); + } } }.execute(); }) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 8bc53250a..03e1c6b4f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -357,8 +357,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im saveTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null)); - // Sending a Data extraction notification - sendMediaSavedNotificationIfNeeded(); + // Sending a Data extraction notification (for incoming attachments only) + if(!mediaItem.outgoing) { + sendMediaSavedNotificationIfNeeded(); + } }) .execute(); }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index d69574c4d..32668cb24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -746,8 +746,10 @@ public class ConversationFragment extends Fragment if (!Util.isEmpty(attachments)) { SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity()); saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0])); - // Sending a Data extraction notification - sendMediaSavedNotificationIfNeeded(); + // Sending a Data extraction notification (for incoming attachments only) + if(!message.isOutgoing()) { + sendMediaSavedNotificationIfNeeded(); + } return; } From 0eadc55325ec7d0d626a37311d5184bcd0feee97 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 13 Apr 2021 17:17:16 +1000 Subject: [PATCH 18/99] feat: add open group v2 storage and db methods, starting on new open group v2 poller --- .../securesms/database/Storage.kt | 53 +++++ .../securesms/loki/api/PublicChatManager.kt | 2 +- .../loki/database/LokiAPIDatabase.kt | 51 ++++ .../loki/database/LokiThreadDatabase.kt | 40 +++- .../loki/protocol/MultiDeviceProtocol.kt | 89 ------- .../libsession/messaging/StorageProtocol.kt | 50 ++-- .../messaging/opengroups/OpenGroupAPIV2.kt | 37 +++ .../messaging/opengroups/OpenGroupV2.kt | 45 ++++ .../MessageReceiverHandler.kt | 2 + .../pollers/OpenGroupV2Poller.kt | 225 ++++++++++++++++++ .../service/loki/api/opengroups/PublicChat.kt | 1 + .../loki/database/LokiAPIDatabaseProtocol.kt | 5 + 12 files changed, 485 insertions(+), 115 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupV2Poller.kt 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 50d723271..62caac50f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -14,6 +14,7 @@ import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup +import org.session.libsession.messaging.opengroups.OpenGroupV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview @@ -221,6 +222,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(server, null) } + override fun getAuthToken(room: String, server: String): String? { + val id = "$server.$room" + return DatabaseFactory.getLokiAPIDatabase(context).getAuthToken(id) + } + + override fun setAuthToken(room: String, server: String, newValue: String) { + val id = "$server.$room" + DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(id, newValue) + } + + override fun removeAuthToken(room: String, server: String) { + val id = "$server.$room" + DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(id, null) + } + override fun getOpenGroup(threadID: String): OpenGroup? { if (threadID.toInt() < 0) { return null } val database = databaseHelper.readableDatabase @@ -230,6 +246,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun getV2OpenGroup(threadId: String): OpenGroupV2? { + if (threadId.toInt() < 0) { return null } + val database = databaseHelper.readableDatabase + return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf(threadId)) { cursor -> + val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat) + OpenGroupV2.fromJson(publicChatAsJson) + } + } + override fun getThreadID(openGroupID: String): String { val address = Address.fromSerialized(openGroupID) val recipient = Recipient.from(context, address, false) @@ -254,6 +279,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(groupID, publicKey) } + override fun getLastMessageServerId(room: String, server: String): Long? { + return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(room, server) + } + + override fun setLastMessageServerId(room: String, server: String, newValue: Long) { + DatabaseFactory.getLokiAPIDatabase(context).setLastMessageServerID(room, server, newValue) + } + + override fun removeLastMessageServerId(room: String, server: String) { + DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(room, server) + } + override fun getLastMessageServerID(group: Long, server: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(group, server) } @@ -266,6 +303,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(group, server) } + override fun getLastDeletionServerId(room: String, server: String): Long? { + TODO("Not yet implemented") + } + + override fun setLastDeletionServerId(room: String, server: String, newValue: Long) { + TODO("Not yet implemented") + } + + override fun removeLastDeletionServerId(room: String, server: String) { + TODO("Not yet implemented") + } + override fun getLastDeletionServerID(group: Long, server: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(group, server) } @@ -471,6 +520,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun getAllV2OpenGroups(): Map { + return DatabaseFactory.getLokiThreadDatabase(context).getAllV2OpenGroups() + } + override fun addOpenGroup(server: String, channel: Long) { OpenGroupUtilities.addGroup(context, server, channel) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index c2200ac0a..ef36d98de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -24,7 +24,7 @@ class PublicChatManager(private val context: Context) { private val pollers = mutableMapOf() private val observers = mutableMapOf() private var isPolling = false - private val executorService = Executors.newScheduledThreadPool(16) + private val executorService = Executors.newScheduledThreadPool(4) public fun areAllCaughtUp(): Boolean { var areAllCaughtUp = true diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index a9325a552..e4ea2d009 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -286,6 +286,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( }?.toLong() } + override fun getLastMessageServerID(room: String, server: String): Long? { + val database = databaseHelper.writableDatabase + val index = "$server.$room" + return database.get(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) { cursor -> + cursor.getInt(lastMessageServerID) + }?.toLong() + } + override fun setLastMessageServerID(group: Long, server: String, newValue: Long) { val database = databaseHelper.writableDatabase val index = "$server.$group" @@ -293,12 +301,25 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(lastMessageServerIDTable, row, "$lastMessageServerIDTableIndex = ?", wrap(index)) } + override fun setLastMessageServerID(room: String, server: String, newValue: Long) { + val database = databaseHelper.writableDatabase + val index = "$server.$room" + val row = wrap(mapOf( lastMessageServerIDTableIndex to index, lastMessageServerID to newValue.toString() )) + database.insertOrUpdate(lastMessageServerIDTable, row, "$lastMessageServerIDTableIndex = ?", wrap(index)) + } + fun removeLastMessageServerID(group: Long, server: String) { val database = databaseHelper.writableDatabase val index = "$server.$group" database.delete(lastMessageServerIDTable,"$lastMessageServerIDTableIndex = ?", wrap(index)) } + fun removeLastMessageServerID(room: String, server:String) { + val database = databaseHelper.writableDatabase + val index = "$server.$channel" + database.delete(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) + } + override fun getLastDeletionServerID(group: Long, server: String): Long? { val database = databaseHelper.readableDatabase val index = "$server.$group" @@ -307,6 +328,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( }?.toLong() } + override fun getLastDeletionServerID(room: String, server: String): Long? { + val database = databaseHelper.readableDatabase + val index = "$server.$room" + return database.get(lastDeletionServerIDTable, "$lastDeletionServerIDTableIndex = ?", wrap(index)) { cursor -> + cursor.getInt(lastDeletionServerID) + }?.toLong() + } + override fun setLastDeletionServerID(group: Long, server: String, newValue: Long) { val database = databaseHelper.writableDatabase val index = "$server.$group" @@ -314,6 +343,13 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index)) } + override fun setLastDeletionServerID(room: String, server: String, newValue: Long) { + val database = databaseHelper.writableDatabase + val index = "$server.$room" + val row = wrap(mapOf(lastDeletionServerIDTableIndex to index, lastDeletionServerID to newValue.toString())) + database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index)) + } + fun removeLastDeletionServerID(group: Long, server: String) { val database = databaseHelper.writableDatabase val index = "$server.$group" @@ -328,6 +364,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( }?.toInt() } + fun getUserCount(room: String, server: String): Int? { + val database = databaseHelper.readableDatabase + val index = "$server.$room" + return database.get(userCountTable, "$publicChatID = ?", wrap(index)) { cursor -> + cursor.getInt(userCount) + }?.toInt() + } + override fun setUserCount(group: Long, server: String, newValue: Int) { val database = databaseHelper.writableDatabase val index = "$server.$group" @@ -335,6 +379,13 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index)) } + override fun setUserCount(room: String, server: String, newValue: Int) { + val database = databaseHelper.writableDatabase + val index = "$server.$room" + val row = wrap(mapOf( publicChatID to index, userCount to newValue.toString() )) + database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index)) + } + override fun getSessionRequestSentTimestamp(publicKey: String): Long? { val database = databaseHelper.readableDatabase return database.get(sessionRequestSentTimestampTable, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt index 0c2dff9a8..60057f606 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt @@ -3,20 +3,18 @@ package org.thoughtcrime.securesms.loki.database import android.content.ContentValues import android.content.Context import android.database.Cursor - +import org.session.libsession.messaging.opengroups.OpenGroupV2 +import org.session.libsession.messaging.threads.Address +import org.session.libsession.messaging.threads.recipients.Recipient +import org.session.libsignal.service.loki.api.opengroups.PublicChat +import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol +import org.session.libsignal.utilities.JsonUtil import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper -import org.thoughtcrime.securesms.loki.utilities.* - -import org.session.libsession.messaging.threads.Address -import org.session.libsession.messaging.threads.recipients.Recipient -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.service.loki.api.opengroups.PublicChat - -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol -import org.session.libsignal.service.loki.utilities.PublicKeyValidation +import org.thoughtcrime.securesms.loki.utilities.get +import org.thoughtcrime.securesms.loki.utilities.getString +import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol { @@ -57,6 +55,26 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa return result } + fun getAllV2OpenGroups(): Map { + val database = databaseHelper.readableDatabase + var cursor: Cursor? = null + val result = mutableMapOf() + try { + cursor = database.rawQuery("select * from $publicChatTable", null) + while (cursor != null && cursor.moveToNext()) { + val threadID = cursor.getLong(threadID) + val string = cursor.getString(publicChat) + val openGroup = OpenGroupV2.fromJson(string) + if (openGroup != null) result[threadID] = openGroup + } + } catch (e: Exception) { + // do nothing + } finally { + cursor?.close() + } + return result + } + fun getAllPublicChatServers(): Set { return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt index 41b3de72f..b58f60850 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt @@ -1,25 +1,12 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context -import com.google.protobuf.ByteString -import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.threads.Address -import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.preferences.ProfileKeyUtil -import org.session.libsignal.service.internal.push.SignalServiceProtos -import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage -import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded -import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.Hex -import org.thoughtcrime.securesms.ApplicationContext -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.loki.utilities.ContactUtilities -import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities object MultiDeviceProtocol { @@ -51,80 +38,4 @@ object MultiDeviceProtocol { TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis()) } - // TODO: remove this after we migrate to new message receiving pipeline - @JvmStatic - fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String, timestamp: Long) { - synchronized(this) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return - if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, timestamp)) return - if (senderPublicKey != userPublicKey) return - TextSecurePreferences.setConfigurationMessageSynced(context, true) - TextSecurePreferences.setLastProfileUpdateTime(context, timestamp) - - val configurationMessage = ConfigurationMessage.fromProto(content) ?: return - - val storage = MessagingConfiguration.shared.storage - val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys() - - val threadDatabase = DatabaseFactory.getThreadDatabase(context) - val recipientDatabase = DatabaseFactory.getRecipientDatabase(context) - - val ourRecipient = Recipient.from(context, Address.fromSerialized(userPublicKey),false) - - for (closedGroup in configurationMessage.closedGroups) { - if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue - - val closedGroupUpdate = DataMessage.ClosedGroupControlMessage.newBuilder() - closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.NEW - closedGroupUpdate.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(closedGroup.publicKey)) - closedGroupUpdate.name = closedGroup.name - val encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder() - encryptionKeyPair.publicKey = ByteString.copyFrom(closedGroup.encryptionKeyPair!!.publicKey.serialize().removing05PrefixIfNeeded()) - encryptionKeyPair.privateKey = ByteString.copyFrom(closedGroup.encryptionKeyPair!!.privateKey.serialize()) - closedGroupUpdate.encryptionKeyPair = encryptionKeyPair.build() - closedGroupUpdate.addAllMembers(closedGroup.members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) - closedGroupUpdate.addAllAdmins(closedGroup.admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) - - ClosedGroupsProtocolV2.handleNewClosedGroup(context, closedGroupUpdate.build(), userPublicKey, timestamp) - } - val allOpenGroups = storage.getAllOpenGroups().map { it.value.server } - for (openGroup in configurationMessage.openGroups) { - if (allOpenGroups.contains(openGroup)) continue - OpenGroupUtilities.addGroup(context, openGroup, 1) - } - if (configurationMessage.displayName.isNotEmpty()) { - TextSecurePreferences.setProfileName(context, configurationMessage.displayName) - recipientDatabase.setProfileName(ourRecipient, configurationMessage.displayName) - } - if (configurationMessage.profileKey.isNotEmpty()) { - val profileKey = Base64.encodeBytes(configurationMessage.profileKey) - ProfileKeyUtil.setEncodedProfileKey(context, profileKey) - recipientDatabase.setProfileKey(ourRecipient, configurationMessage.profileKey) - if (!configurationMessage.profilePicture.isNullOrEmpty() && TextSecurePreferences.getProfilePictureURL(context) != configurationMessage.profilePicture) { - TextSecurePreferences.setProfilePictureURL(context, configurationMessage.profilePicture) - ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, configurationMessage.profilePicture)) - } - } - for (contact in configurationMessage.contacts) { - val address = Address.fromSerialized(contact.publicKey) - val recipient = Recipient.from(context, address, true) - if (!contact.profilePicture.isNullOrEmpty()) { - recipientDatabase.setProfileAvatar(recipient, contact.profilePicture) - } - if (contact.profileKey?.isNotEmpty() == true) { - recipientDatabase.setProfileKey(recipient, contact.profileKey) - } - if (contact.name.isNotEmpty()) { - recipientDatabase.setProfileName(recipient, contact.name) - } - recipientDatabase.setProfileSharing(recipient, true) - recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED) - // create Thread if needed - threadDatabase.getOrCreateThreadIdFor(recipient) - } - if (configurationMessage.contacts.isNotEmpty()) { - threadDatabase.notifyUpdatedFromConfig() - } - } - } } \ No newline at end of file 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 64c1828d5..95e5eb680 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -10,6 +10,7 @@ import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup +import org.session.libsession.messaging.opengroups.OpenGroupV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview @@ -51,14 +52,16 @@ interface StorageProtocol { fun isJobCanceled(job: Job): Boolean // Authorization - fun getAuthToken(server: String): String? - fun setAuthToken(server: String, newValue: String?) - fun removeAuthToken(server: String) + fun getAuthToken(room: String, server: String): String? + fun setAuthToken(room: String, server: String, newValue: String) + fun removeAuthToken(room: String, server: String) + + // Open Groups + fun getAllV2OpenGroups(): Map + fun getV2OpenGroup(threadId: String): OpenGroupV2? // Open Groups - fun getOpenGroup(threadID: String): OpenGroup? fun getThreadID(openGroupID: String): String? - fun getAllOpenGroups(): Map fun addOpenGroup(server: String, channel: Long) fun setOpenGroupServerMessageID(messageID: Long, serverID: Long) fun getQuoteServerID(quoteID: Long, publicKey: String): Long? @@ -72,21 +75,19 @@ interface StorageProtocol { fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String? // Open Group Metadata - fun setUserCount(group: Long, server: String, newValue: Int) - fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String) - fun getOpenGroupProfilePictureURL(group: Long, server: String): String? + fun updateTitle(groupID: String, newValue: String) fun updateProfilePicture(groupID: String, newValue: ByteArray) // Last Message Server ID - fun getLastMessageServerID(group: Long, server: String): Long? - fun setLastMessageServerID(group: Long, server: String, newValue: Long) - fun removeLastMessageServerID(group: Long, server: String) + fun getLastMessageServerId(room: String, server: String): Long? + fun setLastMessageServerId(room: String, server: String, newValue: Long) + fun removeLastMessageServerId(room: String, server: String) // Last Deletion Server ID - fun getLastDeletionServerID(group: Long, server: String): Long? - fun setLastDeletionServerID(group: Long, server: String, newValue: Long) - fun removeLastDeletionServerID(group: Long, server: String) + fun getLastDeletionServerId(room: String, server: String): Long? + fun setLastDeletionServerId(room: String, server: String, newValue: Long) + fun removeLastDeletionServerId(room: String, server: String) // Message Handling fun isMessageDuplicated(timestamp: Long, sender: String): Boolean @@ -158,4 +159,25 @@ interface StorageProtocol { // Message Handling /// Returns the ID of the `TSIncomingMessage` that was constructed. fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? + + // DEPRECATED + fun getAuthToken(server: String): String? + fun setAuthToken(server: String, newValue: String?) + fun removeAuthToken(server: String) + + fun getLastMessageServerID(group: Long, server: String): Long? + fun setLastMessageServerID(group: Long, server: String, newValue: Long) + fun removeLastMessageServerID(group: Long, server: String) + + fun getLastDeletionServerID(group: Long, server: String): Long? + fun setLastDeletionServerID(group: Long, server: String, newValue: Long) + fun removeLastDeletionServerID(group: Long, server: String) + + fun getOpenGroup(threadID: String): OpenGroup? + fun getAllOpenGroups(): Map + + fun setUserCount(group: Long, server: String, newValue: Int) + fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String) + fun getOpenGroupProfilePictureURL(group: Long, server: String): String? + } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt new file mode 100644 index 000000000..c9bacb0d8 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt @@ -0,0 +1,37 @@ +package org.session.libsession.messaging.opengroups + +import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error +import org.session.libsession.messaging.utilities.DotNetAPI +import java.util.* + +class OpenGroupAPIV2: DotNetAPI() { + + enum class Error { + GENERIC, + PARSING_FAILED, + DECRYPTION_FAILED, + SIGNING_FAILED, + INVALID_URL, + NO_PUBLIC_KEY + } + + companion object { + private val moderators: HashMap>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs) + const val DEFAULT_SERVER = "https://sessionopengroup.com" + const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b" + } + + + +} + +data class Info(val id: String, val name: String, val imageId: String?) + +fun Error.errorDescription() = when (this) { + Error.GENERIC -> "An error occurred." + Error.PARSING_FAILED -> "Invalid response." + Error.DECRYPTION_FAILED -> "Couldn't decrypt response." + Error.SIGNING_FAILED -> "Couldn't sign message." + Error.INVALID_URL -> "Invalid URL." + Error.NO_PUBLIC_KEY -> "Couldn't find server public key." +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2.kt new file mode 100644 index 000000000..db700d3cc --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2.kt @@ -0,0 +1,45 @@ +package org.session.libsession.messaging.opengroups + +import org.session.libsignal.utilities.JsonUtil +import java.util.* + +data class OpenGroupV2( + val server: String, + val room: String, + val id: String, + val name: String, + val publicKey: String, + val imageId: String? +) { + + constructor(server: String, room: String, name: String, publicKey: String, imageId: String?) : this( + server = server, + room = room, + id = "$server.$room", + name = name, + publicKey = publicKey, + imageId = imageId + ) + + companion object { + + fun fromJson(jsonAsString: String): OpenGroupV2? { + return try { + val json = JsonUtil.fromJson(jsonAsString) + if (!json.has("room")) return null + + val room = json.get("room").asText().toLowerCase(Locale.getDefault()) + val server = json.get("server").asText().toLowerCase(Locale.getDefault()) + val displayName = json.get("displayName").asText() + val publicKey = json.get("publicKey").asText() + val imageId = json.get("imageId").asText().let { str -> if (str.isEmpty()) null else str } + + OpenGroupV2(server, room, displayName, publicKey, imageId) + } catch (e: Exception) { + null + } + } + + } + +} \ No newline at end of file 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 bc2546b0a..9557ddb62 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 @@ -105,8 +105,10 @@ private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMes handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closeGroup.publicKey, closeGroup.name, closeGroup.encryptionKeyPair!!, closeGroup.members, closeGroup.admins, message.sentTimestamp!!) } val allOpenGroups = storage.getAllOpenGroups().map { it.value.server } + val allV2OpenGroups = storage.getAllV2OpenGroups().map { it.value.server } for (openGroup in message.openGroups) { if (allOpenGroups.contains(openGroup)) continue + // TODO: add in v2 storage.addOpenGroup(openGroup, 1) } if (message.displayName.isNotEmpty()) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupV2Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupV2Poller.kt new file mode 100644 index 000000000..21e0c0d0c --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupV2Poller.kt @@ -0,0 +1,225 @@ +package org.session.libsession.messaging.sending_receiving.pollers + +import com.google.protobuf.ByteString +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.deferred +import org.session.libsession.messaging.MessagingConfiguration +import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.jobs.MessageReceiveJob +import org.session.libsession.messaging.opengroups.* +import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.utilities.logging.Log +import org.session.libsignal.utilities.successBackground +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit + +class OpenGroupV2Poller(private val openGroup: OpenGroupV2, private val executorService: ScheduledExecutorService? = null) { + + private var hasStarted = false + @Volatile private var isPollOngoing = false + var isCaughtUp = false + + private val cancellableFutures = mutableListOf>() + + // region Convenience + private val userHexEncodedPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: "" + private var displayNameUpdates = setOf() + // endregion + + // region Settings + companion object { + private val pollForNewMessagesInterval: Long = 10 * 1000 + private val pollForDeletedMessagesInterval: Long = 60 * 1000 + private val pollForModeratorsInterval: Long = 10 * 60 * 1000 + private val pollForDisplayNamesInterval: Long = 60 * 1000 + } + // endregion + + // region Lifecycle + fun startIfNeeded() { + if (hasStarted || executorService == null) return + cancellableFutures += listOf( + executorService.scheduleAtFixedRate(::pollForNewMessages,0, pollForNewMessagesInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(::pollForDeletedMessages,0, pollForDeletedMessagesInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(::pollForModerators,0, pollForModeratorsInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(::pollForDisplayNames,0, pollForDisplayNamesInterval, TimeUnit.MILLISECONDS) + ) + hasStarted = true + } + + fun stop() { + cancellableFutures.forEach { future -> + future.cancel(false) + } + cancellableFutures.clear() + hasStarted = false + } + // endregion + + // region Polling + fun pollForNewMessages(): Promise { + return pollForNewMessages(false) + } + + private fun pollForNewMessages(isBackgroundPoll: Boolean): Promise { + if (isPollOngoing) { return Promise.of(Unit) } + isPollOngoing = true + val deferred = deferred() + // Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below + OpenGroupAPIV2.getMessages(openGroup.room, openGroup.server).successBackground { messages -> + // Process messages in the background + Log.d("Loki", "received ${messages.size} messages") + messages.forEach { message -> + try { + val senderPublicKey = message.senderPublicKey + fun generateDisplayName(rawDisplayName: String): String { + return "$rawDisplayName (...${senderPublicKey.takeLast(8)})" + } + val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName(message.displayName) + val id = openGroup.id.toByteArray() + // Main message + val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() + val body = if (message.body == message.timestamp.toString()) { "" } else { message.body } + dataMessageProto.setBody(body) + dataMessageProto.setTimestamp(message.timestamp) + // Attachments + val attachmentProtos = message.attachments.mapNotNull { attachment -> + try { + if (attachment.kind != OpenGroupMessage.Attachment.Kind.Attachment) { return@mapNotNull null } + val attachmentProto = SignalServiceProtos.AttachmentPointer.newBuilder() + attachmentProto.setId(attachment.serverID) + attachmentProto.setContentType(attachment.contentType) + attachmentProto.setSize(attachment.size) + attachmentProto.setFileName(attachment.fileName) + attachmentProto.setFlags(attachment.flags) + attachmentProto.setWidth(attachment.width) + attachmentProto.setHeight(attachment.height) + attachment.caption?.let { attachmentProto.setCaption(it) } + attachmentProto.setUrl(attachment.url) + attachmentProto.build() + } catch (e: Exception) { + Log.e("Loki","Failed to parse attachment as proto",e) + null + } + } + dataMessageProto.addAllAttachments(attachmentProtos) + // Link preview + val linkPreview = message.attachments.firstOrNull { it.kind == OpenGroupMessage.Attachment.Kind.LinkPreview } + if (linkPreview != null) { + val linkPreviewProto = SignalServiceProtos.DataMessage.Preview.newBuilder() + linkPreviewProto.setUrl(linkPreview.linkPreviewURL!!) + linkPreviewProto.setTitle(linkPreview.linkPreviewTitle!!) + val attachmentProto = SignalServiceProtos.AttachmentPointer.newBuilder() + attachmentProto.setId(linkPreview.serverID) + attachmentProto.setContentType(linkPreview.contentType) + attachmentProto.setSize(linkPreview.size) + attachmentProto.setFileName(linkPreview.fileName) + attachmentProto.setFlags(linkPreview.flags) + attachmentProto.setWidth(linkPreview.width) + attachmentProto.setHeight(linkPreview.height) + linkPreview.caption?.let { attachmentProto.setCaption(it) } + attachmentProto.setUrl(linkPreview.url) + linkPreviewProto.setImage(attachmentProto.build()) + dataMessageProto.addPreview(linkPreviewProto.build()) + } + // Quote + val quote = message.quote + if (quote != null) { + val quoteProto = SignalServiceProtos.DataMessage.Quote.newBuilder() + quoteProto.setId(quote.quotedMessageTimestamp) + quoteProto.setAuthor(quote.quoteePublicKey) + if (quote.quotedMessageBody != quote.quotedMessageTimestamp.toString()) { quoteProto.setText(quote.quotedMessageBody) } + dataMessageProto.setQuote(quoteProto.build()) + } + val messageServerID = message.serverID + // Profile + val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder() + profileProto.setDisplayName(senderDisplayName) + val profilePicture = message.profilePicture + if (profilePicture != null) { + profileProto.setProfilePicture(profilePicture.url) + dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey)) + } + dataMessageProto.setProfile(profileProto.build()) + /* TODO: the signal service proto needs to be synced with iOS + // Open group info + if (messageServerID != null) { + val openGroupProto = PublicChatInfo.newBuilder() + openGroupProto.setServerID(messageServerID) + dataMessageProto.setPublicChatInfo(openGroupProto.build()) + } + */ + // Signal group context + val groupProto = SignalServiceProtos.GroupContext.newBuilder() + groupProto.setId(ByteString.copyFrom(id)) + groupProto.setType(SignalServiceProtos.GroupContext.Type.DELIVER) + groupProto.setName(openGroup.displayName) + dataMessageProto.setGroup(groupProto.build()) + // Content + val content = SignalServiceProtos.Content.newBuilder() + content.setDataMessage(dataMessageProto.build()) + // Envelope + val builder = SignalServiceProtos.Envelope.newBuilder() + builder.type = SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER + builder.source = senderPublicKey + builder.sourceDevice = 1 + builder.setContent(content.build().toByteString()) + builder.timestamp = message.timestamp + builder.serverTimestamp = message.serverTimestamp + val envelope = builder.build() + val job = MessageReceiveJob(envelope.toByteArray(), isBackgroundPoll, messageServerID, openGroup.id) + Log.d("Loki", "Scheduling Job $job") + if (isBackgroundPoll) { + job.executeAsync().always { deferred.resolve(Unit) } + // The promise is just used to keep track of when we're done + } else { + JobQueue.shared.add(job) + } + } catch (e: Exception) { + Log.e("Loki", "Exception parsing message", e) + } + } + displayNameUpdates = displayNameUpdates + messages.map { it.senderPublicKey }.toSet() - userHexEncodedPublicKey + executorService?.schedule(::pollForDisplayNames, 0, TimeUnit.MILLISECONDS) + isCaughtUp = true + isPollOngoing = false + deferred.resolve(Unit) + }.fail { + Log.d("Loki", "Failed to get messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.") + isPollOngoing = false + } + return deferred.promise + } + + private fun pollForDisplayNames() { + if (displayNameUpdates.isEmpty()) { return } + val hexEncodedPublicKeys = displayNameUpdates + displayNameUpdates = setOf() + OpenGroupAPI.getDisplayNames(hexEncodedPublicKeys, openGroup.server).successBackground { mapping -> + for (pair in mapping.entries) { + if (pair.key == userHexEncodedPublicKey) continue + val senderDisplayName = "${pair.value} (...${pair.key.substring(pair.key.count() - 8)})" + MessagingConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName) + } + }.fail { + displayNameUpdates = displayNameUpdates.union(hexEncodedPublicKeys) + } + } + + private fun pollForDeletedMessages() { + OpenGroupAPI.getDeletedMessageServerIDs(openGroup.channel, openGroup.server).success { deletedMessageServerIDs -> + val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getMessageID(it) } + deletedMessageIDs.forEach { + MessagingConfiguration.shared.messageDataProvider.deleteMessage(it) + } + }.fail { + Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.") + } + } + + private fun pollForModerators() { + OpenGroupAPI.getModerators(openGroup.channel, openGroup.server) + } + // endregion +} \ No newline at end of file diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChat.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChat.kt index 1cf9ada3c..75dea6b4c 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChat.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChat.kt @@ -20,6 +20,7 @@ public data class PublicChat( @JvmStatic fun fromJSON(jsonAsString: String): PublicChat? { try { val json = JsonUtil.fromJson(jsonAsString) + if (!json.has("channel")) return null val channel = json.get("channel").asLong() val server = json.get("server").asText().toLowerCase() val displayName = json.get("displayName").asText() diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt index 0d24f218b..2fdf5c9db 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt @@ -24,6 +24,11 @@ interface LokiAPIDatabaseProtocol { fun getLastDeletionServerID(group: Long, server: String): Long? fun setLastDeletionServerID(group: Long, server: String, newValue: Long) fun setUserCount(group: Long, server: String, newValue: Int) + fun getLastMessageServerID(room: String, server: String): Long? + fun setLastMessageServerID(room: String, server: String, newValue: Long) + fun getLastDeletionServerID(room: String, server: String): Long? + fun setLastDeletionServerID(room: String, server: String, newValue: Long) + fun setUserCount(room: String, server: String, newValue: Int) fun getSessionRequestSentTimestamp(publicKey: String): Long? fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long) fun getSessionRequestProcessedTimestamp(publicKey: String): Long? From 9cdcdc43a604dfcb1d24e66131501fae4c66f56c Mon Sep 17 00:00:00 2001 From: Brice-W Date: Wed, 14 Apr 2021 16:37:04 +1000 Subject: [PATCH 19/99] redesign of group update messages management --- .../conversation/ConversationActivity.java | 18 +++----- .../securesms/database/MmsDatabase.java | 15 ++++--- .../securesms/database/SmsDatabase.java | 3 +- .../securesms/database/Storage.kt | 25 ++++------- .../database/model/MessageRecord.java | 2 + .../loki/protocol/ClosedGroupsProtocolV2.kt | 35 +++++++-------- .../service/ExpiringMessageManager.java | 19 ++++---- .../libsession/messaging/StorageProtocol.kt | 4 +- .../messages/control/ExpirationTimerUpdate.kt | 5 +++ .../messages/signal/IncomingGroupMessage.java | 14 ++---- .../OutgoingExpirationUpdateMessage.java | 7 +-- .../signal/OutgoingGroupMediaMessage.java | 44 +++++-------------- .../MessageReceiverHandler.kt | 35 ++++++++------- .../MessageSenderClosedGroup.kt | 15 ++++--- .../utilities/UpdateMessageBuilder.kt | 15 ++++--- .../api/messages/SignalServiceGroup.java | 2 +- 16 files changed, 117 insertions(+), 141 deletions(-) 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 de07e941b..1582f3622 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -89,7 +89,6 @@ import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.threads.DistributionTypes; -import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.MediaTypes; import org.session.libsignal.libsignal.InvalidMessageException; @@ -159,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; @@ -176,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; @@ -807,16 +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()); - String displayedMessage = UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(getApplicationContext(), expirationTime, null, false); - OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient, displayedMessage); - 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/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 700d64101..de4248016 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -515,7 +515,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); + return new OutgoingGroupMediaMessage(recipient, body, null, attachments, timestamp, 0, quote, contacts, previews); } else if (Types.isExpirationTimerUpdate(outboxType)) { return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn); } @@ -689,8 +689,14 @@ 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 = ((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 { @@ -746,8 +752,7 @@ public class MmsDatabase extends MessagingDatabase { 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; + type |= Types.GROUP_UPDATE_BIT; } if (message.isExpirationUpdate()) { 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 d5c34c5c5..a5affbb56 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; + type |= Types.GROUP_UPDATE_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 1c6a28b56..30c515e91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -400,31 +400,22 @@ 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 messageBody = UpdateMessageBuilder.buildGroupUpdateMessage(context, group, senderPublicKey) - val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), messageBody) + val infoMessage = IncomingGroupMessage(m, groupID, messageBody) 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 group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) + val messageBody = UpdateMessageBuilder.buildGroupUpdateMessage(context, group, null, true) + val infoMessage = OutgoingGroupMediaMessage(recipient, messageBody, groupID, null, sentTimestamp, 0, false, 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/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index b41e4ae78..f4c9bb95a 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,6 +24,8 @@ import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import network.loki.messenger.R; + +import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; import org.session.libsession.database.documents.IdentityKeyMismatch; 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 6d6f5b497..df1d7c0b5 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 @@ -26,7 +25,6 @@ import org.session.libsession.messaging.sending_receiving.sendEncryptionKeyPair import org.session.libsession.messaging.threads.Address 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.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences @@ -105,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.NEW_GROUP, 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) @@ -157,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.MEMBER_REMOVED + 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) } } @@ -192,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.MEMBER_ADDED, 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 @@ -231,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.NAME_CHANGE, name, members, admins, sentTimestamp) + DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) } } @@ -273,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) } } @@ -386,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/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index 8cc1224d7..8d10605fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -78,8 +78,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); @@ -97,9 +97,10 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Long sentTimestamp = message.getSentTimestamp(); String groupId = message.getGroupPublicKey(); int duration = message.getDuration(); + String messageBody = UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, duration, senderPublicKey, false); 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 @@ -117,7 +118,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1, duration * 1000L, true, false, - Optional.absent(), + Optional.of(messageBody), groupInfo, Optional.absent(), Optional.absent(), @@ -137,27 +138,25 @@ 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(); + String messageBody = UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, duration, null, true); - 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 { 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()); + OutgoingGroupMediaMessage infoMessage = new OutgoingGroupMediaMessage(recipient, messageBody, groupId, 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, + messageBody, Collections.emptyList(), message.getSentTimestamp(), -1, 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 d4aaf2b2c..d7d5624e2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -117,9 +117,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 85f34eed5..5dff39072 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 b0a7de08e..901f151cd 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,11 @@ import static org.session.libsignal.service.internal.push.SignalServiceProtos.Gr public class IncomingGroupMessage extends IncomingTextMessage { - private final GroupContext groupContext; + private final String groupID; - public IncomingGroupMessage(IncomingTextMessage base, GroupContext groupContext, String body) { + public IncomingGroupMessage(IncomingTextMessage base, String groupID, String body) { super(base, body); - this.groupContext = groupContext; + this.groupID = groupID; } @Override @@ -16,12 +16,4 @@ 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; - } - } 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 94f1016b5..087751e2f 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 @@ -8,17 +8,18 @@ import org.session.libsession.messaging.threads.recipients.Recipient; import java.util.Collections; import java.util.LinkedList; +// TODO this class could be deleted if its usage in MmsDatabase.getOutgoingMessage is replaced by something elsex public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage { - public OutgoingExpirationUpdateMessage(Recipient recipient, String body, long sentTimeMillis, long expiresIn) { + public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) { super(recipient, "", new LinkedList(), sentTimeMillis, DistributionTypes.CONVERSATION, expiresIn, true, null, Collections.emptyList(), Collections.emptyList()); } public static OutgoingExpirationUpdateMessage from(ExpirationTimerUpdate message, - Recipient recipient, String body) { - return new OutgoingExpirationUpdateMessage(recipient, body, message.getSentTimestamp(), message.getDuration() * 1000); + Recipient recipient) { + return new OutgoingExpirationUpdateMessage(recipient, message.getSentTimestamp(), message.getDuration() * 1000); } @Override 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 7f151ae7e..a726286be 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,10 +19,11 @@ import java.util.List; public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { - private final GroupContext group; + private final String groupID; public OutgoingGroupMediaMessage(@NonNull Recipient recipient, - @NonNull String encodedGroupContext, + @NonNull String body, + @Nullable String groupId, @NonNull List avatar, long sentTimeMillis, long expiresIn, @@ -31,30 +32,15 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { @NonNull List previews) throws IOException { - super(recipient, encodedGroupContext, avatar, sentTimeMillis, + super(recipient, body, avatar, sentTimeMillis, DistributionTypes.CONVERSATION, expiresIn, false, quote, contacts, previews); - this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext)); + this.groupID = groupId; } 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, @@ -63,12 +49,12 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { @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); - this.group = group; + this.groupID = groupId; } @Override @@ -76,15 +62,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { return true; } - public boolean isGroupUpdate() { - return group.getType().getNumber() == GroupContext.Type.UPDATE_VALUE; - } - - public boolean isGroupQuit() { - return group.getType().getNumber() == GroupContext.Type.QUIT_VALUE; - } - - public GroupContext getGroupContext() { - return group; + public String getGroupId() { + return groupID; } } 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 00415aa9c..b860f0273 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 @@ -243,8 +243,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.NEW_GROUP, 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 @@ -304,9 +311,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) { @@ -378,9 +384,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.NAME_CHANGE, name, members, admins, message.sentTimestamp!!) + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.NAME_CHANGE, name, members, admins, message.sentTimestamp!!) } } @@ -413,9 +419,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.MEMBER_ADDED, 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 @@ -477,17 +483,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.MEMBER_REMOVED + 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!!) } } @@ -533,9 +538,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 58f8ffe64..143fa7fb3 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 index 367e549c0..2e2163f1a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -19,7 +19,7 @@ object UpdateMessageBuilder { } else { sender } when (updateType) { - SignalServiceGroup.Type.NEW_GROUP -> { + SignalServiceGroup.Type.CREATION -> { message = if (isOutgoing) { context.getString(R.string.MessageRecord_you_created_a_new_group) } else { @@ -79,24 +79,29 @@ object UpdateMessageBuilder { return message } - fun buildExpirationTimerMessage(context: Context, duration: Int, sender: String? = null, isOutgoing: Boolean = false): String { - val seconds = (duration!! / 1000) + 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 (seconds <= 0) { + 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, seconds) + 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 one this is merged in fun buildDataExtractionMessage(): String { return "" } + /*TODO retro compatibilite old update messages (MessageRecord) + ThreadRecord to display specific messages? (hard unless we can get the incoming / outgoing messages) + Clean code (comments, logs...) + Delete OutgoingExpirationUpdateMessage (check how its used in MmsDatabase l.520 to save messages and how to do the same when getting messages from db without breaking it) + */ } \ No newline at end of file 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 68856a87c..5115844a1 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 @@ -38,7 +38,7 @@ public class SignalServiceGroup { DELIVER, QUIT, REQUEST_INFO, - NEW_GROUP, + CREATION, NAME_CHANGE, MEMBER_ADDED, MEMBER_REMOVED From 6f46bbefbeb0a90d3515bf179eaecb8a49f10968 Mon Sep 17 00:00:00 2001 From: Harris Date: Wed, 14 Apr 2021 23:25:38 +1000 Subject: [PATCH 20/99] feat: add more opengroupv2 functions and classes --- .../messaging/opengroups/OpenGroupAPIV2.kt | 15 +++--- .../opengroups/OpenGroupV2Message.kt | 52 +++++++++++++++++++ .../pollers/OpenGroupV2Poller.kt | 2 +- 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2Message.kt diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt index c9bacb0d8..a0ee35bc9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt @@ -1,10 +1,11 @@ package org.session.libsession.messaging.opengroups +import nl.komponents.kovenant.Promise import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error import org.session.libsession.messaging.utilities.DotNetAPI import java.util.* -class OpenGroupAPIV2: DotNetAPI() { +object OpenGroupAPIV2: DotNetAPI() { enum class Error { GENERIC, @@ -15,14 +16,14 @@ class OpenGroupAPIV2: DotNetAPI() { NO_PUBLIC_KEY } - companion object { - private val moderators: HashMap>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs) - const val DEFAULT_SERVER = "https://sessionopengroup.com" - const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b" + private val moderators: HashMap>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs) + const val DEFAULT_SERVER = "https://sessionopengroup.com" + const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b" + + fun getMessages(room: String, server: String): Promise, Exception> { + } - - } data class Info(val id: String, val name: String, val imageId: String?) diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2Message.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2Message.kt new file mode 100644 index 000000000..a40bd53bf --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2Message.kt @@ -0,0 +1,52 @@ +package org.session.libsession.messaging.opengroups + +import org.session.libsession.messaging.MessagingConfiguration +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.logging.Log +import org.whispersystems.curve25519.Curve25519 + +data class OpenGroupV2Message( + val serverID: Long?, + val sender: String?, + val sentTimestamp: Long, + // The serialized protobuf in base64 encoding + val base64EncodedData: String, + // When sending a message, the sender signs the serialized protobuf with their private key so that + // a receiving user can verify that the message wasn't tampered with. + val base64EncodedSignature: String? +) { + + companion object { + private val curve = Curve25519.getInstance(Curve25519.BEST) + } + + fun sign(): OpenGroupV2Message? { + if (base64EncodedData.isEmpty()) return null + val (publicKey, privateKey) = MessagingConfiguration.shared.storage.getUserKeyPair() ?: return null + + if (sender != publicKey) return null // only sign our own messages? + + val signature = try { + curve.calculateSignature(privateKey, Base64.decode(base64EncodedData)) + } catch (e: Exception) { + Log.e("Loki", "Couldn't sign OpenGroupV2Message", e) + return null + } + + return copy(base64EncodedSignature = Base64.encodeBytes(signature)) + } + + fun toJSON(): Map { + val jsonMap = mutableMapOf("data" to base64EncodedData, "timestamp" to sentTimestamp) + serverID?.let { jsonMap["server_id"] = serverID } + sender?.let { jsonMap["public_key"] = sender } + base64EncodedSignature?.let { jsonMap["signature"] = base64EncodedSignature } + } + + fun fromJSON(json: Map): OpenGroupV2Message? { + if (!json.containsKey("data") || !json.containsKey("timestamp")) return null + + } + + +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupV2Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupV2Poller.kt index 21e0c0d0c..d1480670b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupV2Poller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupV2Poller.kt @@ -76,7 +76,7 @@ class OpenGroupV2Poller(private val openGroup: OpenGroupV2, private val executor fun generateDisplayName(rawDisplayName: String): String { return "$rawDisplayName (...${senderPublicKey.takeLast(8)})" } - val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName(message.displayName) + val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.room, openGroup.server) ?: generateDisplayName(message.displayName) val id = openGroup.id.toByteArray() // Main message val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() From b7272bdf66f9afe096c66d1c138f72fc68fa05e9 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 15 Apr 2021 10:42:47 +1000 Subject: [PATCH 21/99] Increase TTL & remove PoW --- .../libsession/messaging/messages/Message.kt | 2 +- .../control/ClosedGroupControlMessage.kt | 4 ++-- .../messages/control/ConfigurationMessage.kt | 1 - .../sending_receiving/MessageSender.kt | 8 ++----- .../org/session/libsession/snode/SnodeAPI.kt | 23 +++++++++++-------- .../session/libsession/snode/SnodeMessage.kt | 10 ++++---- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt index 6f2a91635..ef60aaa83 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt @@ -15,7 +15,7 @@ abstract class Message { var groupPublicKey: String? = null var openGroupServerMessageID: Long? = null - open val ttl: Long = 2 * 24 * 60 * 60 * 1000 + open val ttl: Long = 14 * 24 * 60 * 60 * 1000 open val isSelfSendValid: Boolean = false // validation diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index c02cfc4c1..fa6544fc2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -19,8 +19,8 @@ class ClosedGroupControlMessage() : ControlMessage() { override val ttl: Long = run { when (kind) { - is Kind.EncryptionKeyPair -> return@run 4 * 24 * 60 * 60 * 1000 - else -> return@run 2 * 24 * 60 * 60 * 1000 + is Kind.EncryptionKeyPair -> 14 * 24 * 60 * 60 * 1000 + else -> 14 * 24 * 60 * 60 * 1000 } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt index fe06cc6c0..2fbf13588 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt @@ -87,7 +87,6 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: } } - override val ttl: Long = 4 * 24 * 60 * 60 * 1000 override val isSelfSendValid: Boolean = true companion object { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 225c38b7d..eac8acb32 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -31,7 +31,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview as SignalLinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote - object MessageSender { // Error @@ -147,15 +146,12 @@ object MessageSender { is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!") } val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext) - // Calculate proof of work + // Send the result if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { SnodeConfiguration.shared.broadcaster.broadcast("calculatingPoW", message.sentTimestamp!!) } - val recipient = message.recipient!! val base64EncodedData = Base64.encodeBytes(wrappedMessage) - val nonce = ProofOfWork.calculate(base64EncodedData, recipient, message.sentTimestamp!!, message.ttl.toInt()) ?: throw Error.ProofOfWorkCalculationFailed - // Send the result - val snodeMessage = SnodeMessage(recipient, base64EncodedData, message.ttl, message.sentTimestamp!!, nonce) + val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, message.sentTimestamp!!) if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { SnodeConfiguration.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!) } diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 9be9db1d2..9f8d417ed 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -12,6 +12,7 @@ import org.session.libsignal.service.loki.api.utilities.HTTP import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.utilities.Broadcaster import org.session.libsignal.service.loki.utilities.prettifiedDescription +import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.retryIfNeeded import org.session.libsignal.utilities.* import org.session.libsignal.utilities.logging.Log @@ -37,16 +38,18 @@ object SnodeAPI { // use port 4433 if API level can handle network security config and enforce pinned certificates private val seedPort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433 - private val seedNodePool: Set = setOf( - "https://storage.seed1.loki.network:$seedPort", - "https://storage.seed3.loki.network:$seedPort", - "https://public.loki.foundation:$seedPort" - ) - internal val snodeFailureThreshold = 4 + private val seedNodePool by lazy { + if (useTestnet) { + setOf( "http://public.loki.foundation:38157" ) + } else { + setOf( "https://storage.seed1.loki.network:$seedPort", "https://storage.seed3.loki.network:$seedPort", "https://public.loki.foundation:$seedPort" ) + } + } + private val snodeFailureThreshold = 4 private val targetSwarmSnodeCount = 2 - private val useOnionRequests = true + internal val useTestnet = false internal var powDifficulty = 1 // Error @@ -164,7 +167,7 @@ object SnodeAPI { cachedSwarmCopy.addAll(cachedSwarm) return task { cachedSwarmCopy } } else { - val parameters = mapOf( "pubKey" to publicKey ) + val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey ) return getRandomSnode().bind { invoke(Snode.Method.GetSwarm, it, publicKey, parameters) }.map(sharedContext) { @@ -177,7 +180,7 @@ object SnodeAPI { fun getRawMessages(snode: Snode, publicKey: String): RawResponsePromise { val lastHashValue = database.getLastMessageHashValue(snode, publicKey) ?: "" - val parameters = mapOf( "pubKey" to publicKey, "lastHash" to lastHashValue ) + val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey, "lastHash" to lastHashValue ) return invoke(Snode.Method.GetMessages, snode, publicKey, parameters) } @@ -190,7 +193,7 @@ object SnodeAPI { } fun sendMessage(message: SnodeMessage): Promise, Exception> { - val destination = message.recipient + val destination = if (useTestnet) message.recipient.removing05PrefixIfNeeded() else message.recipient return retryIfNeeded(maxRetryCount) { getTargetSnodes(destination).map { swarm -> swarm.map { snode -> diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt index 558447c54..0a653e400 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt @@ -1,5 +1,7 @@ package org.session.libsession.snode +import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded + data class SnodeMessage( // The hex encoded public key of the recipient. val recipient: String, @@ -8,16 +10,14 @@ data class SnodeMessage( // The time to live for the message in milliseconds. val ttl: Long, // When the proof of work was calculated. - val timestamp: Long, - // The base 64 encoded proof of work. - val nonce: String + val timestamp: Long ) { internal fun toJSON(): Map { return mutableMapOf( - "pubKey" to recipient, + "pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient, "data" to data, "ttl" to ttl.toString(), "timestamp" to timestamp.toString(), - "nonce" to nonce) + "nonce" to "") } } From abb1db7a7e40c812bcbf4ba646609793a01a59a7 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 15 Apr 2021 14:41:29 +1000 Subject: [PATCH 22/99] new approach in update saving --- .../conversation/ConversationUpdateItem.java | 4 -- .../securesms/database/MmsDatabase.java | 23 ++++----- .../securesms/database/MmsSmsColumns.java | 3 ++ .../securesms/database/SmsDatabase.java | 2 +- .../securesms/database/Storage.kt | 11 +++-- .../database/model/DisplayRecord.java | 8 +++- .../database/model/MessageRecord.java | 40 +++++----------- .../database/model/ThreadRecord.java | 4 +- .../loki/utilities/GroupDescription.kt | 2 +- .../service/ExpiringMessageManager.java | 34 ++----------- .../messages/signal/IncomingGroupMessage.java | 6 ++- .../OutgoingExpirationUpdateMessage.java | 25 ++++++---- .../signal/OutgoingGroupMediaMessage.java | 27 ++++------- .../messages/signal/OutgoingMediaMessage.java | 8 +--- .../signal/OutgoingSecureMediaMessage.java | 3 +- .../utilities/UpdateMessageBuilder.kt | 24 ++++------ .../messaging/utilities/UpdateMessageData.kt | 48 +++++++++++++++++++ 17 files changed, 133 insertions(+), 139 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt 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 7efd5ca4e..46e0bef27 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -14,11 +14,9 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - 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; @@ -174,8 +172,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); //TODO Brice: could be removed if GroupDescription is removed 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 de4248016..d4a26b6ea 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, null, 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,7 +679,12 @@ public class MmsDatabase extends MessagingDatabase { { if (threadId == -1) { if(retrieved.isGroup()) { - String decodedGroupId = ((OutgoingGroupMediaMessage)retrieved).getGroupId(); + String decodedGroupId; + if (retrieved instanceof OutgoingExpirationUpdateMessage) { + decodedGroupId = ((OutgoingExpirationUpdateMessage)retrieved).getGroupId(); + } else { + decodedGroupId = ((OutgoingGroupMediaMessage)retrieved).getGroupId(); + } String groupId; try { groupId = GroupUtil.doubleEncodeGroupID(decodedGroupId); @@ -751,8 +746,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()) { - type |= Types.GROUP_UPDATE_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 36b86023e..ae8a47006 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; // Encrypted Storage Information XXX public static final long ENCRYPTION_MASK = 0xFF000000; @@ -213,6 +214,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 a5affbb56..226645b39 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -347,7 +347,7 @@ public class SmsDatabase extends MessagingDatabase { type |= Types.SECURE_MESSAGE_BIT; } else if (message.isGroup()) { type |= Types.SECURE_MESSAGE_BIT; - type |= Types.GROUP_UPDATE_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 30c515e91..dc2452b51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -22,6 +22,7 @@ import org.session.libsession.messaging.threads.Address 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 @@ -41,6 +42,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? { @@ -403,8 +405,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, 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 messageBody = UpdateMessageBuilder.buildGroupUpdateMessage(context, group, senderPublicKey) - val infoMessage = IncomingGroupMessage(m, groupID, messageBody) + val updateData = UpdateMessageData.buildGroupUpdate(type, name, members).toJSON() + val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val smsDB = DatabaseFactory.getSmsDatabase(context) smsDB.insertMessageInbox(infoMessage) } @@ -413,9 +415,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val userPublicKey = getUserPublicKey() val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) - val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) - val messageBody = UpdateMessageBuilder.buildGroupUpdateMessage(context, group, null, true) - val infoMessage = OutgoingGroupMediaMessage(recipient, messageBody, groupID, 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 ab40f3381..d3be43d87 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 f4c9bb95a..631883eb5 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 @@ -26,14 +26,13 @@ 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; @@ -92,39 +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 (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 e2a286c85..eab5931c8 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/utilities/GroupDescription.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt index dd642beb5..3844b9fbf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt @@ -13,7 +13,7 @@ import network.loki.messenger.R import org.session.libsignal.utilities.logging.Log import java.io.IOException -//TODO Brice: that class should be deprecated +//TODO that class isn't used anymore class GroupDescription(context: Context, groupContext: SignalServiceProtos.GroupContext?) { private val context: Context private val groupContext: SignalServiceProtos.GroupContext? 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 8d10605fc..dd23927bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -2,16 +2,12 @@ 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.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.TextSecurePreferences; @@ -27,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; @@ -97,7 +92,6 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Long sentTimestamp = message.getSentTimestamp(); String groupId = message.getGroupPublicKey(); int duration = message.getDuration(); - String messageBody = UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, duration, senderPublicKey, false); Optional groupInfo = Optional.absent(); Address address = Address.fromSerialized(senderPublicKey); @@ -118,7 +112,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1, duration * 1000L, true, false, - Optional.of(messageBody), + Optional.absent(), groupInfo, Optional.absent(), Optional.absent(), @@ -141,36 +135,18 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Long sentTimestamp = message.getSentTimestamp(); String groupId = message.getGroupPublicKey(); int duration = message.getDuration(); - String messageBody = UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, duration, null, true); 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 - OutgoingGroupMediaMessage infoMessage = new OutgoingGroupMediaMessage(recipient, messageBody, groupId, 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, - messageBody, - 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/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 901f151cd..125267afb 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 @@ -5,10 +5,12 @@ import static org.session.libsignal.service.internal.push.SignalServiceProtos.Gr public class IncomingGroupMessage extends IncomingTextMessage { private final String groupID; + private final boolean updateMessage; - public IncomingGroupMessage(IncomingTextMessage base, String groupID, String body) { + public IncomingGroupMessage(IncomingTextMessage base, String groupID, String body, boolean updateMessage) { super(base, body); this.groupID = groupID; + this.updateMessage = updateMessage; } @Override @@ -16,4 +18,6 @@ public class IncomingGroupMessage extends IncomingTextMessage { return true; } + 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 087751e2f..b45f33c78 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; @@ -8,18 +7,15 @@ import org.session.libsession.messaging.threads.recipients.Recipient; import java.util.Collections; import java.util.LinkedList; -// TODO this class could be deleted if its usage in MmsDatabase.getOutgoingMessage is replaced by something elsex 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 @@ -27,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 a726286be..574c47cd1 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 @@ -20,23 +20,7 @@ import java.util.List; public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { private final String groupID; - - public OutgoingGroupMediaMessage(@NonNull Recipient recipient, - @NonNull String body, - @Nullable String groupId, - @NonNull List avatar, - long sentTimeMillis, - long expiresIn, - @Nullable QuoteModel quote, - @NonNull List contacts, - @NonNull List previews) - throws IOException - { - super(recipient, body, avatar, sentTimeMillis, - DistributionTypes.CONVERSATION, expiresIn, false, quote, contacts, previews); - - this.groupID = groupId; - } + private final boolean isUpdateMessage; public OutgoingGroupMediaMessage(@NonNull Recipient recipient, @NonNull String body, @@ -44,7 +28,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { @Nullable final Attachment avatar, long sentTime, long expireIn, - boolean expirationUpdate, + boolean updateMessage, @Nullable QuoteModel quote, @NonNull List contacts, @NonNull List previews) @@ -52,9 +36,10 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { 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.groupID = groupId; + this.isUpdateMessage = updateMessage; } @Override @@ -65,4 +50,8 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { public String getGroupId() { return groupID; } + + 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 9a6618dc4..d8d4dff3c 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 c7822d8b9..8b5e7ddef 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/utilities/UpdateMessageBuilder.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt index 2e2163f1a..6d3dbb858 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -1,17 +1,15 @@ package org.session.libsession.messaging.utilities import android.content.Context -import android.text.SpannableString import org.session.libsession.R import org.session.libsession.messaging.MessagingConfiguration -import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.utilities.ExpirationUtil import org.session.libsignal.service.api.messages.SignalServiceGroup object UpdateMessageBuilder { - fun buildGroupUpdateMessage(context: Context, groupInfo: SignalServiceGroup, sender: String? = null, isOutgoing: Boolean = false): String { - val updateType = groupInfo.type + fun buildGroupUpdateMessage(context: Context, updateData: UpdateMessageData, sender: String? = null, isOutgoing: Boolean = false): String { + val updateType = updateData.type var message: String = "" if (!isOutgoing && sender == null) return message val senderName: String? = if (!isOutgoing) { @@ -28,13 +26,13 @@ object UpdateMessageBuilder { } SignalServiceGroup.Type.NAME_CHANGE -> { message = if (isOutgoing) { - context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, groupInfo.name.get()) + 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, groupInfo.name.get()) + context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.groupName) } } SignalServiceGroup.Type.MEMBER_ADDED -> { - val members = groupInfo.members.get().joinToString(", ") { + val members = updateData.updatedMembers.joinToString(", ") { MessagingConfiguration.shared.storage.getDisplayNameForRecipient(it) ?: it } message = if (isOutgoing) { @@ -47,7 +45,7 @@ object UpdateMessageBuilder { val storage = MessagingConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! // 1st case: you are part of the removed members - message = if (userPublicKey in groupInfo.members.get()) { + message = if (userPublicKey in updateData.updatedMembers) { if (isOutgoing) { context.getString(R.string.MessageRecord_left_group) } else { @@ -55,7 +53,7 @@ object UpdateMessageBuilder { } } else { // 2nd case: you are not part of the removed members - val members = groupInfo.members.get().joinToString(", ") { + val members = updateData.updatedMembers.joinToString(", ") { storage.getDisplayNameForRecipient(it) ?: it } if (isOutgoing) { @@ -94,14 +92,8 @@ object UpdateMessageBuilder { } } - //TODO one this is merged in + //TODO do this when the current update is merged fun buildDataExtractionMessage(): String { return "" } - - /*TODO retro compatibilite old update messages (MessageRecord) - ThreadRecord to display specific messages? (hard unless we can get the incoming / outgoing messages) - Clean code (comments, logs...) - Delete OutgoingExpirationUpdateMessage (check how its used in MmsDatabase l.520 to save messages and how to do the same when getting messages from db without breaking it) - */ } \ No newline at end of file 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 000000000..bca6c7acf --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt @@ -0,0 +1,48 @@ +package org.session.libsession.messaging.utilities + +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 type: SignalServiceGroup.Type = SignalServiceGroup.Type.UNKNOWN + var groupName: String? = null + var updatedMembers: Collection = Collections.emptyList() + + constructor(type: SignalServiceGroup.Type, groupName: String?, updatedMembers: Collection): this() { + this.type = type + this.groupName = groupName + this.updatedMembers = updatedMembers + } + + 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(type, name, Collections.emptyList()) + SignalServiceGroup.Type.MEMBER_ADDED -> UpdateMessageData(type,null, members) + SignalServiceGroup.Type.MEMBER_REMOVED -> UpdateMessageData(type,null, members) + else -> UpdateMessageData(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(SignalServiceGroup.Type.UNKNOWN, null, Collections.emptyList()) + } + } + } + + fun toJSON(): String { + return JsonUtil.toJson(this) + } + +} \ No newline at end of file From a346bb4ea528db020c7e9cd13893046c5fcdcd46 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 15 Apr 2021 15:44:42 +1000 Subject: [PATCH 23/99] clean --- .../libsession/messaging/utilities/UpdateMessageBuilder.kt | 2 +- .../libsession/messaging/utilities/UpdateMessageData.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) 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 index 6d3dbb858..85be97837 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -96,4 +96,4 @@ object UpdateMessageBuilder { fun buildDataExtractionMessage(): String { return "" } -} \ No newline at end of file +} 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 index bca6c7acf..a21e403b7 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt @@ -44,5 +44,4 @@ class UpdateMessageData () { fun toJSON(): String { return JsonUtil.toJson(this) } - -} \ No newline at end of file +} From 2a1dfff8c46e5da8eb01f803fc2a674a119c7ddb Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 15 Apr 2021 16:33:40 +1000 Subject: [PATCH 24/99] translations added --- app/src/main/res/values-fr/strings.xml | 40 ++++++++++++++++++++++++++ app/src/main/res/values/strings.xml | 4 +-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d562e0c62..728266183 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -418,6 +418,15 @@ 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 avez appelé Contact appelé Appel manqué @@ -1483,4 +1492,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 ac5121c1d..4ed3697e6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -495,8 +495,8 @@ 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 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. From 96e604d06b8a59bf8987cdf916bda7d9145ba533 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 15 Apr 2021 17:17:55 +1000 Subject: [PATCH 25/99] feat: more opengroup in chat manager, poller and API. refactor mentions to libsession --- .../securesms/ApplicationContext.java | 2 +- .../conversation/ConversationActivity.java | 53 +++---- .../securesms/database/Storage.kt | 10 ++ .../securesms/loki/activities/HomeActivity.kt | 2 +- .../securesms/loki/api/PublicChatManager.kt | 38 ++++- .../loki/utilities/MentionManagerUtilities.kt | 4 +- .../views/MentionCandidateSelectionView.kt | 2 +- .../loki/views/MentionCandidateView.kt | 2 +- .../loki/views/ProfilePictureView.kt | 2 +- .../libsession/messaging/StorageProtocol.kt | 7 +- .../messaging/opengroups/OpenGroupAPIV2.kt | 149 ++++++++++++++++-- ...roupV2Message.kt => OpenGroupMessageV2.kt} | 20 ++- .../libsession}/utilities/mentions/Mention.kt | 2 +- .../utilities/mentions/MentionsManager.kt | 11 +- 14 files changed, 241 insertions(+), 63 deletions(-) rename libsession/src/main/java/org/session/libsession/messaging/opengroups/{OpenGroupV2Message.kt => OpenGroupMessageV2.kt} (69%) rename {libsignal/src/main/java/org/session/libsignal/service/loki => libsession/src/main/java/org/session/libsession}/utilities/mentions/Mention.kt (52%) rename {libsignal/src/main/java/org/session/libsignal/service/loki => libsession/src/main/java/org/session/libsession}/utilities/mentions/MentionsManager.kt (80%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 0ebcdc36a..77f6614dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -45,6 +45,7 @@ import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; import org.session.libsession.utilities.dynamiclanguage.LocaleParser; +import org.session.libsession.utilities.mentions.MentionsManager; import org.session.libsession.utilities.preferences.ProfileKeyUtil; import org.session.libsignal.service.api.util.StreamDetails; import org.session.libsignal.service.loki.api.PushNotificationAPI; @@ -52,7 +53,6 @@ import org.session.libsignal.service.loki.api.SnodeAPI; import org.session.libsignal.service.loki.api.SwarmAPI; import org.session.libsignal.service.loki.api.fileserver.FileServerAPI; import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; -import org.session.libsignal.service.loki.utilities.mentions.MentionsManager; import org.session.libsignal.utilities.logging.Log; import org.signal.aesgcmprovider.AesGcmProvider; import org.thoughtcrime.securesms.components.TypingStatusSender; 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 3cceeb089..8806cc23f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -83,21 +83,42 @@ import com.annimon.stream.Stream; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; - - import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; +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.session.libsession.messaging.messages.signal.OutgoingTextMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage; +import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; +import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; +import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; +import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; +import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.DistributionTypes; +import org.session.libsession.messaging.threads.GroupRecord; +import org.session.libsession.messaging.threads.recipients.Recipient; +import org.session.libsession.messaging.threads.recipients.RecipientFormattingException; +import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener; +import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.MediaTypes; +import org.session.libsession.utilities.ServiceUtil; +import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.ViewUtil; +import org.session.libsession.utilities.concurrent.AssertedSuccessListener; +import org.session.libsession.utilities.mentions.Mention; +import org.session.libsession.utilities.mentions.MentionsManager; +import org.session.libsession.utilities.views.Stub; import org.session.libsignal.libsignal.InvalidMessageException; import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.service.loki.api.opengroups.PublicChat; -import org.session.libsignal.service.loki.utilities.mentions.Mention; -import org.session.libsignal.service.loki.utilities.mentions.MentionsManager; import org.session.libsignal.service.loki.utilities.HexEncodingKt; import org.session.libsignal.service.loki.utilities.PublicKeyValidation; +import org.session.libsignal.utilities.concurrent.ListenableFuture; +import org.session.libsignal.utilities.concurrent.SettableFuture; +import org.session.libsignal.utilities.logging.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ExpirationDialog; import org.thoughtcrime.securesms.MediaOverviewActivity; @@ -121,7 +142,6 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; -import org.session.libsession.messaging.threads.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -135,7 +155,6 @@ import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel; -import org.session.libsignal.utilities.logging.Log; import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity; import org.thoughtcrime.securesms.loki.activities.HomeActivity; import org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker; @@ -158,9 +177,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; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -169,30 +185,11 @@ import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.BlobProvider; -import org.session.libsession.messaging.threads.recipients.Recipient; -import org.session.libsession.messaging.threads.recipients.RecipientFormattingException; -import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener; 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.util.BitmapUtil; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.PushCharacterCalculator; -import org.session.libsession.utilities.ServiceUtil; -import org.session.libsession.utilities.Util; - -import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; -import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; -import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.session.libsession.messaging.threads.GroupRecord; -import org.session.libsession.utilities.ExpirationUtil; -import org.session.libsession.utilities.views.Stub; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.concurrent.AssertedSuccessListener; -import org.session.libsignal.utilities.concurrent.ListenableFuture; -import org.session.libsignal.utilities.concurrent.SettableFuture; -import org.session.libsession.utilities.TextSecurePreferences; import java.io.IOException; import java.text.SimpleDateFormat; 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 62caac50f..445fdb36b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -274,11 +274,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(groupID, publicKey, displayName) } + override fun setOpenGroupDisplayName(publicKey: String, room: String, server: String, displayName: String) { + val groupID = "$server.$room" + DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(groupID, publicKey, displayName) + } + override fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String? { val groupID = "$server.$channel" return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(groupID, publicKey) } + override fun getOpenGroupDisplayName(publicKey: String, room: String, server: String): String? { + val groupID = "$server.$room" + return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(groupID, publicKey) + } + override fun getLastMessageServerId(room: String, server: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(room, server) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 8ed97bf3e..3442c98c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -33,7 +33,7 @@ import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.utilities.* -import org.session.libsignal.service.loki.utilities.mentions.MentionsManager +import org.session.libsession.utilities.mentions.MentionsManager import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.ApplicationContext diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index ef36d98de..ab587c817 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -6,10 +6,9 @@ import android.graphics.Bitmap import android.text.TextUtils import androidx.annotation.WorkerThread import org.session.libsession.messaging.MessagingConfiguration -import org.session.libsession.messaging.opengroups.OpenGroup -import org.session.libsession.messaging.opengroups.OpenGroupAPI -import org.session.libsession.messaging.opengroups.OpenGroupInfo +import org.session.libsession.messaging.opengroups.* import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller +import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupV2Poller import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.Util import org.session.libsignal.service.loki.api.opengroups.PublicChat @@ -21,7 +20,9 @@ import java.util.concurrent.Executors class PublicChatManager(private val context: Context) { private var chats = mutableMapOf() + private var v2Chats = mutableMapOf() private val pollers = mutableMapOf() + private val v2Pollers = mutableMapOf() private val observers = mutableMapOf() private var isPolling = false private val executorService = Executors.newScheduledThreadPool(4) @@ -53,6 +54,12 @@ class PublicChatManager(private val context: Context) { listenToThreadDeletion(threadId) if (!pollers.containsKey(threadId)) { pollers[threadId] = poller } } + for ((threadId, chat) in v2Chats) { + val poller = v2Pollers[threadId] ?: OpenGroupV2Poller(chat, executorService) + poller.startIfNeeded() + listenToThreadDeletion(threadId) + if (!v2Pollers.containsKey(threadId)) { v2Pollers[threadId] = poller } + } isPolling = true } @@ -73,6 +80,15 @@ class PublicChatManager(private val context: Context) { return addChat(server, channel, channelInfo) } + @WorkerThread + fun addChat(server: String, room: String): OpenGroupV2 { + // Ensure the auth token is acquired. + OpenGroupAPIV2.getAuthToken(server).get() + + val channelInfo = OpenGroupAPIV2.getChannelInfo(channel, server).get() + return addChat(server, room, channelInfo) + } + @WorkerThread public fun addChat(server: String, channel: Long, info: OpenGroupInfo): OpenGroup { val chat = PublicChat(channel, server, info.displayName, true) @@ -99,6 +115,20 @@ class PublicChatManager(private val context: Context) { return OpenGroup.from(chat) } + @WorkerThread + fun addChat(server: String, room: String, info: OpenGroupInfo): OpenGroupV2 { + val chat = OpenGroupV2(server, room, info.displayName, ) + var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) + var profilePicture: Bitmap? = null + if (threadID < 0) { + if (info.profilePictureURL.isNotEmpty()) { + val profilePictureAsByteArray = OpenGroupAPIV2.downloadOpenGroupProfilePicture(server, info.profilePictureURL) + profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray) + } + val result = GroupManager.createOpenGroup() + } + } + public fun removeChat(server: String, channel: Long) { val threadDB = DatabaseFactory.getThreadDatabase(context) val groupId = PublicChat.getId(channel, server) @@ -112,11 +142,13 @@ class PublicChatManager(private val context: Context) { private fun refreshChatsAndPollers() { val storage = MessagingConfiguration.shared.storage val chatsInDB = storage.getAllOpenGroups() + val v2ChatsInDB = storage.getAllV2OpenGroups() val removedChatThreadIds = chats.keys.filter { !chatsInDB.keys.contains(it) } removedChatThreadIds.forEach { pollers.remove(it)?.stop() } // Only append to chats if we have a thread for the chat chats = chatsInDB.filter { GroupManager.getOpenGroupThreadID(it.value.id, context) > -1 }.toMutableMap() + v2Chats = v2ChatsInDB.filter { GroupManager.getOpenGroupThreadID(it.value.id, context) > -1 }.toMutableMap() } private fun listenToThreadDeletion(threadID: Long) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt index f59bc7b71..d00f56851 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt @@ -1,10 +1,10 @@ package org.thoughtcrime.securesms.loki.utilities import android.content.Context +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.mentions.MentionsManager import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.MessageRecord -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.service.loki.utilities.mentions.MentionsManager object MentionManagerUtilities { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt index ddda015d7..63445c4ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt @@ -7,10 +7,10 @@ import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.ListView +import org.session.libsession.utilities.mentions.Mention import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.mms.GlideRequests -import org.session.libsignal.service.loki.utilities.mentions.Mention class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) { private var mentionCandidates = listOf() diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt index f85e5e986..9c6933390 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt @@ -9,7 +9,7 @@ import android.widget.LinearLayout import kotlinx.android.synthetic.main.view_mention_candidate.view.* import network.loki.messenger.R import org.session.libsession.messaging.opengroups.OpenGroupAPI -import org.session.libsignal.service.loki.utilities.mentions.Mention +import org.session.libsession.utilities.mentions.Mention import org.thoughtcrime.securesms.mms.GlideRequests class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt index a09284163..c6c69d158 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -14,7 +14,7 @@ import org.session.libsession.messaging.avatars.ProfileContactPhoto import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.service.loki.utilities.mentions.MentionsManager +import org.session.libsession.utilities.mentions.MentionsManager import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator import org.thoughtcrime.securesms.mms.GlideRequests 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 95e5eb680..66def868f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -71,8 +71,8 @@ interface StorageProtocol { fun setOpenGroupPublicKey(server: String, newValue: String) // Open Group User Info - fun setOpenGroupDisplayName(publicKey: String, channel: Long, server: String, displayName: String) - fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String? + fun setOpenGroupDisplayName(publicKey: String, room: String, server: String, displayName: String) + fun getOpenGroupDisplayName(publicKey: String, room: String, server: String): String? // Open Group Metadata @@ -180,4 +180,7 @@ interface StorageProtocol { fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String) fun getOpenGroupProfilePictureURL(group: Long, server: String): String? + fun setOpenGroupDisplayName(publicKey: String, channel: Long, server: String, displayName: String) + fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String? + } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt index a0ee35bc9..b0fde9913 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt @@ -1,32 +1,153 @@ package org.session.libsession.messaging.opengroups import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.bind +import okhttp3.HttpUrl +import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error -import org.session.libsession.messaging.utilities.DotNetAPI +import org.session.libsignal.service.loki.api.utilities.HTTP import java.util.* -object OpenGroupAPIV2: DotNetAPI() { - - enum class Error { - GENERIC, - PARSING_FAILED, - DECRYPTION_FAILED, - SIGNING_FAILED, - INVALID_URL, - NO_PUBLIC_KEY - } +object OpenGroupAPIV2 { private val moderators: HashMap>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs) const val DEFAULT_SERVER = "https://sessionopengroup.com" const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b" - fun getMessages(room: String, server: String): Promise, Exception> { + sealed class Error : Exception() { + object GENERIC : Error() + object PARSING_FAILED : Error() + object DECRYPTION_FAILED : Error() + object SIGNING_FAILED : Error() + object INVALID_URL : Error() + object NO_PUBLIC_KEY : Error() + } + + data class Info( + val id: String, + val name: String, + val imageID: String + ) + + data class Request( + val verb: HTTP.Verb, + val room: String?, + val server: String, + val endpoint: String, + val queryParameters: Map, + val parameters: Any, + val headers: Map, + val isAuthRequired: Boolean, + // Always `true` under normal circumstances. You might want to disable + // this when running over Lokinet. + val useOnionRouting: Boolean + ) + + private fun send(request: Request): Promise { + val parsed = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.INVALID_URL) + val urlBuilder = HttpUrl.Builder() + .scheme(parsed.scheme()) + .host(parsed.host()) + .addPathSegment(request.endpoint) + + for ((key, value) in request.queryParameters) { + urlBuilder.addQueryParameter(key, value) + } + + fun execute(token: String?): Promise, Exception> { + } + return if (request.isAuthRequired) { + getAuthToken(request.room!!, request.server).bind(::execute) + } else { + execute(null) + } + } + + fun getAuthToken(room: String, server: String): Promise { + val storage = MessagingConfiguration.shared.storage + return storage.getAuthToken(room, server)?.let { + Promise.of(it) + } ?: run { + requestNewAuthToken(room, server) + .bind { claimAuthToken(it, room, server) } + .success { authToken -> + storage.setAuthToken(room, server, authToken) + } + } + } + + fun requestNewAuthToken(room: String, server: String): Promise { + val (publicKey, _) = MessagingConfiguration.shared.storage.getUserKeyPair() + ?: return Promise.ofFail(Error.GENERIC) + val queryParameters = mutableMapOf("public_key" to publicKey) } -} + fun claimAuthToken(authToken: String, room: String, server: String): Promise { + TODO("implement") + } -data class Info(val id: String, val name: String, val imageId: String?) + fun deleteAuthToken(room: String, server: String): Promise { + TODO("implement") + } + + fun upload(file: ByteArray, room: String, server: String): Promise { + TODO("implement") + } + + fun download(file: Long, room: String, server: String): Promise { + TODO("implement") + } + + fun send(message: OpenGroupMessageV2, room: String, server: String): Promise { + TODO("implement") + } + + fun getMessages(room: String, server: String): Promise, Exception> { + TODO("implement") + } + + fun deleteMessage(serverID: Long, room: String, server: String): Promise { + TODO("implement") + } + + fun getDeletedMessages(room: String, server: String): Promise, Exception> { + TODO("implement") + } + + fun getModerators(room: String, server: String): Promise, Exception> { + TODO("implement") + } + + fun ban(publicKey: String, room: String, server: String): Promise { + TODO("implement") + } + + fun unban(publicKey: String, room: String, server: String): Promise { + TODO("implement") + } + + fun isUserModerator(publicKey: String, room: String, server: String): Promise { + TODO("implement") + } + + fun getDefaultRoomsIfNeeded() { + TODO("implement") + } + + fun getInfo(room: String, server: String): Promise { + TODO("implement") + } + + fun getAllRooms(server: String): Promise, Exception> { + TODO("implement") + } + + fun getMemberCount(room: String, server: String): Promise { + TODO("implement") + } + +} fun Error.errorDescription() = when (this) { Error.GENERIC -> "An error occurred." diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2Message.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessageV2.kt similarity index 69% rename from libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2Message.kt rename to libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessageV2.kt index a40bd53bf..80d72228d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupV2Message.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessageV2.kt @@ -5,7 +5,7 @@ import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.logging.Log import org.whispersystems.curve25519.Curve25519 -data class OpenGroupV2Message( +data class OpenGroupMessageV2( val serverID: Long?, val sender: String?, val sentTimestamp: Long, @@ -20,7 +20,7 @@ data class OpenGroupV2Message( private val curve = Curve25519.getInstance(Curve25519.BEST) } - fun sign(): OpenGroupV2Message? { + fun sign(): OpenGroupMessageV2? { if (base64EncodedData.isEmpty()) return null val (publicKey, privateKey) = MessagingConfiguration.shared.storage.getUserKeyPair() ?: return null @@ -41,11 +41,21 @@ data class OpenGroupV2Message( serverID?.let { jsonMap["server_id"] = serverID } sender?.let { jsonMap["public_key"] = sender } base64EncodedSignature?.let { jsonMap["signature"] = base64EncodedSignature } + return jsonMap } - fun fromJSON(json: Map): OpenGroupV2Message? { - if (!json.containsKey("data") || !json.containsKey("timestamp")) return null - + fun fromJSON(json: Map): OpenGroupMessageV2? { + val base64EncodedData = json["data"] as? String ?: return null + val sentTimestamp = json["timestamp"] as? Long ?: return null + val serverID = json["server_id"] as? Long + val sender = json["public_key"] as? String + val base64EncodedSignature = json["signature"] as? String + return OpenGroupMessageV2(serverID = serverID, + sender = sender, + sentTimestamp = sentTimestamp, + base64EncodedData = base64EncodedData, + base64EncodedSignature = base64EncodedSignature + ) } diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/Mention.kt b/libsession/src/main/java/org/session/libsession/utilities/mentions/Mention.kt similarity index 52% rename from libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/Mention.kt rename to libsession/src/main/java/org/session/libsession/utilities/mentions/Mention.kt index 8d97c4526..7900439d5 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/Mention.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/mentions/Mention.kt @@ -1,3 +1,3 @@ -package org.session.libsignal.service.loki.utilities.mentions +package org.session.libsession.utilities.mentions data class Mention(val publicKey: String, val displayName: String) diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/MentionsManager.kt b/libsession/src/main/java/org/session/libsession/utilities/mentions/MentionsManager.kt similarity index 80% rename from libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/MentionsManager.kt rename to libsession/src/main/java/org/session/libsession/utilities/mentions/MentionsManager.kt index 2edf6f677..e784ff49a 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/MentionsManager.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/mentions/MentionsManager.kt @@ -1,5 +1,6 @@ -package org.session.libsignal.service.loki.utilities.mentions +package org.session.libsession.utilities.mentions +import org.session.libsession.messaging.MessagingConfiguration import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol @@ -9,9 +10,9 @@ class MentionsManager(private val userPublicKey: String, private val threadDatab companion object { - public lateinit var shared: MentionsManager + lateinit var shared: MentionsManager - public fun configureIfNeeded(userPublicKey: String, threadDatabase: LokiThreadDatabaseProtocol, userDatabase: LokiUserDatabaseProtocol) { + fun configureIfNeeded(userPublicKey: String, threadDatabase: LokiThreadDatabaseProtocol, userDatabase: LokiUserDatabaseProtocol) { if (::shared.isInitialized) { return; } shared = MentionsManager(userPublicKey, threadDatabase, userDatabase) } @@ -30,11 +31,15 @@ class MentionsManager(private val userPublicKey: String, private val threadDatab // Prepare val cache = userPublicKeyCache[threadID] ?: return listOf() // Gather candidates + val storage = MessagingConfiguration.shared.storage val publicChat = threadDatabase.getPublicChat(threadID) + val openGroupV2 = storage.getV2OpenGroup(threadID.toString()) var candidates: List = cache.mapNotNull { publicKey -> val displayName: String? if (publicChat != null) { displayName = userDatabase.getServerDisplayName(publicChat.id, publicKey) + } else if (openGroupV2 != null) { + displayName = userDatabase.getServerDisplayName(openGroupV2.id, publicKey) } else { displayName = userDatabase.getDisplayName(publicKey) } From 2b7cf7c1b446d28d70858e98769395c740ee14a5 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 16 Apr 2021 15:54:39 +1000 Subject: [PATCH 26/99] added Kind sealed class in UpdateMessageData + minor fixes --- .../loki/utilities/GroupDescription.kt | 104 ------------------ app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../utilities/UpdateMessageBuilder.kt | 7 +- .../messaging/utilities/UpdateMessageData.kt | 35 +++--- libsession/src/main/res/values/strings.xml | 1 + 6 files changed, 29 insertions(+), 120 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt 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 3844b9fbf..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/GroupDescription.kt +++ /dev/null @@ -1,104 +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 - -//TODO that class isn't used anymore -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/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 728266183..f156dad77 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -427,6 +427,7 @@ 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é diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4ed3697e6..c7279e90c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -502,6 +502,7 @@ 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/utilities/UpdateMessageBuilder.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt index 85be97837..d6f446e36 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -8,13 +8,14 @@ import org.session.libsignal.service.api.messages.SignalServiceGroup object UpdateMessageBuilder { - fun buildGroupUpdateMessage(context: Context, updateData: UpdateMessageData, sender: String? = null, isOutgoing: Boolean = false): String { - val updateType = updateData.type + 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 { sender } + } else { context.getString(R.string.MessageRecord_you) } when (updateType) { SignalServiceGroup.Type.CREATION -> { 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 index a21e403b7..5ca4bbbf9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageData.kt @@ -1,5 +1,7 @@ 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 @@ -9,14 +11,21 @@ import java.util.* // class used to save update messages details class UpdateMessageData () { - var type: SignalServiceGroup.Type = SignalServiceGroup.Type.UNKNOWN - var groupName: String? = null - var updatedMembers: Collection = Collections.emptyList() + var kind: Kind? = null - constructor(type: SignalServiceGroup.Type, groupName: String?, updatedMembers: Collection): this() { - this.type = type - this.groupName = groupName - this.updatedMembers = updatedMembers + //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 { @@ -24,19 +33,19 @@ class UpdateMessageData () { fun buildGroupUpdate(type: SignalServiceGroup.Type, name: String, members: Collection): UpdateMessageData { return when(type) { - SignalServiceGroup.Type.NAME_CHANGE -> UpdateMessageData(type, name, Collections.emptyList()) - SignalServiceGroup.Type.MEMBER_ADDED -> UpdateMessageData(type,null, members) - SignalServiceGroup.Type.MEMBER_REMOVED -> UpdateMessageData(type,null, members) - else -> UpdateMessageData(type,null, Collections.emptyList()) + 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 { + return try { JsonUtil.fromJson(json, UpdateMessageData::class.java) } catch (e: JsonParseException) { Log.e(TAG, "${e.message}") - UpdateMessageData(SignalServiceGroup.Type.UNKNOWN, null, Collections.emptyList()) + UpdateMessageData(Kind.GroupUpdate(SignalServiceGroup.Type.UNKNOWN, null, Collections.emptyList())) } } } diff --git a/libsession/src/main/res/values/strings.xml b/libsession/src/main/res/values/strings.xml index 90cbb9286..116b10601 100644 --- a/libsession/src/main/res/values/strings.xml +++ b/libsession/src/main/res/values/strings.xml @@ -496,6 +496,7 @@ 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 From aea23a6fc1f06a0a5ff07b690d6cef03a8eb1c9d Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 19 Apr 2021 10:16:38 +1000 Subject: [PATCH 27/99] feat: finishing up OpenGroupAPIV2.kt calls --- .../securesms/database/Storage.kt | 10 +- .../loki/database/LokiAPIDatabase.kt | 10 +- .../libsession/messaging/StorageProtocol.kt | 1 + .../messaging/opengroups/OpenGroupAPIV2.kt | 253 +++++++++++++++--- .../opengroups/OpenGroupMessageV2.kt | 31 ++- .../session/libsession/utilities/AESGCM.kt | 19 +- .../loki/database/LokiAPIDatabaseProtocol.kt | 2 +- 7 files changed, 264 insertions(+), 62 deletions(-) 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 445fdb36b..6e4a26cde 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -314,15 +314,19 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun getLastDeletionServerId(room: String, server: String): Long? { - TODO("Not yet implemented") + return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(room, server) } override fun setLastDeletionServerId(room: String, server: String, newValue: Long) { - TODO("Not yet implemented") + DatabaseFactory.getLokiAPIDatabase(context).setLastDeletionServerID(room, server, newValue) } override fun removeLastDeletionServerId(room: String, server: String) { - TODO("Not yet implemented") + DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(room, server) + } + + override fun setUserCount(room: String, server: String, newValue: Long) { + DatabaseFactory.getLokiAPIDatabase(context).setUserCount(room, server, newValue) } override fun getLastDeletionServerID(group: Long, server: String): Long? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index e4ea2d009..beeaaa8a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -316,7 +316,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( fun removeLastMessageServerID(room: String, server:String) { val database = databaseHelper.writableDatabase - val index = "$server.$channel" + val index = "$server.$room" database.delete(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) } @@ -350,6 +350,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index)) } + fun removeLastDeletionServerID(room: String, server: String) { + val database = databaseHelper.writableDatabase + val index = "$server.$room" + database.delete(lastDeletionServerIDTable, "$lastDeletionServerID = ?", wrap(index)) + } + fun removeLastDeletionServerID(group: Long, server: String) { val database = databaseHelper.writableDatabase val index = "$server.$group" @@ -379,7 +385,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index)) } - override fun setUserCount(room: String, server: String, newValue: Int) { + override fun setUserCount(room: String, server: String, newValue: Long) { val database = databaseHelper.writableDatabase val index = "$server.$room" val row = wrap(mapOf( publicChatID to index, userCount to newValue.toString() )) 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 66def868f..c15e62ad3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -78,6 +78,7 @@ interface StorageProtocol { fun updateTitle(groupID: String, newValue: String) fun updateProfilePicture(groupID: String, newValue: ByteArray) + fun setUserCount(room: String, server: String, newValue: Long) // Last Message Server ID fun getLastMessageServerId(room: String, server: String): Long? diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt index b0fde9913..51a354af8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPIV2.kt @@ -1,19 +1,36 @@ package org.session.libsession.messaging.opengroups +import nl.komponents.kovenant.Kovenant import nl.komponents.kovenant.Promise import nl.komponents.kovenant.functional.bind +import nl.komponents.kovenant.functional.map import okhttp3.HttpUrl +import okhttp3.MediaType +import okhttp3.RequestBody import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error +import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.utilities.AESGCM import org.session.libsignal.service.loki.api.utilities.HTTP +import org.session.libsignal.service.loki.api.utilities.HTTP.Verb.* +import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded +import org.session.libsignal.service.loki.utilities.toHexString +import org.session.libsignal.utilities.Base64.* +import org.session.libsignal.utilities.JsonUtil +import org.session.libsignal.utilities.createContext +import org.session.libsignal.utilities.logging.Log +import org.whispersystems.curve25519.Curve25519 import java.util.* object OpenGroupAPIV2 { - private val moderators: HashMap>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs) + private val moderators: HashMap> = hashMapOf() // Server URL to (channel ID to set of moderator IDs) const val DEFAULT_SERVER = "https://sessionopengroup.com" const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b" + private val sharedContext = Kovenant.createContext() + private val curve = Curve25519.getInstance(Curve25519.BEST) + sealed class Error : Exception() { object GENERIC : Error() object PARSING_FAILED : Error() @@ -26,7 +43,7 @@ object OpenGroupAPIV2 { data class Info( val id: String, val name: String, - val imageID: String + val imageID: String? ) data class Request( @@ -34,30 +51,67 @@ object OpenGroupAPIV2 { val room: String?, val server: String, val endpoint: String, - val queryParameters: Map, - val parameters: Any, - val headers: Map, - val isAuthRequired: Boolean, + val queryParameters: Map = mapOf(), + val parameters: Any? = null, + val headers: Map = mapOf(), + val isAuthRequired: Boolean = true, // Always `true` under normal circumstances. You might want to disable // this when running over Lokinet. - val useOnionRouting: Boolean + val useOnionRouting: Boolean = true ) - private fun send(request: Request): Promise { + private fun createBody(parameters: Any): RequestBody { + val parametersAsJSON = JsonUtil.toJson(parameters) + return RequestBody.create(MediaType.get("application/json"), parametersAsJSON) + } + + private fun send(request: Request): Promise, Exception> { val parsed = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.INVALID_URL) val urlBuilder = HttpUrl.Builder() .scheme(parsed.scheme()) .host(parsed.host()) .addPathSegment(request.endpoint) - for ((key, value) in request.queryParameters) { - urlBuilder.addQueryParameter(key, value) + if (request.verb == GET) { + for ((key, value) in request.queryParameters) { + urlBuilder.addQueryParameter(key, value) + } } fun execute(token: String?): Promise, Exception> { + val requestBuilder = okhttp3.Request.Builder() + .url(urlBuilder.build()) + if (request.isAuthRequired) { + if (token.isNullOrEmpty()) throw IllegalStateException("No auth token for request") + requestBuilder.addHeader("Authorization", token) + } + when (request.verb) { + GET -> requestBuilder.get() + PUT -> requestBuilder.put(createBody(request.parameters!!)) + POST -> requestBuilder.post(createBody(request.parameters!!)) + DELETE -> requestBuilder.delete(createBody(request.parameters!!)) + } + + if (!request.room.isNullOrEmpty()) { + requestBuilder.header("Room", request.room) + } + + if (request.useOnionRouting) { + val publicKey = MessagingConfiguration.shared.storage.getOpenGroupPublicKey(request.server) + ?: return Promise.ofFail(Error.NO_PUBLIC_KEY) + return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey) + .fail { e -> + if (e is OnionRequestAPI.HTTPRequestFailedAtDestinationException + && e.statusCode == 401) { + MessagingConfiguration.shared.storage.removeAuthToken(request.server) + } + } + } else { + return Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests.")) + } } return if (request.isAuthRequired) { - getAuthToken(request.room!!, request.server).bind(::execute) + getAuthToken(request.room!!, request.server).bind(sharedContext) { execute(it) } } else { execute(null) } @@ -69,7 +123,7 @@ object OpenGroupAPIV2 { Promise.of(it) } ?: run { requestNewAuthToken(room, server) - .bind { claimAuthToken(it, room, server) } + .bind(sharedContext) { claimAuthToken(it, room, server) } .success { authToken -> storage.setAuthToken(room, server, authToken) } @@ -77,75 +131,204 @@ object OpenGroupAPIV2 { } fun requestNewAuthToken(room: String, server: String): Promise { - val (publicKey, _) = MessagingConfiguration.shared.storage.getUserKeyPair() + val (publicKey, privateKey) = MessagingConfiguration.shared.storage.getUserKeyPair() ?: return Promise.ofFail(Error.GENERIC) val queryParameters = mutableMapOf("public_key" to publicKey) - + val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null) + return send(request).map(sharedContext) { json -> + val challenge = json["challenge"] as? Map<*,*> ?: throw Error.PARSING_FAILED + val base64EncodedCiphertext = challenge["ciphertext"] as? String ?: throw Error.PARSING_FAILED + val base64EncodedEphemeralPublicKey = challenge["ephemeral_public_key"] as? String ?: throw Error.PARSING_FAILED + val ciphertext = decode(base64EncodedCiphertext) + val ephemeralPublicKey = decode(base64EncodedEphemeralPublicKey) + val symmetricKey = AESGCM.generateSymmetricKey(ephemeralPublicKey, privateKey) + val tokenAsData = try { + AESGCM.decrypt(ciphertext, symmetricKey) + } catch (e: Exception) { + throw Error.DECRYPTION_FAILED + } + tokenAsData.toHexString() + } } fun claimAuthToken(authToken: String, room: String, server: String): Promise { - TODO("implement") + val parameters = mapOf("public_key" to MessagingConfiguration.shared.storage.getUserPublicKey()!!) + val headers = mapOf("Authorization" to authToken) + val request = Request(verb = POST, room = room, server = server, endpoint = "claim_auth_token", + parameters = parameters, headers = headers, isAuthRequired = false) + return send(request).map(sharedContext) { authToken } } - fun deleteAuthToken(room: String, server: String): Promise { - TODO("implement") + fun deleteAuthToken(room: String, server: String): Promise { + val request = Request(verb = DELETE, room = room, server = server, endpoint = "auth_token") + return send(request).map(sharedContext) { + MessagingConfiguration.shared.storage.removeAuthToken(room, server) + } } + // region Sending fun upload(file: ByteArray, room: String, server: String): Promise { - TODO("implement") + val base64EncodedFile = encodeBytes(file) + val parameters = mapOf("file" to base64EncodedFile) + val request = Request(verb = POST, room = room, server = server, endpoint = "files", parameters = parameters) + return send(request).map(sharedContext) { json -> + json["result"] as? Long ?: throw Error.PARSING_FAILED + } } fun download(file: Long, room: String, server: String): Promise { - TODO("implement") + val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file") + return send(request).map(sharedContext) { json -> + val base64EncodedFile = json["result"] as? String ?: throw Error.PARSING_FAILED + decode(base64EncodedFile) ?: throw Error.PARSING_FAILED + } } fun send(message: OpenGroupMessageV2, room: String, server: String): Promise { - TODO("implement") + val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED) + val json = signedMessage.toJSON() + val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = json) + return send(request).map(sharedContext) { + @Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map ?: throw Error.PARSING_FAILED + OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED + } } + // endregion + // region Messages fun getMessages(room: String, server: String): Promise, Exception> { - TODO("implement") - } + val storage = MessagingConfiguration.shared.storage + val queryParameters = mutableMapOf() + storage.getLastMessageServerId(room,server)?.let { lastId -> + queryParameters += "from_server_id" to lastId.toString() + } + val request = Request(verb = GET, room = room, server = server, endpoint = "messages", queryParameters = queryParameters) + return send(request).map(sharedContext) { jsonList -> + @Suppress("UNCHECKED_CAST") val rawMessages = jsonList["messages"] as? List> ?: throw Error.PARSING_FAILED + val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0 + var currentMax = lastMessageServerId + val messages = rawMessages.mapNotNull { json -> + val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null + if (message.serverID == null || message.sender.isNullOrEmpty()) return@mapNotNull null + val sender = message.sender + val data = decode(message.base64EncodedData) + val signature = decode(message.base64EncodedSignature) + val publicKey = sender.removing05PrefixIfNeeded().encodeToByteArray() + val isValid = curve.verifySignature(publicKey, data, signature) + if (!isValid) { + Log.d("Loki", "Ignoring message with invalid signature") + return@mapNotNull null + } + if (message.serverID > lastMessageServerId) { + currentMax = message.serverID + } + message + } + storage.setLastMessageServerId(room,server,currentMax) + messages + } + } + // endregion + + // region Message Deletion fun deleteMessage(serverID: Long, room: String, server: String): Promise { - TODO("implement") + val request = Request(verb = DELETE, room = room, server = server, endpoint = "message/$serverID") + return send(request).map(sharedContext) { + Log.d("Loki", "Deleted server message") + } } fun getDeletedMessages(room: String, server: String): Promise, Exception> { - TODO("implement") + val storage = MessagingConfiguration.shared.storage + val queryParameters = mutableMapOf() + storage.getLastDeletionServerId(room, server)?.let { last -> + queryParameters["from_server_id"] = last.toString() + } + val request = Request(verb = GET, room = room, server = server, endpoint = "deleted_messages", queryParameters = queryParameters) + return send(request).map(sharedContext) { json -> + @Suppress("UNCHECKED_CAST") val serverIDs = json["ids"] as? List ?: throw Error.PARSING_FAILED + val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0 + val serverID = serverIDs.maxOrNull() ?: 0 + if (serverID > lastMessageServerId) { + storage.setLastDeletionServerId(room, server, serverID) + } + serverIDs + } } + // endregion + // region Moderation fun getModerators(room: String, server: String): Promise, Exception> { - TODO("implement") + val request = Request(verb = GET, room = room, server = server, endpoint = "moderators") + return send(request).map(sharedContext) { json -> + @Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List ?: throw Error.PARSING_FAILED + val id = "$server.$room" + moderators[id] = moderatorsJson.toMutableSet() + moderatorsJson + } } fun ban(publicKey: String, room: String, server: String): Promise { - TODO("implement") + val parameters = mapOf("public_key" to publicKey) + val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters) + return send(request).map(sharedContext) { + Log.d("Loki", "Banned user $publicKey from $server.$room") + } } fun unban(publicKey: String, room: String, server: String): Promise { - TODO("implement") + val request = Request(verb = DELETE, room = room, server = server, endpoint = "block_list/$publicKey") + return send(request).map(sharedContext) { + Log.d("Loki", "Unbanned user $publicKey from $server.$room") + } } - fun isUserModerator(publicKey: String, room: String, server: String): Promise { - TODO("implement") - } + fun isUserModerator(publicKey: String, room: String, server: String): Boolean = moderators["$server.$room"]?.contains(publicKey) ?: false + // endregion - fun getDefaultRoomsIfNeeded() { - TODO("implement") + // region General + fun getDefaultRoomsIfNeeded(): Promise, Exception> { + val storage = MessagingConfiguration.shared.storage + storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY) + return getAllRooms(DEFAULT_SERVER) } fun getInfo(room: String, server: String): Promise { - TODO("implement") + val request = Request(verb = GET, room = room, server = server, endpoint = "rooms/$room", isAuthRequired = false) + return send(request).map(sharedContext) { json -> + val rawRoom = json["room"] as? Map<*,*> ?: throw Error.PARSING_FAILED + val id = rawRoom["id"] as? String ?: throw Error.PARSING_FAILED + val name = rawRoom["name"] as? String ?: throw Error.PARSING_FAILED + val imageID = rawRoom["image_id"] as? String + Info(id = id, name = name, imageID = imageID) + } } fun getAllRooms(server: String): Promise, Exception> { - TODO("implement") + val request = Request(verb = GET, room = null, server = server, endpoint = "rooms", isAuthRequired = false) + return send(request).map(sharedContext) { json -> + val rawRooms = json["rooms"] as? Map<*,*> ?: throw Error.PARSING_FAILED + rawRooms.mapNotNull { + val roomJson = it as? Map<*, *> ?: return@mapNotNull null + val id = roomJson["id"] as? String ?: return@mapNotNull null + val name = roomJson["name"] as? String ?: return@mapNotNull null + val imageId = roomJson["image_id"] as? String + Info(id, name, imageId) + } + } } fun getMemberCount(room: String, server: String): Promise { - TODO("implement") + val request = Request(verb = GET, room = room, server = server, endpoint = "member_count") + return send(request).map(sharedContext) { json -> + val memberCount = json["member_count"] as? Long ?: throw Error.PARSING_FAILED + val storage = MessagingConfiguration.shared.storage + storage.setUserCount(room, server, memberCount) + memberCount + } } + // endregion } diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessageV2.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessageV2.kt index 80d72228d..fa3195a90 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessageV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessageV2.kt @@ -18,6 +18,21 @@ data class OpenGroupMessageV2( companion object { private val curve = Curve25519.getInstance(Curve25519.BEST) + + fun fromJSON(json: Map): OpenGroupMessageV2? { + val base64EncodedData = json["data"] as? String ?: return null + val sentTimestamp = json["timestamp"] as? Long ?: return null + val serverID = json["server_id"] as? Long + val sender = json["public_key"] as? String + val base64EncodedSignature = json["signature"] as? String + return OpenGroupMessageV2(serverID = serverID, + sender = sender, + sentTimestamp = sentTimestamp, + base64EncodedData = base64EncodedData, + base64EncodedSignature = base64EncodedSignature + ) + } + } fun sign(): OpenGroupMessageV2? { @@ -43,20 +58,4 @@ data class OpenGroupMessageV2( base64EncodedSignature?.let { jsonMap["signature"] = base64EncodedSignature } return jsonMap } - - fun fromJSON(json: Map): OpenGroupMessageV2? { - val base64EncodedData = json["data"] as? String ?: return null - val sentTimestamp = json["timestamp"] as? Long ?: return null - val serverID = json["server_id"] as? Long - val sender = json["public_key"] as? String - val base64EncodedSignature = json["signature"] as? String - return OpenGroupMessageV2(serverID = serverID, - sender = sender, - sentTimestamp = sentTimestamp, - base64EncodedData = base64EncodedData, - base64EncodedSignature = base64EncodedSignature - ) - } - - } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/utilities/AESGCM.kt b/libsession/src/main/java/org/session/libsession/utilities/AESGCM.kt index fcbbf548d..f5a170d8b 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/AESGCM.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/AESGCM.kt @@ -1,14 +1,16 @@ package org.session.libsession.utilities -import org.whispersystems.curve25519.Curve25519 +import androidx.annotation.WorkerThread import org.session.libsignal.libsignal.util.ByteUtil import org.session.libsignal.service.internal.util.Util import org.session.libsignal.utilities.Hex +import org.whispersystems.curve25519.Curve25519 import javax.crypto.Cipher import javax.crypto.Mac import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.SecretKeySpec +@WorkerThread internal object AESGCM { internal data class EncryptionResult( @@ -31,6 +33,16 @@ internal object AESGCM { return cipher.doFinal(ciphertext) } + /** + * Sync. Don't call from the main thread. + */ + internal fun generateSymmetricKey(x25519PublicKey: ByteArray, x25519PrivateKey: ByteArray): ByteArray { + val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(x25519PublicKey, x25519PrivateKey) + val mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256")) + return mac.doFinal(ephemeralSharedSecret) + } + /** * Sync. Don't call from the main thread. */ @@ -47,10 +59,7 @@ internal object AESGCM { internal fun encrypt(plaintext: ByteArray, hexEncodedX25519PublicKey: String): EncryptionResult { val x25519PublicKey = Hex.fromStringCondensed(hexEncodedX25519PublicKey) val ephemeralKeyPair = Curve25519.getInstance(Curve25519.BEST).generateKeyPair() - val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(x25519PublicKey, ephemeralKeyPair.privateKey) - val mac = Mac.getInstance("HmacSHA256") - mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256")) - val symmetricKey = mac.doFinal(ephemeralSharedSecret) + val symmetricKey = generateSymmetricKey(x25519PublicKey, ephemeralKeyPair.privateKey) val ciphertext = encrypt(plaintext, symmetricKey) return EncryptionResult(ciphertext, symmetricKey, ephemeralKeyPair.publicKey) } diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt index 2fdf5c9db..8107c1844 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt @@ -28,7 +28,7 @@ interface LokiAPIDatabaseProtocol { fun setLastMessageServerID(room: String, server: String, newValue: Long) fun getLastDeletionServerID(room: String, server: String): Long? fun setLastDeletionServerID(room: String, server: String, newValue: Long) - fun setUserCount(room: String, server: String, newValue: Int) + fun setUserCount(room: String, server: String, newValue: Long) fun getSessionRequestSentTimestamp(publicKey: String): Long? fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long) fun getSessionRequestProcessedTimestamp(publicKey: String): Long? From 3c210ca437e2e4a15ba70f33f035eb6e544e89ab Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 19 Apr 2021 10:46:19 +1000 Subject: [PATCH 28/99] optimization on contacts selection --- .../securesms/loki/activities/SelectContactsAdapter.kt | 8 ++++---- .../org/thoughtcrime/securesms/loki/views/UserView.kt | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt index 135762f66..c81c0e38d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt @@ -26,7 +26,7 @@ class SelectContactsAdapter(private val context: Context, private val glide: Gli override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { val member = members[position] - viewHolder.view.setOnClickListener { onMemberClick(member) } + viewHolder.view.setOnClickListener { onMemberClick(viewHolder, member) } val isSelected = selectedMembers.contains(member) viewHolder.view.bind(Recipient.from( context, @@ -36,13 +36,13 @@ class SelectContactsAdapter(private val context: Context, private val glide: Gli isSelected) } - private fun onMemberClick(member: String) { + private fun onMemberClick(viewHolder: ViewHolder, member: String) { if (selectedMembers.contains(member)) { selectedMembers.remove(member) + viewHolder.view.toggleCheckbox() } else { selectedMembers.add(member) + viewHolder.view.toggleCheckbox(true) } - val index = members.indexOf(member) - notifyItemChanged(index) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt index 4c6098a71..ae9f001f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt @@ -82,6 +82,11 @@ class UserView : LinearLayout { } } + fun toggleCheckbox(isSelected: Boolean = false) { + actionIndicatorImageView.visibility = View.VISIBLE + actionIndicatorImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle) + } + fun unbind() { } From cba4e65b9e15022c2b164883e54962b946def490 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 19 Apr 2021 13:35:09 +1000 Subject: [PATCH 29/99] update with payload logic --- .../loki/activities/SelectContactsAdapter.kt | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt index c81c0e38d..4fb31d9fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt @@ -26,7 +26,7 @@ class SelectContactsAdapter(private val context: Context, private val glide: Gli override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { val member = members[position] - viewHolder.view.setOnClickListener { onMemberClick(viewHolder, member) } + viewHolder.view.setOnClickListener { onMemberClick(member) } val isSelected = selectedMembers.contains(member) viewHolder.view.bind(Recipient.from( context, @@ -36,13 +36,39 @@ class SelectContactsAdapter(private val context: Context, private val glide: Gli isSelected) } - private fun onMemberClick(viewHolder: ViewHolder, member: String) { - if (selectedMembers.contains(member)) { - selectedMembers.remove(member) - viewHolder.view.toggleCheckbox() + override fun onBindViewHolder(viewHolder: ViewHolder, + position: Int, + payloads: MutableList) { + if (payloads.isNotEmpty()) { + // Because these updates can be batched, + // there can be multiple payloads for a single bind + when (payloads[0]) { + Payload.MEMBER_CLICKED -> { + val member = members[position] + val isSelected = selectedMembers.contains(member) + viewHolder.view.toggleCheckbox(isSelected) + } + } } else { - selectedMembers.add(member) - viewHolder.view.toggleCheckbox(true) + // When payload list is empty, + // or we don't have logic to handle a given type, + // default to full bind: + this.onBindViewHolder(viewHolder, position) } } + + private fun onMemberClick(member: String) { + if (selectedMembers.contains(member)) { + selectedMembers.remove(member) + } else { + selectedMembers.add(member) + } + val index = members.indexOf(member) + notifyItemChanged(index, Payload.MEMBER_CLICKED) + } + + // define below the different events used to notify the adapter + enum class Payload { + MEMBER_CLICKED + } } \ No newline at end of file From b92f2e79048b10f72d7a14895507fb554e77263f Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 19 Apr 2021 13:36:42 +1000 Subject: [PATCH 30/99] clean --- .../main/java/org/thoughtcrime/securesms/loki/views/UserView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt index ae9f001f5..19642a6c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt @@ -91,4 +91,4 @@ class UserView : LinearLayout { } // endregion -} \ No newline at end of file +} From be2692fec672cbf5cb3fe00d81752879b2d10b04 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 19 Apr 2021 13:37:34 +1000 Subject: [PATCH 31/99] clean --- .../securesms/loki/activities/SelectContactsAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt index 4fb31d9fd..e8bbede27 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SelectContactsAdapter.kt @@ -71,4 +71,4 @@ class SelectContactsAdapter(private val context: Context, private val glide: Gli enum class Payload { MEMBER_CLICKED } -} \ No newline at end of file +} From 10554011f8b1873afa55fd87fecf41198cb9d2f1 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 20 Apr 2021 09:39:51 +1000 Subject: [PATCH 32/99] avoid displaying self in the contacts list --- .../securesms/loki/activities/CreateClosedGroupActivity.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index 2ad317bdb..bebbc5885 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -32,6 +32,10 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM set(newValue) { field = newValue; invalidateOptionsMenu() } private var members = listOf() set(value) { field = value; selectContactsAdapter.members = value } + private val publicKey: String + get() { + return TextSecurePreferences.getLocalNumber(this)!! + } private val selectContactsAdapter by lazy { SelectContactsAdapter(this, GlideApp.with(this)) @@ -72,7 +76,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM } private fun update(members: List) { - this.members = members + this.members = members.minus(publicKey) mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE invalidateOptionsMenu() From f9de34f9479ad9752fd30f6933d510a83aebdd87 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 20 Apr 2021 09:45:29 +1000 Subject: [PATCH 33/99] comment --- .../securesms/loki/activities/CreateClosedGroupActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index bebbc5885..f3a7f94bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -76,6 +76,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM } private fun update(members: List) { + //if there is a Note to self conversation, it loads self in the list, so we need to remove it here this.members = members.minus(publicKey) mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE From 3bfef2d0cdf1a92eccfd9ebc6eae66f90f45a119 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 20 Apr 2021 16:55:46 +1000 Subject: [PATCH 34/99] e UpdateMessageBuilder to generate data extraction message --- .../securesms/database/model/MessageRecord.java | 4 ++++ app/src/main/res/values-fr/strings.xml | 2 ++ .../messaging/utilities/UpdateMessageBuilder.kt | 14 ++++++++++---- libsession/src/main/res/values/strings.xml | 2 ++ 4 files changed, 18 insertions(+), 4 deletions(-) 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 af293d383..954cedb15 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 @@ -27,6 +27,7 @@ import network.loki.messenger.R; import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.messaging.utilities.UpdateMessageData; +import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; import org.session.libsession.database.documents.IdentityKeyMismatch; @@ -97,6 +98,9 @@ public abstract class MessageRecord extends DisplayRecord { } else if (isExpirationTimerUpdate()) { int seconds = (int) (getExpiresIn() / 1000); return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, getIndividualRecipient().getAddress().serialize(), isOutgoing())); + } else if (isDataExtraction()) { + if (isScreenshotExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT, getIndividualRecipient().getAddress().serialize()))); + else if (isMediaSavedExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize()))); } // TODO below lines are left here for compatibility with older group update messages, it can be deleted later on else if (isGroupUpdate() && isOutgoing()) { diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f156dad77..5fbc37a75 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -440,6 +440,8 @@ %1$s a désactivé les messages éphémères. Vous avez défini l’expiration des messages éphémères à %1$s. %1$s a défini l’expiration des messages éphémères à %2$s. + %1$s a pris une capture d’écran. + %1$s a sauvegardé un media. Votre numéro de sécurité avec %s a changé. Vous avez marqué votre numéro de sécurité avec %s comme vérifié Vous avez marqué votre numéro de sécurité avec %s comme vérifié à partir d’un autre appareil 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 index d6f446e36..7404d2e3c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -5,6 +5,7 @@ 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 +import org.session.libsignal.service.internal.push.SignalServiceProtos object UpdateMessageBuilder { @@ -82,7 +83,7 @@ object UpdateMessageBuilder { if (!isOutgoing && sender == null) return "" val senderName: String? = if (!isOutgoing) { MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender - } else { sender } + } else { context.getString(R.string.MessageRecord_you) } 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) @@ -93,8 +94,13 @@ object UpdateMessageBuilder { } } - //TODO do this when the current update is merged - fun buildDataExtractionMessage(): String { - return "" + fun buildDataExtractionMessage(context: Context, kind: SignalServiceProtos.DataExtractionNotification.Type, sender: String? = null): String { + val senderName = MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender + return when (kind) { + SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT -> + context.getString(R.string.MessageRecord_s_took_a_screenshot, senderName) + SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED -> + context.getString(R.string.MessageRecord_media_saved_by_s, senderName) + } } } diff --git a/libsession/src/main/res/values/strings.xml b/libsession/src/main/res/values/strings.xml index 116b10601..ee0510385 100644 --- a/libsession/src/main/res/values/strings.xml +++ b/libsession/src/main/res/values/strings.xml @@ -509,6 +509,8 @@ %1$s disabled disappearing messages. You set the disappearing message timer to %1$s %1$s set the disappearing message timer to %2$s + %1$s took a screenshot. + Media saved by %1$s. Your safety number with %s has changed. You marked your safety number with %s verified You marked your safety number with %s verified from another device From 1e40c861d17a3d2bbe9d8b8ac92137ce1fc17f27 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 20 Apr 2021 17:02:14 +1000 Subject: [PATCH 35/99] make screenlock work within 60s --- .../securesms/preferences/AppProtectionPreferenceFragment.java | 3 ++- .../org/thoughtcrime/securesms/service/KeyCachingService.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java index db9484f9b..e516916de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java @@ -102,7 +102,8 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment if (duration == 0) { TextSecurePreferences.setScreenLockTimeout(getContext(), 0); } else { - long timeoutSeconds = Math.max(TimeUnit.MILLISECONDS.toSeconds(duration), 60); + long timeoutSeconds = TimeUnit.MILLISECONDS.toSeconds(duration); +// long timeoutSeconds = Math.max(TimeUnit.MILLISECONDS.toSeconds(duration), 60); TextSecurePreferences.setScreenLockTimeout(getContext(), timeoutSeconds); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java index e1c2350ef..cde706c6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java @@ -210,7 +210,7 @@ public class KeyCachingService extends Service { boolean passLockActive = timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(context); long screenTimeout = TextSecurePreferences.getScreenLockTimeout(context); - boolean screenLockActive = screenTimeout >= 60 && TextSecurePreferences.isScreenLockEnabled(context); + boolean screenLockActive = screenTimeout >= 0 && TextSecurePreferences.isScreenLockEnabled(context); if (!appVisible && secretSet && (passLockActive || screenLockActive)) { long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(context); From 7aa1f30c986574f6af7b6ada06ad5e20b7be34fe Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 20 Apr 2021 17:07:21 +1000 Subject: [PATCH 36/99] fix --- .../securesms/conversation/ConversationUpdateItem.java | 4 ++-- .../securesms/database/model/MessageRecord.java | 6 +++--- .../messaging/messages/signal/IncomingMediaMessage.java | 2 +- .../messaging/sending_receiving/MessageReceiverHandler.kt | 2 +- .../DataExtractionNotificationInfoMessage.kt | 2 +- .../messaging/utilities/UpdateMessageBuilder.kt | 8 ++++---- 6 files changed, 12 insertions(+), 12 deletions(-) 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 721800675..91d1c4244 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -106,7 +106,7 @@ public class ConversationUpdateItem extends LinearLayout else if (messageRecord.isJoined()) setJoinedRecord(messageRecord); else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord); else if (messageRecord.isScreenshotExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT); - else if (messageRecord.isMediaSavedExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.MEDIASAVED); + else if (messageRecord.isMediaSavedExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED); else if (messageRecord.isEndSession()) setEndSessionRecord(messageRecord); else if (messageRecord.isIdentityUpdate()) setIdentityRecord(messageRecord); else if (messageRecord.isIdentityVerified() || @@ -154,7 +154,7 @@ public class ConversationUpdateItem extends LinearLayout @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getContext().getTheme()); if (kind == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT) { icon.setImageResource(R.drawable.quick_camera_dark); - } else if (kind == DataExtractionNotificationInfoMessage.Kind.MEDIASAVED) { + } else if (kind == DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED) { icon.setImageResource(R.drawable.ic_file_download_white_36dp); } icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); 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 954cedb15..9ecb9bac2 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 @@ -25,9 +25,9 @@ import android.text.style.StyleSpan; import network.loki.messenger.R; +import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.messaging.utilities.UpdateMessageData; -import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; import org.session.libsession.database.documents.IdentityKeyMismatch; @@ -99,8 +99,8 @@ public abstract class MessageRecord extends DisplayRecord { int seconds = (int) (getExpiresIn() / 1000); return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, getIndividualRecipient().getAddress().serialize(), isOutgoing())); } else if (isDataExtraction()) { - if (isScreenshotExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT, getIndividualRecipient().getAddress().serialize()))); - else if (isMediaSavedExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize()))); + if (isScreenshotExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize()))); + else if (isMediaSavedExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize()))); } // TODO below lines are left here for compatibility with older group update messages, it can be deleted later on else if (isGroupUpdate() && isOutgoing()) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index 1d359a6c6..f235e84e9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -131,7 +131,7 @@ public class IncomingMediaMessage { public boolean isMediaSavedDataExtraction() { if (dataExtractionNotification == null) return false; else { - return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.MEDIASAVED; + return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED; } } 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 e5d0c8e27..fc1b28e1c 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 @@ -103,7 +103,7 @@ private fun MessageReceiver.handleDataExtractionNotification(message: DataExtrac val senderPublicKey = message.sender!! val notification: DataExtractionNotificationInfoMessage = when(message.kind) { is DataExtractionNotification.Kind.Screenshot -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.SCREENSHOT) - is DataExtractionNotification.Kind.MediaSaved -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.MEDIASAVED) + is DataExtractionNotification.Kind.MediaSaved -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED) else -> return } storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.sentTimestamp!!) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt index 108b51c7a..aca360eca 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/dataextraction/DataExtractionNotificationInfoMessage.kt @@ -4,7 +4,7 @@ class DataExtractionNotificationInfoMessage { enum class Kind { SCREENSHOT, - MEDIASAVED + MEDIA_SAVED } var kind: Kind? = null 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 index 7404d2e3c..b2ad3ca2b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -3,9 +3,9 @@ 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.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage import org.session.libsession.utilities.ExpirationUtil import org.session.libsignal.service.api.messages.SignalServiceGroup -import org.session.libsignal.service.internal.push.SignalServiceProtos object UpdateMessageBuilder { @@ -94,12 +94,12 @@ object UpdateMessageBuilder { } } - fun buildDataExtractionMessage(context: Context, kind: SignalServiceProtos.DataExtractionNotification.Type, sender: String? = null): String { + fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, sender: String? = null): String { val senderName = MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender return when (kind) { - SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT -> + DataExtractionNotificationInfoMessage.Kind.SCREENSHOT -> context.getString(R.string.MessageRecord_s_took_a_screenshot, senderName) - SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED -> + DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED -> context.getString(R.string.MessageRecord_media_saved_by_s, senderName) } } From 1e164f8648b8b436d1e7885c2ce78b5abc42b975 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 20 Apr 2021 17:22:36 +1000 Subject: [PATCH 37/99] feat: adding default group handling to frontend viewmodel --- .../loki/activities/JoinPublicChatActivity.kt | 28 +++- .../securesms/loki/api/PublicChatManager.kt | 20 +-- .../loki/viewmodel/DefaultGroupsViewModel.kt | 39 ++++++ .../securesms/loki/viewmodel/State.kt | 7 + .../fragment_enter_chat_url.xml | 43 +++++- .../res/layout/fragment_enter_chat_url.xml | 12 +- .../xml/network_security_configuration.xml | 11 +- .../messaging/opengroups/OpenGroupAPIV2.kt | 67 ++++++--- .../opengroups/OpenGroupMessageV2.kt | 7 + .../sending_receiving/MessageSender.kt | 4 +- .../pollers/OpenGroupV2Poller.kt | 130 ++---------------- .../libsession/snode/OnionRequestAPI.kt | 14 +- .../org/session/libsession/snode/SnodeAPI.kt | 23 ++-- .../session/libsession/snode/SnodeMessage.kt | 10 +- .../libsignal/service/loki/api/SwarmAPI.kt | 16 ++- 15 files changed, 246 insertions(+), 185 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/DefaultGroupsViewModel.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/State.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt index 79c107c3c..51e6816d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt @@ -9,8 +9,7 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentPagerAdapter +import androidx.fragment.app.* import androidx.lifecycle.lifecycleScope import kotlinx.android.synthetic.main.activity_join_public_chat.* import kotlinx.android.synthetic.main.fragment_enter_chat_url.* @@ -18,13 +17,16 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R +import org.session.libsignal.utilities.logging.Log import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.session.libsignal.utilities.logging.Log import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities +import org.thoughtcrime.securesms.loki.viewmodel.DefaultGroup +import org.thoughtcrime.securesms.loki.viewmodel.DefaultGroupsViewModel +import org.thoughtcrime.securesms.loki.viewmodel.State class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate { private val adapter = JoinPublicChatActivityAdapter(this) @@ -122,14 +124,34 @@ private class JoinPublicChatActivityAdapter(val activity: JoinPublicChatActivity // region Enter Chat URL Fragment class EnterChatURLFragment : Fragment() { + // factory producer is app scoped because default groups will want to stick around for app lifetime + private val viewModel by activityViewModels() + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return inflater.inflate(R.layout.fragment_enter_chat_url, container, false) } + private fun populateDefaultGroups(groups: List) { + Log.d("Loki", "Got some default groups $groups") + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) chatURLEditText.imeOptions = chatURLEditText.imeOptions or 16777216 // Always use incognito keyboard joinPublicChatButton.setOnClickListener { joinPublicChatIfPossible() } + viewModel.defaultRooms.observe(viewLifecycleOwner) { state -> + when (state) { + State.Loading -> { + // show a loader here probs + } + is State.Error -> { + // hide the loader and the + } + is State.Success -> { + populateDefaultGroups(state.value) + } + } + } } private fun joinPublicChatIfPossible() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index ab587c817..de45a9207 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -30,7 +30,7 @@ class PublicChatManager(private val context: Context) { public fun areAllCaughtUp(): Boolean { var areAllCaughtUp = true refreshChatsAndPollers() - for ((threadID, chat) in chats) { + for ((threadID, _) in chats) { val poller = pollers[threadID] areAllCaughtUp = if (poller != null) areAllCaughtUp && poller.isCaughtUp else true } @@ -83,9 +83,9 @@ class PublicChatManager(private val context: Context) { @WorkerThread fun addChat(server: String, room: String): OpenGroupV2 { // Ensure the auth token is acquired. - OpenGroupAPIV2.getAuthToken(server).get() + OpenGroupAPIV2.getAuthToken(room, server).get() - val channelInfo = OpenGroupAPIV2.getChannelInfo(channel, server).get() + val channelInfo = OpenGroupAPIV2.getInfo(room, server).get() return addChat(server, room, channelInfo) } @@ -116,17 +116,19 @@ class PublicChatManager(private val context: Context) { } @WorkerThread - fun addChat(server: String, room: String, info: OpenGroupInfo): OpenGroupV2 { - val chat = OpenGroupV2(server, room, info.displayName, ) - var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) + fun addChat(server: String, room: String, info: OpenGroupAPIV2.Info): OpenGroupV2 { + val chat = OpenGroupV2(server, room, info.id, info.name, info.imageID) + val threadID = GroupManager.getOpenGroupThreadID(chat.id, context) var profilePicture: Bitmap? = null if (threadID < 0) { - if (info.profilePictureURL.isNotEmpty()) { - val profilePictureAsByteArray = OpenGroupAPIV2.downloadOpenGroupProfilePicture(server, info.profilePictureURL) + val imageID = info.imageID + if (!imageID.isNullOrEmpty()) { + val profilePictureAsByteArray = OpenGroupAPIV2.downloadOpenGroupProfilePicture(imageID) profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray) } - val result = GroupManager.createOpenGroup() + val result = GroupManager.createOpenGroup(chat.id, context, profilePicture, info.name) } + return chat } public fun removeChat(server: String, channel: Long) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/DefaultGroupsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/DefaultGroupsViewModel.kt new file mode 100644 index 000000000..5bb564e72 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/DefaultGroupsViewModel.kt @@ -0,0 +1,39 @@ +package org.thoughtcrime.securesms.loki.viewmodel + +import androidx.lifecycle.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import org.session.libsession.messaging.opengroups.OpenGroupAPIV2 +import org.session.libsignal.utilities.logging.Log + +class DefaultGroupsViewModel : ViewModel() { + + init { + OpenGroupAPIV2.getDefaultRoomsIfNeeded() + } + + val defaultRooms = OpenGroupAPIV2.defaultRooms.asLiveData().distinctUntilChanged().switchMap { groups -> + liveData { + // load images etc + emit(State.Loading) + val images = groups.filterNot { it.imageID.isNullOrEmpty() }.map { group -> + val image = viewModelScope.async(Dispatchers.IO) { + try { + OpenGroupAPIV2.downloadOpenGroupProfilePicture(group.imageID!!) + } catch (e: Exception) { + Log.e("Loki", "Error getting group profile picture", e) + null + } + } + group.id to image + }.toMap() + val defaultGroups = groups.map { group -> + DefaultGroup(group.id, group.name, images[group.id]?.await()) + } + emit(State.Success(defaultGroups)) + } + } + +} + +data class DefaultGroup(val id: String, val name: String, val image: ByteArray?) \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/State.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/State.kt new file mode 100644 index 000000000..88223b909 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/State.kt @@ -0,0 +1,7 @@ +package org.thoughtcrime.securesms.loki.viewmodel + +sealed class State { + object Loading : State() + data class Success(val value: T): State() + data class Error(val error: Exception): State() +} diff --git a/app/src/main/res/layout-sw400dp/fragment_enter_chat_url.xml b/app/src/main/res/layout-sw400dp/fragment_enter_chat_url.xml index a1bf13aed..b56e3e110 100644 --- a/app/src/main/res/layout-sw400dp/fragment_enter_chat_url.xml +++ b/app/src/main/res/layout-sw400dp/fragment_enter_chat_url.xml @@ -1,6 +1,6 @@ - + android:hint="@string/fragment_enter_chat_url_edit_text_hint" /> + + + + + +