diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 393163b649..02786c1943 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -1,6 +1,5 @@ package org.session.libsession.database -import android.content.Context import android.net.Uri import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.KeyPair @@ -26,7 +25,6 @@ import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer -import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord @@ -117,8 +115,6 @@ interface StorageProtocol { fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String, timestamp: Long) fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String) - fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, - members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long): Long? fun isLegacyClosedGroup(publicKey: String): Boolean fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? diff --git a/app/src/main/java/org/session/libsession/messaging/messages/Message.kt b/app/src/main/java/org/session/libsession/messaging/messages/Message.kt index 223ad0ea6f..05e839341b 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/Message.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/Message.kt @@ -1,7 +1,6 @@ package org.session.libsession.messaging.messages import network.loki.messenger.libsession_util.util.ExpiryMode -import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.visible.VisibleMessage diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingEncryptedMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingEncryptedMessage.java deleted file mode 100644 index f5a63d4ac5..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingEncryptedMessage.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -public class IncomingEncryptedMessage extends IncomingTextMessage { - - public IncomingEncryptedMessage(IncomingTextMessage base, String newBody) { - super(base, newBody); - } - - @Override - public boolean isSecureMessage() { - return true; - } -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingGroupMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingGroupMessage.java deleted file mode 100644 index 8046c82d99..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingGroupMessage.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -public class IncomingGroupMessage extends IncomingTextMessage { - - private final boolean updateMessage; - - public IncomingGroupMessage(IncomingTextMessage base, String body, boolean updateMessage) { - super(base, body); - this.updateMessage = updateMessage; - } - - @Override - public boolean isGroup() { - return true; - } - - public boolean isUpdateMessage() { return updateMessage; } - -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java deleted file mode 100644 index f61bd72e5f..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -import org.jspecify.annotations.Nullable; -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.data_extraction.DataExtractionNotificationInfoMessage; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.Contact; -import org.session.libsignal.messages.SignalServiceAttachment; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.database.model.content.MessageContent; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -public class IncomingMediaMessage { - - private final Address from; - private final Address.GroupLike groupId; - private final String body; - private final boolean push; - private final long sentTimeMillis; - private final int subscriptionId; - private final long expiresIn; - private final long expireStartedAt; - private final boolean messageRequestResponse; - private final boolean hasMention; - @Nullable - private final MessageContent messageContent; - - 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<>(); - - public IncomingMediaMessage(Address from, - long sentTimeMillis, - int subscriptionId, - long expiresIn, - long expireStartedAt, - boolean messageRequestResponse, - boolean hasMention, - Optional body, - Optional group, - Optional> attachments, - @Nullable MessageContent messageContent, - Optional quote, - Optional> sharedContacts, - Optional> linkPreviews, - Optional dataExtractionNotification) - { - this.messageContent = messageContent; - this.push = true; - this.from = from; - this.sentTimeMillis = sentTimeMillis; - this.body = body.orNull(); - this.subscriptionId = subscriptionId; - this.expiresIn = expiresIn; - this.expireStartedAt = expireStartedAt; - this.dataExtractionNotification = dataExtractionNotification.orNull(); - this.quote = quote.orNull(); - this.messageRequestResponse = messageRequestResponse; - this.hasMention = hasMention; - this.groupId = group.orNull(); - - this.attachments.addAll(PointerAttachment.forPointers(attachments)); - this.sharedContacts.addAll(sharedContacts.or(Collections.emptyList())); - this.linkPreviews.addAll(linkPreviews.or(Collections.emptyList())); - } - - public static IncomingMediaMessage from(VisibleMessage message, - Address from, - long expiresIn, - long expireStartedAt, - Optional group, - List attachments, - Optional quote, - Optional> linkPreviews) - { - return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, expireStartedAt, - false, message.getHasMention(), Optional.fromNullable(message.getText()), - group, Optional.fromNullable(attachments), null, quote, Optional.absent(), linkPreviews, Optional.absent()); - } - - public int getSubscriptionId() { - return subscriptionId; - } - - public String getBody() { - return body; - } - - public List getAttachments() { - return attachments; - } - - public Address getFrom() { - return from; - } - - public Address.GroupLike getGroupId() { - return groupId; - } - - public @Nullable MessageContent getMessageContent() { - return messageContent; - } - - public boolean isPushMessage() { - return push; - } - - public long getSentTimeMillis() { - return sentTimeMillis; - } - - public long getExpiresIn() { - return expiresIn; - } - - public long getExpireStartedAt() { - return expireStartedAt; - } - - public boolean isGroupMessage() { - return groupId != null; - } - - public boolean hasMention() { - return hasMention; - } - - public boolean isMediaSavedDataExtraction() { - if (dataExtractionNotification == null) return false; - else { - return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED; - } - } - - public QuoteModel getQuote() { - return quote; - } - - public List getSharedContacts() { - return sharedContacts; - } - - public List getLinkPreviews() { - return linkPreviews; - } - - public boolean isMessageRequestResponse() { - return messageRequestResponse; - } -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.kt new file mode 100644 index 0000000000..8e45afc698 --- /dev/null +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.kt @@ -0,0 +1,62 @@ +package org.session.libsession.messaging.messages.signal + +import network.loki.messenger.libsession_util.protocol.ProFeatures +import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.messaging.sending_receiving.attachments.Attachment +import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage +import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview +import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Contact +import org.thoughtcrime.securesms.database.model.content.MessageContent + +class IncomingMediaMessage( + val from: Address, + val sentTimeMillis: Long, + val subscriptionId: Int, + val expiresIn: Long, + val expireStartedAt: Long, + val isMessageRequestResponse: Boolean, + val hasMention: Boolean, + val body: String?, + val group: Address.GroupLike?, + val attachments: List, + val proFeatures: ProFeatures, + val messageContent: MessageContent?, + val quote: QuoteModel?, + val sharedContacts: List, + val linkPreviews: List, + val dataExtractionNotification: DataExtractionNotificationInfoMessage?, +) { + + constructor( + message: VisibleMessage, + from: Address, + expiresIn: Long, + expireStartedAt: Long, + group: Address.GroupLike?, + attachments: List, + quote: QuoteModel?, + linkPreviews: List + ): this( + from = from, + sentTimeMillis = message.sentTimestamp!!, + subscriptionId = -1, + expiresIn = expiresIn, + expireStartedAt = expireStartedAt, + isMessageRequestResponse = false, + hasMention = message.hasMention, + body = message.text, + group = group, + attachments = attachments, + proFeatures = message.proFeatures, + messageContent = null, + quote = quote, + sharedContacts = emptyList(), + linkPreviews = linkPreviews, + dataExtractionNotification = null + ) + + val isMediaSavedDataExtraction: Boolean get() = + dataExtractionNotification?.kind == DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED +} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java deleted file mode 100644 index 73bfd7a4d4..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java +++ /dev/null @@ -1,257 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.Nullable; - -import org.session.libsession.messaging.calls.CallMessageType; -import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.utilities.UpdateMessageData; -import org.session.libsession.utilities.Address; -import org.session.libsignal.utilities.guava.Optional; - -public class IncomingTextMessage implements Parcelable { - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public IncomingTextMessage createFromParcel(Parcel in) { - return new IncomingTextMessage(in); - } - - @Override - public IncomingTextMessage[] newArray(int size) { - return new IncomingTextMessage[size]; - } - }; - private static final String TAG = IncomingTextMessage.class.getSimpleName(); - - private final String message; - private Address sender; - private final int senderDeviceId; - private final int protocol; - private final String serviceCenterAddress; - private final boolean replyPathPresent; - private final String pseudoSubject; - private final long sentTimestampMillis; - private final Address.GroupLike groupId; - private final boolean push; - private final int subscriptionId; - private final long expiresInMillis; - private final long expireStartedAt; - private final boolean unidentified; - private final int callType; - private final boolean hasMention; - - private boolean isOpenGroupInvitation = false; - - public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, - String encodedBody, Optional group, - long expiresInMillis, long expireStartedAt, boolean unidentified, boolean hasMention) { - this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, expireStartedAt, unidentified, -1, hasMention); - } - - public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, - String encodedBody, Optional group, - long expiresInMillis, long expireStartedAt, boolean unidentified, int callType, boolean hasMention) { - this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, expireStartedAt, unidentified, callType, hasMention, true); - } - - public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, - String encodedBody, Optional group, - long expiresInMillis, long expireStartedAt, boolean unidentified, int callType, boolean hasMention, boolean isPush) { - this.message = encodedBody; - this.sender = sender; - this.senderDeviceId = senderDeviceId; - this.protocol = 31337; - this.serviceCenterAddress = "GCM"; - this.replyPathPresent = true; - this.pseudoSubject = ""; - this.sentTimestampMillis = sentTimestampMillis; - this.push = isPush; - this.subscriptionId = -1; - this.expiresInMillis = expiresInMillis; - this.expireStartedAt = expireStartedAt; - this.unidentified = unidentified; - this.callType = callType; - this.hasMention = hasMention; - this.groupId = group.orNull(); - } - - public IncomingTextMessage(Parcel in) { - this.message = in.readString(); - this.sender = in.readParcelable(IncomingTextMessage.class.getClassLoader()); - this.senderDeviceId = in.readInt(); - this.protocol = in.readInt(); - this.serviceCenterAddress = in.readString(); - this.replyPathPresent = (in.readInt() == 1); - this.pseudoSubject = in.readString(); - this.sentTimestampMillis = in.readLong(); - this.groupId = in.readParcelable(IncomingTextMessage.class.getClassLoader()); - this.push = (in.readInt() == 1); - this.subscriptionId = in.readInt(); - this.expiresInMillis = in.readLong(); - this.expireStartedAt = in.readLong(); - this.unidentified = in.readInt() == 1; - this.isOpenGroupInvitation = in.readInt() == 1; - this.callType = in.readInt(); - this.hasMention = in.readInt() == 1; - } - - public IncomingTextMessage(IncomingTextMessage base, String newBody) { - this.message = newBody; - this.sender = base.getSender(); - this.senderDeviceId = base.getSenderDeviceId(); - this.protocol = base.getProtocol(); - this.serviceCenterAddress = base.getServiceCenterAddress(); - this.replyPathPresent = base.isReplyPathPresent(); - this.pseudoSubject = base.getPseudoSubject(); - this.sentTimestampMillis = base.getSentTimestampMillis(); - this.groupId = base.getGroupId(); - this.push = base.isPush(); - this.subscriptionId = base.getSubscriptionId(); - this.expiresInMillis = base.getExpiresIn(); - this.expireStartedAt = base.getExpireStartedAt(); - this.unidentified = base.isUnidentified(); - this.isOpenGroupInvitation = base.isOpenGroupInvitation(); - this.callType = base.callType; - this.hasMention = base.hasMention; - } - - public static IncomingTextMessage from(VisibleMessage message, - Address sender, - Optional group, - long expiresInMillis, - long expireStartedAt) - { - return new IncomingTextMessage(sender, 1, message.getSentTimestamp(), message.getText(), group, expiresInMillis, expireStartedAt, false, message.getHasMention()); - } - - public static IncomingTextMessage fromOpenGroupInvitation(OpenGroupInvitation openGroupInvitation, - Address sender, - Long sentTimestamp, - long expiresInMillis, - long expireStartedAt) { - String url = openGroupInvitation.getUrl(); - String name = openGroupInvitation.getName(); - if (url == null || name == null) { return null; } - // FIXME: Doing toJSON() to get the body here is weird - String body = UpdateMessageData.Companion.buildOpenGroupInvitation(url, name).toJSON(); - IncomingTextMessage incomingTextMessage = new IncomingTextMessage(sender, 1, sentTimestamp, body, Optional.absent(), expiresInMillis, expireStartedAt, false, false); - incomingTextMessage.isOpenGroupInvitation = true; - return incomingTextMessage; - } - - public static IncomingTextMessage fromCallInfo(CallMessageType callMessageType, - Address sender, - Optional group, - long sentTimestamp, - long expiresInMillis, - long expireStartedAt) { - return new IncomingTextMessage(sender, 1, sentTimestamp, null, group, expiresInMillis, expireStartedAt, false, callMessageType.ordinal(), false, false); - } - - public int getSubscriptionId() { - return subscriptionId; - } - - public long getExpiresIn() { - return expiresInMillis; - } - - public long getExpireStartedAt() { - return expireStartedAt; - } - - public long getSentTimestampMillis() { - return sentTimestampMillis; - } - - public String getPseudoSubject() { - return pseudoSubject; - } - - public String getMessageBody() { - return message; - } - - public Address getSender() { - return sender; - } - - public int getSenderDeviceId() { - return senderDeviceId; - } - - public int getProtocol() { - return protocol; - } - - public String getServiceCenterAddress() { - return serviceCenterAddress; - } - - public boolean isReplyPathPresent() { - return replyPathPresent; - } - - public boolean isSecureMessage() { - return false; - } - - public boolean isPush() { - return push; - } - - public @Nullable Address.GroupLike getGroupId() { - return groupId; - } - - public boolean isGroup() { - return false; - } - - public boolean isUnidentified() { - return unidentified; - } - - public boolean isOpenGroupInvitation() { return isOpenGroupInvitation; } - - public boolean hasMention() { return hasMention; } - - public boolean isUnreadCallMessage() { - return callType == CallMessageType.CALL_MISSED.ordinal() || callType == CallMessageType.CALL_FIRST_MISSED.ordinal(); - } - - @Nullable - public CallMessageType getCallType() { - int callTypeLength = CallMessageType.values().length; - if (callType < 0 || callType >= callTypeLength) return null; - return CallMessageType.values()[callType]; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(message); - out.writeParcelable(sender, flags); - out.writeInt(senderDeviceId); - out.writeInt(protocol); - out.writeString(serviceCenterAddress); - out.writeInt(replyPathPresent ? 1 : 0); - out.writeString(pseudoSubject); - out.writeLong(sentTimestampMillis); - out.writeParcelable(groupId, flags); - out.writeInt(push ? 1 : 0); - out.writeInt(subscriptionId); - out.writeInt(unidentified ? 1 : 0); - out.writeInt(isOpenGroupInvitation ? 1 : 0); - out.writeInt(callType); - out.writeInt(hasMention ? 1 : 0); - } -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.kt new file mode 100644 index 0000000000..37e7e22a8a --- /dev/null +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.kt @@ -0,0 +1,138 @@ +package org.session.libsession.messaging.messages.signal + +import network.loki.messenger.libsession_util.protocol.ProFeatures +import org.session.libsession.messaging.calls.CallMessageType +import org.session.libsession.messaging.messages.visible.OpenGroupInvitation +import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.messaging.utilities.UpdateMessageData +import org.session.libsession.utilities.Address +import java.util.EnumSet + +data class IncomingTextMessage( + val message: String?, + val sender: Address, + val senderDeviceId: Int, + val sentTimestampMillis: Long, + val group: Address.GroupLike?, + val push: Boolean, + val expiresInMillis: Long, + val expireStartedAt: Long, + val unidentified: Boolean, + val callType: Int, + val hasMention: Boolean, + val isOpenGroupInvitation: Boolean, + val isSecureMessage: Boolean, + val proFeatures: ProFeatures, + val isGroupMessage: Boolean = false, + val isGroupUpdateMessage: Boolean = false, +) { + // Legacy code + val protocol: Int get() = 31337 + + // Legacy code + val serviceCenterAddress: String get() = "GCM" + + // Legacy code + val replyPathPresent: Boolean get() = true + + // Legacy code + val pseudoSubject: String get() = "" + + // Legacy code + val subscriptionId: Int get() = -1 + + val callMessageType: CallMessageType? get() = + CallMessageType.entries.getOrNull(callType) + + val isUnreadCallMessage: Boolean + get() = callMessageType in EnumSet.of( + CallMessageType.CALL_MISSED, + CallMessageType.CALL_FIRST_MISSED, + ) + + val proFeaturesRawValue: Long get() = proFeatures.rawValue + + init { + check(!isGroupUpdateMessage || isGroupMessage) { + "A message cannot be a group update message if it is not a group message" + } + } + + constructor( + message: VisibleMessage, + sender: Address, + group: Address.GroupLike?, + expiresInMillis: Long, + expireStartedAt: Long, + ): this( + sender = sender, + senderDeviceId = 1, + sentTimestampMillis = message.sentTimestamp!!, + message = message.text, + group = group, + expiresInMillis = expiresInMillis, + expireStartedAt = expireStartedAt, + unidentified = false, + hasMention = message.hasMention, + push = true, + callType = -1, + isOpenGroupInvitation = false, + isSecureMessage = false, + proFeatures = message.proFeatures, + ) + constructor( + callMessageType: CallMessageType, + sender: Address, + group: Address.GroupLike?, + sentTimestampMillis: Long, + expiresInMillis: Long, + expireStartedAt: Long, + ): this( + message = null, + sender = sender, + senderDeviceId = 1, + sentTimestampMillis = sentTimestampMillis, + group = group, + push = false, + expiresInMillis = expiresInMillis, + expireStartedAt = expireStartedAt, + unidentified = false, + callType = callMessageType.ordinal, + hasMention = false, + isOpenGroupInvitation = false, + isSecureMessage = false, + proFeatures = ProFeatures.NONE, + ) + + companion object { + fun fromOpenGroupInvitation( + invitation: OpenGroupInvitation, + sender: Address, + sentTimestampMillis: Long, + expiresInMillis: Long, + expireStartedAt: Long, + ): IncomingTextMessage? { + val body = UpdateMessageData.buildOpenGroupInvitation( + url = invitation.url ?: return null, + name = invitation.name ?: return null, + ).toJSON() + + return IncomingTextMessage( + message = body, + sender = sender, + senderDeviceId = 1, + sentTimestampMillis = sentTimestampMillis, + group = null, + push = true, + expiresInMillis = expiresInMillis, + expireStartedAt = expireStartedAt, + unidentified = false, + callType = -1, + hasMention = false, + isOpenGroupInvitation = true, + isSecureMessage = false, + proFeatures = ProFeatures.NONE, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java deleted file mode 100644 index 05ed23dbaf..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.utilities.Contact; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.thoughtcrime.securesms.database.model.content.MessageContent; - -import java.util.LinkedList; -import java.util.List; - -public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { - - private final String groupID; - private final boolean isUpdateMessage; - - public OutgoingGroupMediaMessage(@NonNull Address recipient, - @NonNull String body, - @Nullable String groupId, - @Nullable final Attachment avatar, - long sentTime, - long expireIn, - long expireStartedAt, - boolean updateMessage, - @Nullable QuoteModel quote, - @NonNull List contacts, - @NonNull List previews, - @Nullable MessageContent messageContent) - { - super(recipient, body, - new LinkedList() {{if (avatar != null) add(avatar);}}, - sentTime, - DistributionTypes.CONVERSATION, expireIn, expireStartedAt, quote, contacts, previews, messageContent); - - this.groupID = groupId; - this.isUpdateMessage = updateMessage; - } - - @Override - public boolean isGroup() { - return true; - } - - public String getGroupId() { - return groupID; - } - - public boolean isUpdateMessage() { - return isUpdateMessage; - } -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java deleted file mode 100644 index 67c35f699c..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ /dev/null @@ -1,163 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.Contact; -import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.IdentityKeyMismatch; -import org.session.libsession.utilities.NetworkFailure; -import org.thoughtcrime.securesms.database.model.content.MessageContent; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -/** - * Represents an outgoing mms message. Note this class is only used for saving messages - * into the database. We will still use {@link org.session.libsession.messaging.messages.Message} - * as a model when sending the message to the network. - *
- * See {@link OutgoingTextMessage} for the sms table counterpart. - */ -public class OutgoingMediaMessage { - - private final Address recipient; - protected final String body; - protected final List attachments; - private final long sentTimeMillis; - private final int distributionType; - private final int subscriptionId; - private final long expiresIn; - private final long expireStartedAt; - private final QuoteModel outgoingQuote; - @Nullable - private final MessageContent messageContent; - - private final List networkFailures = new LinkedList<>(); - private final List identityKeyMismatches = new LinkedList<>(); - private final List contacts = new LinkedList<>(); - private final List linkPreviews = new LinkedList<>(); - - public OutgoingMediaMessage(Address recipient, String message, - List attachments, long sentTimeMillis, - int subscriptionId, long expiresIn, long expireStartedAt, - int distributionType, - @Nullable QuoteModel outgoingQuote, - @NonNull List contacts, - @NonNull List linkPreviews, - @NonNull List networkFailures, - @NonNull List identityKeyMismatches, - @Nullable MessageContent messageContent) - { - this.recipient = recipient; - this.body = message; - this.sentTimeMillis = sentTimeMillis; - this.distributionType = distributionType; - this.attachments = attachments; - this.subscriptionId = subscriptionId; - this.expiresIn = expiresIn; - this.expireStartedAt = expireStartedAt; - this.outgoingQuote = outgoingQuote; - this.messageContent = messageContent; - - this.contacts.addAll(contacts); - this.linkPreviews.addAll(linkPreviews); - this.networkFailures.addAll(networkFailures); - this.identityKeyMismatches.addAll(identityKeyMismatches); - } - - public OutgoingMediaMessage(OutgoingMediaMessage that) { - this.recipient = that.getRecipient(); - this.body = that.body; - this.distributionType = that.distributionType; - this.attachments = that.attachments; - this.sentTimeMillis = that.sentTimeMillis; - this.subscriptionId = that.subscriptionId; - this.expiresIn = that.expiresIn; - this.expireStartedAt = that.expireStartedAt; - this.outgoingQuote = that.outgoingQuote; - this.messageContent = that.messageContent; - - this.identityKeyMismatches.addAll(that.identityKeyMismatches); - this.networkFailures.addAll(that.networkFailures); - this.contacts.addAll(that.contacts); - this.linkPreviews.addAll(that.linkPreviews); - } - - public static OutgoingMediaMessage from(VisibleMessage message, - Address recipient, - List attachments, - @Nullable QuoteModel outgoingQuote, - @Nullable LinkPreview linkPreview, - long expiresInMillis, - long expireStartedAt) - { - List previews = Collections.emptyList(); - if (linkPreview != null) { - previews = Collections.singletonList(linkPreview); - } - return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1, - expiresInMillis, expireStartedAt, DistributionTypes.DEFAULT, outgoingQuote, - Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList(), null); - } - - @Nullable - public MessageContent getMessageContent() { - return messageContent; - } - - public Address getRecipient() { - return recipient; - } - - public String getBody() { - return body; - } - - public List getAttachments() { - return attachments; - } - - public boolean isSecure() { - return true; - } - - public boolean isGroup() { - return false; - } - - public long getSentTimeMillis() { - return sentTimeMillis; - } - - public int getSubscriptionId() { - return subscriptionId; - } - - public long getExpiresIn() { - return expiresIn; - } - - public long getExpireStartedAt() { - return expireStartedAt; - } - - public @Nullable QuoteModel getOutgoingQuote() { - return outgoingQuote; - } - - public @NonNull List getSharedContacts() { - return contacts; - } - - public @NonNull List getLinkPreviews() { - return linkPreviews; - } - -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.kt new file mode 100644 index 0000000000..459afdc644 --- /dev/null +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.kt @@ -0,0 +1,101 @@ +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.link_preview.LinkPreview +import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Contact +import org.session.libsession.utilities.DistributionTypes +import org.session.libsession.utilities.IdentityKeyMismatch +import org.session.libsession.utilities.NetworkFailure +import org.thoughtcrime.securesms.database.model.content.MessageContent + +class OutgoingMediaMessage( + val recipient: Address, + val body: String?, + val attachments: List, + val sentTimeMillis: Long, + val distributionType: Int, + val subscriptionId: Int, + val expiresInMillis: Long, + val expireStartedAtMillis: Long, + val outgoingQuote: QuoteModel?, + val messageContent: MessageContent?, + val networkFailures: List, + val identityKeyMismatches: List, + val contacts: List, + val linkPreviews: List, + val group: Address.GroupLike?, + val isGroupUpdateMessage: Boolean, +) { + init { + check(!isGroupUpdateMessage || group != null) { + "Group update messages must have a group address" + } + } + + constructor( + message: VisibleMessage, + recipient: Address, + attachments: List, + outgoingQuote: QuoteModel?, + linkPreview: LinkPreview?, + expiresInMillis: Long, + expireStartedAt: Long + ) : this( + recipient = recipient, + body = message.text, + attachments = attachments, + sentTimeMillis = message.sentTimestamp!!, + subscriptionId = -1, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAt, + distributionType = DistributionTypes.DEFAULT, + outgoingQuote = outgoingQuote, + contacts = emptyList(), + messageContent = null, + linkPreviews = linkPreview?.let { listOf(it) } ?: emptyList(), + networkFailures = emptyList(), + identityKeyMismatches = emptyList(), + group = null, + isGroupUpdateMessage = false, + ) + + constructor( + recipient: Address, + body: String?, + group: Address.GroupLike, + avatar: Attachment?, + sentTimeMillis: Long, + expiresInMillis: Long, + expireStartedAtMillis: Long, + isGroupUpdateMessage: Boolean, + quote: QuoteModel?, + contacts: List, + previews: List, + messageContent: MessageContent?, + ) : this( + recipient = recipient, + body = body, + attachments = avatar?.let { listOf(it) } ?: emptyList(), + sentTimeMillis = sentTimeMillis, + distributionType = DistributionTypes.CONVERSATION, + subscriptionId = -1, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAtMillis, + outgoingQuote = quote, + messageContent = messageContent, + networkFailures = emptyList(), + identityKeyMismatches = emptyList(), + contacts = contacts, + linkPreviews = previews, + group = group, + isGroupUpdateMessage = isGroupUpdateMessage, + ) + + // legacy code + val isSecure: Boolean get() = true + + val isGroup: Boolean get() = group != null +} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java deleted file mode 100644 index 0f594d4eba..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.Contact; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.thoughtcrime.securesms.database.model.content.MessageContent; - -import java.util.Collections; -import java.util.List; - -public class OutgoingSecureMediaMessage extends OutgoingMediaMessage { - - public OutgoingSecureMediaMessage(Address recipient, String body, - List attachments, - long sentTimeMillis, - int distributionType, - long expiresIn, - long expireStartedAt, - @Nullable QuoteModel quote, - @NonNull List contacts, - @NonNull List previews, - @Nullable MessageContent messageContent) - { - super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expireStartedAt, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList(), messageContent); - } - - public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { - super(base); - } - - @Override - public boolean isSecure() { - return true; - } -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java deleted file mode 100644 index c79a68102f..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.utilities.Address; -import org.session.libsession.messaging.utilities.UpdateMessageData; - -public class OutgoingTextMessage { - private final Address recipient; - private final String message; - private final int subscriptionId; - private final long expiresIn; - private final long expireStartedAt; - private final long sentTimestampMillis; - private boolean isOpenGroupInvitation = false; - - public OutgoingTextMessage(Address recipient, String message, long expiresIn, long expireStartedAt, int subscriptionId, long sentTimestampMillis) { - this.recipient = recipient; - this.message = message; - this.expiresIn = expiresIn; - this.expireStartedAt= expireStartedAt; - this.subscriptionId = subscriptionId; - this.sentTimestampMillis = sentTimestampMillis; - } - - public static OutgoingTextMessage from(VisibleMessage message, Address recipient, long expiresInMillis, long expireStartedAt) { - return new OutgoingTextMessage(recipient, message.getText(), expiresInMillis, expireStartedAt, -1, message.getSentTimestamp()); - } - - public static OutgoingTextMessage fromOpenGroupInvitation(OpenGroupInvitation openGroupInvitation, Address recipient, Long sentTimestamp, long expiresInMillis, long expireStartedAt) { - String url = openGroupInvitation.getUrl(); - String name = openGroupInvitation.getName(); - if (url == null || name == null) { return null; } - // FIXME: Doing toJSON() to get the body here is weird - String body = UpdateMessageData.Companion.buildOpenGroupInvitation(url, name).toJSON(); - OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipient, body, expiresInMillis, expireStartedAt, -1, sentTimestamp); - outgoingTextMessage.isOpenGroupInvitation = true; - return outgoingTextMessage; - } - - public long getExpiresIn() { - return expiresIn; - } - - public long getExpireStartedAt() { - return expireStartedAt; - } - - public int getSubscriptionId() { - return subscriptionId; - } - - public String getMessageBody() { - return message; - } - - public Address getRecipient() { - return recipient; - } - - public long getSentTimestampMillis() { - return sentTimestampMillis; - } - - public boolean isSecureMessage() { - return true; - } - - public boolean isOpenGroupInvitation() { return isOpenGroupInvitation; } -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.kt new file mode 100644 index 0000000000..c15d4bb2d5 --- /dev/null +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.kt @@ -0,0 +1,52 @@ +package org.session.libsession.messaging.messages.signal + +import org.session.libsession.messaging.messages.visible.OpenGroupInvitation +import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.messaging.utilities.UpdateMessageData +import org.session.libsession.utilities.Address + +data class OutgoingTextMessage( + val recipient: Address, + val message: String?, + val expiresInMillis: Long, + val expireStartedAtMillis: Long, + val subscriptionId: Int = -1, + val sentTimestampMillis: Long, + val isOpenGroupInvitation: Boolean, +) { + constructor( + message: VisibleMessage, + recipient: Address, + expiresInMillis: Long, + expireStartedAtMillis: Long, + ): this( + recipient = recipient, + message = message.text, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAtMillis, + sentTimestampMillis = message.sentTimestamp!!, + isOpenGroupInvitation = false, + ) + + companion object { + fun fromOpenGroupInvitation( + invitation: OpenGroupInvitation, + recipient: Address, + sentTimestampMillis: Long, + expiresInMillis: Long, + expireStartedAtMillis: Long, + ): OutgoingTextMessage? { + return OutgoingTextMessage( + recipient = recipient, + message = UpdateMessageData.buildOpenGroupInvitation( + url = invitation.url ?: return null, + name = invitation.name ?: return null, + ).toJSON(), + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAtMillis, + sentTimestampMillis = sentTimestampMillis, + isOpenGroupInvitation = true, + ) + } + } +} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index 42059c1c50..e79ccdf0e0 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -1,6 +1,8 @@ package org.session.libsession.messaging.messages.visible +import androidx.annotation.Keep import network.loki.messenger.BuildConfig +import network.loki.messenger.libsession_util.protocol.ProFeatures import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.copyExpiration @@ -24,9 +26,14 @@ data class VisibleMessage( var openGroupInvitation: OpenGroupInvitation? = null, var reaction: Reaction? = null, var hasMention: Boolean = false, - var blocksMessageRequests: Boolean = false + var blocksMessageRequests: Boolean = false, + var proFeatures: ProFeatures = ProFeatures.NONE ) : Message() { + // This empty constructor is needed for kryo serialization + @Keep + constructor(): this(proFeatures = ProFeatures.NONE) + override val isSelfSendValid: Boolean = true override fun shouldDiscardIfBlocked(): Boolean = true diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageParser.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageParser.kt index 4cf5defd89..eb12a41092 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageParser.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageParser.kt @@ -1,7 +1,10 @@ package org.session.libsession.messaging.sending_receiving +import network.loki.messenger.libsession_util.ED25519 import network.loki.messenger.libsession_util.SessionEncrypt +import network.loki.messenger.libsession_util.pro.ProProof import network.loki.messenger.libsession_util.protocol.DecodedEnvelope +import network.loki.messenger.libsession_util.protocol.ProFeatures import network.loki.messenger.libsession_util.protocol.SessionProtocol import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.messages.Message @@ -24,6 +27,7 @@ import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix +import java.time.Instant import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -73,9 +77,20 @@ class MessageParser @Inject constructor( currentUserBlindedIDs: List, senderIdPrefix: IdPrefix ): Pair { + val proFeatures = if (decodedEnvelope.proProof?.status( + senderED25519PubKey = decodedEnvelope.senderEd25519PubKey.data, + signedMessage = null, + now = decodedEnvelope.timestamp) == ProProof.Status.Valid + ) { + decodedEnvelope.proFeatures + } else { + ProFeatures.NONE + } + return parseMessage( sender = AccountId(senderIdPrefix, decodedEnvelope.senderX25519PubKey.data), contentPlaintext = decodedEnvelope.contentPlainText.data, + proFeatures = proFeatures, messageTimestampMs = decodedEnvelope.timestamp.toEpochMilli(), relaxSignatureCheck = relaxSignatureCheck, checkForBlockStatus = checkForBlockStatus, @@ -88,6 +103,7 @@ class MessageParser @Inject constructor( private fun parseMessage( sender: AccountId, contentPlaintext: ByteArray, + proFeatures: ProFeatures, messageTimestampMs: Long, relaxSignatureCheck: Boolean, checkForBlockStatus: Boolean, @@ -126,6 +142,7 @@ class MessageParser @Inject constructor( message.sentTimestamp = messageTimestampMs message.receivedTimestamp = snodeClock.currentTimeMills() message.isSenderSelf = isSenderSelf + (message as? VisibleMessage)?.proFeatures = proFeatures // Validate var isValid = message.isValid() @@ -227,8 +244,19 @@ class MessageParser @Inject constructor( val sender = AccountId(msg.sessionId) + val proFeatures = if (decoded.proProof?.status( + senderED25519PubKey = sender.pubKeyBytes, + signedMessage = null, + now = Instant.ofEpochMilli((msg.posted * 1000.0).toLong())) == ProProof.Status.Valid + ) { + decoded.proFeatures + } else { + ProFeatures.NONE + } + return parseMessage( contentPlaintext = decoded.contentPlainText.data, + proFeatures = proFeatures, relaxSignatureCheck = true, checkForBlockStatus = false, isForGroup = false, @@ -263,9 +291,27 @@ class MessageParser @Inject constructor( ) val sender = Address.Standard(AccountId(senderId)) + val messageSent = Instant.ofEpochMilli((msg.postedAt * 1000.0).toLong()) + + val proProof = decoded.proProof + val proFeatures = if (proProof != null) { + val hasValidProof = ED25519.ed25519PubKeysFromCurve25519(sender.accountId.pubKeyBytes) + .any { senderEd25519PubKey -> + proProof.status(senderEd25519PubKey, now = messageSent) == ProProof.Status.Valid + } + + if (hasValidProof) { + decoded.proFeatures + } else { + ProFeatures.NONE + } + } else { + ProFeatures.NONE + } return parseMessage( contentPlaintext = decoded.contentPlainText.data, + proFeatures = proFeatures, relaxSignatureCheck = true, checkForBlockStatus = false, isForGroup = false, diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageRequestResponseHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageRequestResponseHandler.kt index d153a1606a..1d303ea872 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageRequestResponseHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageRequestResponseHandler.kt @@ -1,5 +1,6 @@ package org.session.libsession.messaging.sending_receiving +import network.loki.messenger.libsession_util.protocol.ProFeatures import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.messaging.messages.ProfileUpdateHandler.Updates.Companion.toUpdates @@ -13,7 +14,6 @@ import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.updateContact import org.session.libsession.utilities.upsertContact import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.database.BlindMappingRepository import org.thoughtcrime.securesms.database.MmsDatabase import org.thoughtcrime.securesms.database.RecipientRepository @@ -151,21 +151,22 @@ class MessageRequestResponseHandler @Inject constructor( if (!didApproveMe) { mmsDatabase.insertSecureDecryptedMessageInbox( retrieved = IncomingMediaMessage( - messageSender.address, - messageTimestampMs, - -1, - 0L, - 0L, - true, - false, - Optional.absent(), - Optional.absent(), - Optional.absent(), - null, - Optional.absent(), - Optional.absent(), - Optional.absent(), - Optional.absent() + from = messageSender.address, + sentTimeMillis = messageTimestampMs, + subscriptionId = -1, + expiresIn = 0L, + expireStartedAt = 0L, + isMessageRequestResponse = true, + hasMention = false, + body = null, + group = null, + attachments = emptyList(), + proFeatures = ProFeatures.NONE, + messageContent = null, + quote = null, + sharedContacts = emptyList(), + linkPreviews = emptyList(), + dataExtractionNotification = null ), threadId, runThreadUpdate = true, diff --git a/app/src/main/java/org/session/libsignal/utilities/AccountId.kt b/app/src/main/java/org/session/libsignal/utilities/AccountId.kt index adce91c77b..bce895ba6c 100644 --- a/app/src/main/java/org/session/libsignal/utilities/AccountId.kt +++ b/app/src/main/java/org/session/libsignal/utilities/AccountId.kt @@ -24,6 +24,12 @@ data class AccountId( val prefix: IdPrefix? get() = IdPrefix.fromValue(hexString) + /** + * The public key bytes with the prefix removed. + * + * Note: the type of public key varies depending on the prefix. For STANDARD, + * it's an Curve25519 public key, otherwise, it should be an Ed25519 public key. + */ val pubKeyBytes: ByteArray by lazy { Hex.fromStringCondensed(hexString.drop(2)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 9be55c88e6..83363452ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -2125,7 +2125,12 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, message.sentTimestamp = sentTimestamp message.text = text val expiresInMillis = viewModel.recipient.expiryMode.expiryMillis - val outgoingTextMessage = OutgoingTextMessage.from(message, recipient.address, expiresInMillis, 0) + val outgoingTextMessage = OutgoingTextMessage( + message = message, + recipient = recipient.address, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = 0 + ) // Clear the input bar binding.inputBar.text = "" @@ -2181,7 +2186,15 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val expireStartedAtMs = if (viewModel.recipient.expiryMode is ExpiryMode.AfterSend) { sentTimestamp } else 0 - val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient.address, attachments, localQuote, linkPreview, expiresInMs, expireStartedAtMs) + val outgoingTextMessage = OutgoingMediaMessage( + message = message, + recipient = recipient.address, + attachments = attachments, + outgoingQuote = localQuote, + linkPreview = linkPreview, + expiresInMillis = expiresInMs, + expireStartedAt = expireStartedAtMs + ) // Clear the input bar binding.inputBar.text = "" diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 62a40f5dab..ae8259f446 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -28,9 +28,7 @@ import org.json.JSONException import org.json.JSONObject import org.session.libsession.messaging.messages.ExpirationConfiguration 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.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment @@ -40,6 +38,7 @@ import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.Address.Companion.toAddress import org.session.libsession.utilities.Contact +import org.session.libsession.utilities.DistributionTypes import org.session.libsession.utilities.IdentityKeyMismatch import org.session.libsession.utilities.IdentityKeyMismatchList import org.session.libsession.utilities.NetworkFailure @@ -457,8 +456,8 @@ class MmsDatabase @Inject constructor( .asSequence() .filterNot { obj: DatabaseAttachment -> obj.isQuote || contactAttachments.contains(obj) || previewAttachments.contains(obj) } .toList() - var networkFailures: List? = LinkedList() - var mismatches: List? = LinkedList() + var networkFailures: List = emptyList() + var mismatches: List = emptyList() var quote: QuoteModel? = null if (quoteId > 0 && (!quoteText.isNullOrEmpty() || quoteAttachments.isNotEmpty())) { quote = QuoteModel( @@ -496,25 +495,24 @@ class MmsDatabase @Inject constructor( Log.w(TAG, "Failed to decode message content for message ID $messageId", it) }.getOrNull() - val message = OutgoingMediaMessage( - fromSerialized(address), - body, - attachments, - timestamp, - subscriptionId, - expiresIn, - expireStartedAt, - distributionType, - quote, - contacts, - previews, - networkFailures!!, - mismatches!!, - messageContent, + return OutgoingMediaMessage( + recipient = fromSerialized(serialized = address), + body = body, + attachments = attachments, + sentTimeMillis = timestamp, + distributionType = distributionType, + subscriptionId = subscriptionId, + expiresInMillis = expiresIn, + expireStartedAtMillis = expireStartedAt, + outgoingQuote = quote, + messageContent = messageContent, + networkFailures = networkFailures?.filterNotNull().orEmpty(), + identityKeyMismatches = mismatches?.filterNotNull().orEmpty(), + contacts = contacts, + linkPreviews = previews, + group = null, + isGroupUpdateMessage = false ) - return if (MmsSmsColumns.Types.isSecureType(outboxType)) { - OutgoingSecureMediaMessage(message) - } else message } throw NoSuchMessageException("No record found for id: $messageId") } finally { @@ -604,7 +602,8 @@ class MmsDatabase @Inject constructor( runThreadUpdate: Boolean ): Optional { if (threadId < 0 ) throw MmsException("No thread ID supplied!") - if (retrieved.messageContent is DisappearingMessageUpdate) deleteExpirationTimerMessages(threadId, false.takeUnless { retrieved.groupId != null }) + if (retrieved.messageContent is DisappearingMessageUpdate) + deleteExpirationTimerMessages(threadId, false.takeUnless { retrieved.group != null }) val contentValues = ContentValues() contentValues.put(DATE_SENT, retrieved.sentTimeMillis) contentValues.put(ADDRESS, retrieved.from.toString()) @@ -612,6 +611,7 @@ class MmsDatabase @Inject constructor( contentValues.put(THREAD_ID, threadId) contentValues.put(CONTENT_LOCATION, contentLocation) contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED) + contentValues.put(PRO_FEATURES, retrieved.proFeatures.rawValue) // In open groups messages should be sorted by their server timestamp var receivedTimestamp = serverTimestamp if (serverTimestamp == 0L) { @@ -625,7 +625,7 @@ class MmsDatabase @Inject constructor( contentValues.put(SUBSCRIPTION_ID, retrieved.subscriptionId) contentValues.put(EXPIRES_IN, retrieved.expiresIn) contentValues.put(EXPIRE_STARTED, retrieved.expireStartedAt) - contentValues.put(HAS_MENTION, retrieved.hasMention()) + contentValues.put(HAS_MENTION, retrieved.hasMention) contentValues.put(MESSAGE_REQUEST_RESPONSE, retrieved.isMessageRequestResponse) if (!contentValues.containsKey(DATE_SENT)) { contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED)) @@ -637,7 +637,7 @@ class MmsDatabase @Inject constructor( contentValues.put(QUOTE_MISSING, if (retrieved.quote.missing) 1 else 0) quoteAttachments = retrieved.quote.attachments } - if (retrieved.isPushMessage && isDuplicate(retrieved, threadId) || + if (isDuplicate(retrieved, threadId) || retrieved.isMessageRequestResponse && isDuplicateMessageRequestResponse( retrieved, threadId @@ -693,10 +693,7 @@ class MmsDatabase @Inject constructor( serverTimestamp: Long = 0, runThreadUpdate: Boolean ): Optional { - var type = MmsSmsColumns.Types.BASE_INBOX_TYPE or MmsSmsColumns.Types.SECURE_MESSAGE_BIT - if (retrieved.isPushMessage) { - type = type or MmsSmsColumns.Types.PUSH_MESSAGE_BIT - } + var type = MmsSmsColumns.Types.BASE_INBOX_TYPE or MmsSmsColumns.Types.SECURE_MESSAGE_BIT or MmsSmsColumns.Types.PUSH_MESSAGE_BIT if (retrieved.isMediaSavedDataExtraction) { type = type or MmsSmsColumns.Types.MEDIA_SAVED_EXTRACTION_BIT } @@ -706,11 +703,11 @@ class MmsDatabase @Inject constructor( return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp, runThreadUpdate) } - @JvmOverloads @Throws(MmsException::class) fun insertMessageOutbox( message: OutgoingMediaMessage, - threadId: Long, forceSms: Boolean, + threadId: Long, + forceSms: Boolean, serverTimestamp: Long = 0, runThreadUpdate: Boolean ): Long { @@ -718,8 +715,8 @@ class MmsDatabase @Inject constructor( if (message.isSecure) type = type or (MmsSmsColumns.Types.SECURE_MESSAGE_BIT or MmsSmsColumns.Types.PUSH_MESSAGE_BIT) if (forceSms) type = type or MmsSmsColumns.Types.MESSAGE_FORCE_SMS_BIT - if (message.isGroup && message is OutgoingGroupMediaMessage) { - if (message.isUpdateMessage) type = type or MmsSmsColumns.Types.GROUP_UPDATE_MESSAGE_BIT + if (message.isGroup) { + if (message.isGroupUpdateMessage) type = type or MmsSmsColumns.Types.GROUP_UPDATE_MESSAGE_BIT } val earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(message.sentTimeMillis) val earlyReadReceipts = earlyReadReceiptCache.remove(message.sentTimeMillis) @@ -735,8 +732,8 @@ class MmsDatabase @Inject constructor( } contentValues.put(DATE_RECEIVED, receivedTimestamp) contentValues.put(SUBSCRIPTION_ID, message.subscriptionId) - contentValues.put(EXPIRES_IN, message.expiresIn) - contentValues.put(EXPIRE_STARTED, message.expireStartedAt) + contentValues.put(EXPIRES_IN, message.expiresInMillis) + contentValues.put(EXPIRE_STARTED, message.expireStartedAtMillis) contentValues.put(ADDRESS, message.recipient.toString()) contentValues.put( DELIVERY_RECEIPT_COUNT, @@ -762,7 +759,7 @@ class MmsDatabase @Inject constructor( messageContent = message.messageContent, attachments = message.attachments, quoteAttachments = quoteAttachments, - sharedContacts = message.sharedContacts, + sharedContacts = message.contacts, linkPreviews = message.linkPreviews, contentValues = contentValues, ) 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 c030c7e600..ef9e5b841d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -24,19 +24,14 @@ import android.database.Cursor; import android.text.TextUtils; - import com.annimon.stream.Stream; import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.json.JSONArray; -import net.zetetic.database.sqlcipher.SQLiteStatement; - -import org.apache.commons.lang3.StringUtils; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.session.libsession.messaging.calls.CallMessageType; -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.snode.SnodeAPI; @@ -59,7 +54,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -447,16 +441,9 @@ public void updateSentTimestamp(long messageId, long newTimestamp) { protected Optional insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp, boolean runThreadUpdate) { Address recipient = message.getSender(); + Address groupRecipient = message.getGroup(); - Address groupRecipient; - - if (message.getGroupId() == null) { - groupRecipient = null; - } else { - groupRecipient = message.getGroupId(); - } - - boolean unread = (message.isSecureMessage() || message.isGroup() || message.isUnreadCallMessage()); + boolean unread = (message.isSecureMessage() || message.isGroupMessage() || message.isUnreadCallMessage()); long threadId; @@ -465,16 +452,16 @@ protected Optional insertMessageInbox(IncomingTextMessage message, if (message.isSecureMessage()) { type |= Types.SECURE_MESSAGE_BIT; - } else if (message.isGroup()) { + } else if (message.isGroupMessage()) { type |= Types.SECURE_MESSAGE_BIT; - if (((IncomingGroupMessage)message).isUpdateMessage()) type |= GROUP_UPDATE_MESSAGE_BIT; + if (message.isGroupUpdateMessage()) type |= GROUP_UPDATE_MESSAGE_BIT; } - if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT; + if (message.getPush()) type |= Types.PUSH_MESSAGE_BIT; if (message.isOpenGroupInvitation()) type |= Types.OPEN_GROUP_INVITATION_BIT; - CallMessageType callMessageType = message.getCallType(); + CallMessageType callMessageType = message.getCallMessageType(); if (callMessageType != null) { type |= getCallMessageTypeMask(callMessageType); } @@ -490,21 +477,22 @@ protected Optional insertMessageInbox(IncomingTextMessage message, values.put(PROTOCOL, message.getProtocol()); values.put(READ, unread ? 0 : 1); values.put(SUBSCRIPTION_ID, message.getSubscriptionId()); - values.put(EXPIRES_IN, message.getExpiresIn()); + values.put(EXPIRES_IN, message.getExpiresInMillis()); values.put(EXPIRE_STARTED, message.getExpireStartedAt()); - values.put(UNIDENTIFIED, message.isUnidentified()); - values.put(HAS_MENTION, message.hasMention()); + values.put(UNIDENTIFIED, message.getUnidentified()); + values.put(HAS_MENTION, message.getHasMention()); if (!TextUtils.isEmpty(message.getPseudoSubject())) values.put(SUBJECT, message.getPseudoSubject()); - values.put(REPLY_PATH_PRESENT, message.isReplyPathPresent()); + values.put(REPLY_PATH_PRESENT, message.getReplyPathPresent()); values.put(SERVICE_CENTER, message.getServiceCenterAddress()); - values.put(BODY, message.getMessageBody()); + values.put(BODY, message.getMessage()); values.put(TYPE, type); values.put(THREAD_ID, threadId); + values.put(PRO_FEATURES, message.getProFeaturesRawValue()); - if (message.isPush() && isDuplicate(message, threadId)) { + if (message.getPush() && isDuplicate(message, threadId)) { Log.w(TAG, "Duplicate message (" + message.getSentTimestampMillis() + "), ignoring..."); return Optional.absent(); } else { @@ -562,9 +550,8 @@ public long insertMessageOutbox(long threadId, OutgoingTextMessage message, boolean forceSms, long date, boolean runThreadUpdate) { - long type = Types.BASE_SENDING_TYPE; + long type = Types.BASE_SENDING_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT; - if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT); if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT; if (message.isOpenGroupInvitation()) type |= Types.OPEN_GROUP_INVITATION_BIT; @@ -575,14 +562,14 @@ public long insertMessageOutbox(long threadId, OutgoingTextMessage message, ContentValues contentValues = new ContentValues(); contentValues.put(ADDRESS, address.toString()); contentValues.put(THREAD_ID, threadId); - contentValues.put(BODY, message.getMessageBody()); + contentValues.put(BODY, message.getMessage()); contentValues.put(DATE_RECEIVED, SnodeAPI.getNowWithOffset()); contentValues.put(DATE_SENT, message.getSentTimestampMillis()); contentValues.put(READ, 1); contentValues.put(TYPE, type); contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); - contentValues.put(EXPIRES_IN, message.getExpiresIn()); - contentValues.put(EXPIRE_STARTED, message.getExpireStartedAt()); + contentValues.put(EXPIRES_IN, message.getExpiresInMillis()); + contentValues.put(EXPIRE_STARTED, message.getExpireStartedAtMillis()); contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum()); contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum()); 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 91b7d38abf..558e534da9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -8,6 +8,7 @@ import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINN import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.MutableConversationVolatileConfig import network.loki.messenger.libsession_util.ReadableUserGroupsConfig +import network.loki.messenger.libsession_util.protocol.ProFeatures import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.Conversation @@ -23,11 +24,8 @@ import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.GroupUpdated -import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage -import org.session.libsession.messaging.messages.signal.IncomingGroupMessage import org.session.libsession.messaging.messages.signal.IncomingMediaMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage -import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.Attachment @@ -36,6 +34,7 @@ import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage 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.attachments.PointerAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier @@ -49,7 +48,6 @@ import org.session.libsession.utilities.Address.Companion.toAddress import org.session.libsession.utilities.GroupDisplayInfo import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.isCommunity import org.session.libsession.utilities.isCommunityInbox @@ -59,7 +57,6 @@ import org.session.libsession.utilities.upsertContact import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer -import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional @@ -79,7 +76,6 @@ import java.time.ZoneId import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton -import kotlin.math.max import network.loki.messenger.libsession_util.util.GroupMember as LibSessionGroupMember private const val TAG = "Storage" @@ -339,20 +335,20 @@ open class Storage @Inject constructor( } val quote: Optional = if (quotes != null) Optional.of(quotes) else Optional.absent() - val linkPreviews: Optional> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! }) + val linkPreviews = linkPreview.mapNotNull { it } val insertResult = if (isUserSender || isUserBlindedSender) { val pointers = attachments.mapNotNull { it.toSignalAttachment() } - val mediaMessage = OutgoingMediaMessage.from( - message, - targetAddress, - pointers, - quote.orNull(), - linkPreviews.orNull()?.firstOrNull(), - expiresInMillis, - expireStartedAt + val mediaMessage = OutgoingMediaMessage( + message = message, + recipient = targetAddress, + attachments = pointers, + outgoingQuote = quote.orNull(), + linkPreview = linkPreviews.firstOrNull(), + expiresInMillis = expiresInMillis, + expireStartedAt = expireStartedAt ) mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!, runThreadUpdate) } else { @@ -360,7 +356,16 @@ open class Storage @Inject constructor( val signalServiceAttachments = attachments.mapNotNull { it.toSignalPointer() } - val mediaMessage = IncomingMediaMessage.from(message, senderAddress, expiresInMillis, expireStartedAt, Optional.fromNullable(threadRecipient.address as? Address.GroupLike), signalServiceAttachments, quote, linkPreviews) + val mediaMessage = IncomingMediaMessage( + message = message, + from = senderAddress, + expiresIn = expiresInMillis, + expireStartedAt = expireStartedAt, + group = threadRecipient.address as? Address.GroupLike, + attachments = PointerAttachment.forPointers(Optional.of(signalServiceAttachments)), + quote = quotes, + linkPreviews = linkPreviews + ) mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID!!, message.receivedTimestamp ?: 0, runThreadUpdate) } @@ -370,14 +375,37 @@ open class Storage @Inject constructor( val isOpenGroupInvitation = (message.openGroupInvitation != null) val insertResult = if (isUserSender || isUserBlindedSender) { - val textMessage = if (isOpenGroupInvitation) OutgoingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, targetAddress, message.sentTimestamp, expiresInMillis, expireStartedAt) - else OutgoingTextMessage.from(message, targetAddress, expiresInMillis, expireStartedAt) + val textMessage = if (isOpenGroupInvitation) OutgoingTextMessage.fromOpenGroupInvitation( + invitation = message.openGroupInvitation!!, + recipient = targetAddress, + sentTimestampMillis = message.sentTimestamp!!, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAt + )!! + else OutgoingTextMessage( + message = message, + recipient = targetAddress, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAt + ) + smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!, runThreadUpdate) } else { - val textMessage = if (isOpenGroupInvitation) IncomingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, senderAddress, message.sentTimestamp, expiresInMillis, expireStartedAt) - else IncomingTextMessage.from(message, senderAddress, Optional.fromNullable(threadRecipient.address as? Address.GroupLike), expiresInMillis, expireStartedAt) - val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody) - smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0, runThreadUpdate) + val textMessage = if (isOpenGroupInvitation) IncomingTextMessage.fromOpenGroupInvitation( + invitation = message.openGroupInvitation!!, + sender = senderAddress, + sentTimestampMillis = message.sentTimestamp!!, + expiresInMillis = expiresInMillis, + expireStartedAt = expireStartedAt + )!! + else IncomingTextMessage( + message = message, + sender = senderAddress, + group = threadRecipient.address as? Address.GroupLike, + expiresInMillis = expiresInMillis, + expireStartedAt = expireStartedAt + ) + smsDatabase.insertMessageInbox(textMessage.copy(isSecureMessage = true), message.receivedTimestamp ?: 0, runThreadUpdate) } messageID = insertResult.orNull()?.messageId?.let { MessageId(it, mms = false) } } @@ -653,40 +681,6 @@ open class Storage @Inject constructor( groupDatabase.updateMembers(groupID, members) } - override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long): Long? { - val userPublicKey = getUserPublicKey()!! - val recipient = fromSerialized(groupID) - val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" - val infoMessage = OutgoingGroupMediaMessage( - recipient, - updateData, - groupID, - null, - sentTimestamp, - 0, - 0, - true, - null, - listOf(), - listOf(), - null - ) - val mmsDB = mmsDatabase - val mmsSmsDB = mmsSmsDatabase - if (mmsSmsDB.getMessageFor(threadID, sentTimestamp, userPublicKey) != null) { - Log.w(TAG, "Bailing from insertOutgoingInfoMessage because we believe the message has already been sent!") - return null - } - val infoMessageID = mmsDB.insertMessageOutbox( - infoMessage, - threadID, - false, - runThreadUpdate = true - ) - mmsDB.markAsSent(infoMessageID, true) - return infoMessageID - } - override fun isLegacyClosedGroup(publicKey: String): Boolean { return lokiAPIDatabase.isClosedGroup(publicKey) } @@ -806,30 +800,30 @@ open class Storage @Inject constructor( private fun insertUpdateControlMessage(updateData: UpdateMessageData, sentTimestamp: Long, senderPublicKey: String?, closedGroup: AccountId): MessageId? { val userPublicKey = getUserPublicKey()!! - val address = fromSerialized(closedGroup.hexString) + val address = Address.Group(closedGroup) val recipient = recipientRepository.getRecipientSync(address) val threadDb = threadDatabase val threadID = threadDb.getThreadIdIfExistsFor(address) val expiryMode = recipient.expiryMode - val expiresInMillis = expiryMode?.expiryMillis ?: 0 + val expiresInMillis = expiryMode.expiryMillis val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val inviteJson = updateData.toJSON() if (senderPublicKey == null || senderPublicKey == userPublicKey) { - val infoMessage = OutgoingGroupMediaMessage( - address, - inviteJson, - closedGroup.hexString, - null, - sentTimestamp, - expiresInMillis, - expireStartedAt, - true, - null, - listOf(), - listOf(), - null + val infoMessage = OutgoingMediaMessage( + recipient = address, + body = inviteJson, + group = address, + avatar = null, + sentTimeMillis = sentTimestamp, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAt, + isGroupUpdateMessage = true, + quote = null, + contacts = listOf(), + previews = listOf(), + messageContent = null ) val mmsDB = mmsDatabase val mmsSmsDB = mmsSmsDatabase @@ -844,10 +838,29 @@ open class Storage @Inject constructor( mmsDB.markAsSent(infoMessageID, true) return MessageId(infoMessageID, mms = true) } else { - val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(Address.Group(closedGroup)), expiresInMillis, expireStartedAt, true, false) - val infoMessage = IncomingGroupMessage(m, inviteJson, true) + val m = IncomingTextMessage( + sender = fromSerialized(senderPublicKey), + senderDeviceId = 1, + sentTimestampMillis = sentTimestamp, + message = inviteJson, + group = Address.Group(closedGroup), + expiresInMillis = expiresInMillis, + expireStartedAt = expireStartedAt, + unidentified = true, + hasMention = false, + push = true, + callType = -1, + isOpenGroupInvitation = false, + isSecureMessage = false, + proFeatures = ProFeatures.NONE, + isGroupMessage = true, + isGroupUpdateMessage = true, + ) val smsDB = smsDatabase - val insertResult = smsDB.insertMessageInbox(infoMessage, true) + val insertResult = smsDB.insertMessageInbox(m.copy( + isGroupUpdateMessage = true, + message = inviteJson + ), true) return insertResult.orNull()?.messageId?.let { MessageId(it, mms = false) } } } @@ -1059,14 +1072,15 @@ open class Storage @Inject constructor( expireStartedAt, false, false, - Optional.absent(), - Optional.absent(), - Optional.absent(), null, - Optional.absent(), - Optional.absent(), - Optional.absent(), - Optional.of(message) + null, + emptyList(), + ProFeatures.NONE, + null, + null, + emptyList(), + emptyList(), + message ) mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) @@ -1079,21 +1093,22 @@ open class Storage @Inject constructor( val userPublicKey = getUserPublicKey() ?: return val message = IncomingMediaMessage( - fromSerialized(userPublicKey), - clock.currentTimeMills(), - -1, - 0, - 0, - true, - false, - Optional.absent(), - Optional.absent(), - Optional.absent(), - null, - Optional.absent(), - Optional.absent(), - Optional.absent(), - Optional.absent() + from = fromSerialized(userPublicKey), + sentTimeMillis = clock.currentTimeMills(), + subscriptionId = -1, + expiresIn = 0, + expireStartedAt = 0, + isMessageRequestResponse = true, + hasMention = false, + body = null, + group = null, + attachments = emptyList(), + proFeatures = ProFeatures.NONE, + messageContent = null, + quote = null, + sharedContacts = emptyList(), + linkPreviews = emptyList(), + dataExtractionNotification = null ) mmsDatabase.insertSecureDecryptedMessageInbox(message, threadId, runThreadUpdate = true) } @@ -1104,7 +1119,14 @@ open class Storage @Inject constructor( val expiryMode = recipient.expiryMode.coerceSendToRead() val expiresInMillis = expiryMode.expiryMillis val expireStartedAt = if (expiryMode != ExpiryMode.NONE) clock.currentTimeMills() else 0 - val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp, expiresInMillis, expireStartedAt) + val callMessage = IncomingTextMessage( + callMessageType = callMessageType, + sender = address, + group = null, + sentTimestampMillis = sentTimestamp, + expiresInMillis = expiresInMillis, + expireStartedAt = expireStartedAt + ) smsDatabase.insertCallMessage(callMessage) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java deleted file mode 100644 index 42b3d406dc..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.thoughtcrime.securesms.notifications; - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; - -import androidx.core.app.RemoteInput; - -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; -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.notifications.MessageNotifier; -import org.session.libsession.snode.SnodeAPI; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.AddressKt; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.database.MarkedMessageInfo; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.RecipientRepository; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.mms.MmsException; - -import java.util.Collections; -import java.util.List; - -import javax.inject.Inject; - -import dagger.hilt.android.AndroidEntryPoint; -import network.loki.messenger.libsession_util.util.ExpiryMode; - -/** - * Get the response text from the Android Auto and sends an message as a reply - */ -@AndroidEntryPoint -public class AndroidAutoReplyReceiver extends BroadcastReceiver { - - public static final String TAG = AndroidAutoReplyReceiver.class.getSimpleName(); - public static final String REPLY_ACTION = "network.loki.securesms.notifications.ANDROID_AUTO_REPLY"; - public static final String ADDRESS_EXTRA = "car_address"; - public static final String VOICE_REPLY_KEY = "car_voice_reply_key"; - public static final String THREAD_ID_EXTRA = "car_reply_thread_id"; - - @Inject - ThreadDatabase threadDatabase; - - @Inject - RecipientRepository recipientRepository; - - @Inject - MmsDatabase mmsDatabase; - - @Inject - SmsDatabase smsDatabase; - - @Inject - MessageNotifier messageNotifier; - - @Inject - MarkReadProcessor markReadProcessor; - - @Inject - MessageSender messageSender; - - @SuppressLint("StaticFieldLeak") - @Override - public void onReceive(final Context context, Intent intent) - { - if (!REPLY_ACTION.equals(intent.getAction())) return; - - Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - - if (remoteInput == null) return; - - final Address address = intent.getParcelableExtra(ADDRESS_EXTRA); - final long threadId = intent.getLongExtra(THREAD_ID_EXTRA, -1); - final CharSequence responseText = getMessageText(intent); - - if (responseText != null) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - - long replyThreadId; - - if (threadId == -1) { - replyThreadId = threadDatabase.getOrCreateThreadIdFor(address); - } else { - replyThreadId = threadId; - } - - VisibleMessage message = new VisibleMessage(); - message.setText(responseText.toString()); - message.setSentTimestamp(SnodeAPI.getNowWithOffset()); - messageSender.send(message, address); - ExpiryMode expiryMode = recipientRepository.getRecipientSync(address).getExpiryMode(); - long expiresInMillis = expiryMode.getExpiryMillis(); - long expireStartedAt = expiryMode instanceof ExpiryMode.AfterSend ? message.getSentTimestamp() : 0L; - - if (AddressKt.isGroupOrCommunity(address)) { - Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); - OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, address, Collections.emptyList(), null, null, expiresInMillis, 0); - try { - mmsDatabase.insertMessageOutbox(reply, replyThreadId, false, true); - } catch (MmsException e) { - Log.w(TAG, e); - } - } else { - Log.w("AndroidAutoReplyReceiver", "Sending regular message "); - OutgoingTextMessage reply = OutgoingTextMessage.from(message, address, expiresInMillis, expireStartedAt); - smsDatabase.insertMessageOutbox(replyThreadId, reply, false, SnodeAPI.getNowWithOffset(), true); - } - - List messageIds = threadDatabase.setRead(replyThreadId, true); - - messageNotifier.updateNotification(context); - markReadProcessor.process(messageIds); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - private CharSequence getMessageText(Intent intent) { - Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - if (remoteInput != null) { - return remoteInput.getCharSequence(VOICE_REPLY_KEY); - } - return null; - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.kt new file mode 100644 index 0000000000..9972ca2685 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms.notifications + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.AsyncTask +import androidx.core.app.RemoteInput +import dagger.hilt.android.AndroidEntryPoint +import network.loki.messenger.libsession_util.util.ExpiryMode.AfterSend +import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage +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.notifications.MessageNotifier +import org.session.libsession.snode.SnodeAPI.nowWithOffset +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.isGroupOrCommunity +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.MmsDatabase +import org.thoughtcrime.securesms.database.RecipientRepository +import org.thoughtcrime.securesms.database.SmsDatabase +import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.mms.MmsException +import javax.inject.Inject + +/** + * Get the response text from the Android Auto and sends an message as a reply + */ +@AndroidEntryPoint +class AndroidAutoReplyReceiver : BroadcastReceiver() { + @Inject + lateinit var threadDatabase: ThreadDatabase + + @Inject + lateinit var recipientRepository: RecipientRepository + + @Inject + lateinit var mmsDatabase: MmsDatabase + + @Inject + lateinit var smsDatabase: SmsDatabase + + @Inject + lateinit var messageNotifier: MessageNotifier + + @Inject + lateinit var markReadProcessor: MarkReadProcessor + + @Inject + lateinit var messageSender: MessageSender + + @SuppressLint("StaticFieldLeak") + override fun onReceive(context: Context, intent: Intent) { + if (REPLY_ACTION != intent.getAction()) return + + val remoteInput = RemoteInput.getResultsFromIntent(intent) + + if (remoteInput == null) return + + val address = intent.getParcelableExtra(ADDRESS_EXTRA) + val threadId = intent.getLongExtra(THREAD_ID_EXTRA, -1) + val responseText = getMessageText(intent) + + if (responseText != null) { + object : AsyncTask() { + override fun doInBackground(vararg params: Void?): Void? { + val replyThreadId: Long + + if (threadId == -1L) { + replyThreadId = threadDatabase.getOrCreateThreadIdFor(address) + } else { + replyThreadId = threadId + } + + val message = VisibleMessage() + message.text = responseText.toString() + message.sentTimestamp = nowWithOffset + messageSender.send(message, address!!) + val expiryMode = recipientRepository.getRecipientSync(address).expiryMode + val expiresInMillis = expiryMode.expiryMillis + val expireStartedAt: Long = + (if (expiryMode is AfterSend) message.sentTimestamp!! else 0L) + + if (address.isGroupOrCommunity) { + Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message") + val reply = OutgoingMediaMessage( + message = message, + recipient = address, + attachments = listOf(), + outgoingQuote = null, + linkPreview = null, + expiresInMillis = expiresInMillis, + expireStartedAt = 0 + ) + try { + mmsDatabase.insertMessageOutbox( + message = reply, + threadId = replyThreadId, + forceSms = false, + runThreadUpdate = true + ) + } catch (e: MmsException) { + Log.w(TAG, e) + } + } else { + Log.w("AndroidAutoReplyReceiver", "Sending regular message ") + val reply = OutgoingTextMessage( + message = message, + recipient = address, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAt + ) + smsDatabase.insertMessageOutbox( + replyThreadId, + reply, + false, + nowWithOffset, + true + ) + } + + val messageIds = threadDatabase.setRead(replyThreadId, true) + + messageNotifier.updateNotification(context) + markReadProcessor.process(messageIds) + + return null + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } + } + + private fun getMessageText(intent: Intent): CharSequence? { + val remoteInput = RemoteInput.getResultsFromIntent(intent) + if (remoteInput != null) { + return remoteInput.getCharSequence(VOICE_REPLY_KEY) + } + return null + } + + companion object { + val TAG: String = AndroidAutoReplyReceiver::class.java.getSimpleName() + const val REPLY_ACTION: String = "network.loki.securesms.notifications.ANDROID_AUTO_REPLY" + const val ADDRESS_EXTRA: String = "car_address" + const val VOICE_REPLY_KEY: String = "car_voice_reply_key" + const val THREAD_ID_EXTRA: String = "car_reply_thread_id" + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java deleted file mode 100644 index 9d10f23a9e..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2016 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.thoughtcrime.securesms.notifications; - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; - -import androidx.core.app.RemoteInput; - -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; -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.notifications.MessageNotifier; -import org.session.libsession.snode.SnodeClock; -import org.session.libsession.utilities.Address; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.database.MarkedMessageInfo; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.RecipientRepository; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.Storage; -import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.database.model.MessageId; -import org.thoughtcrime.securesms.mms.MmsException; - -import java.util.Collections; -import java.util.List; - -import javax.inject.Inject; - -import dagger.hilt.android.AndroidEntryPoint; -import network.loki.messenger.libsession_util.util.ExpiryMode; - -/** - * Get the response text from the Wearable Device and sends an message as a reply - */ -@AndroidEntryPoint -public class RemoteReplyReceiver extends BroadcastReceiver { - - public static final String TAG = RemoteReplyReceiver.class.getSimpleName(); - public static final String REPLY_ACTION = "network.loki.securesms.notifications.WEAR_REPLY"; - public static final String ADDRESS_EXTRA = "address"; - public static final String REPLY_METHOD = "reply_method"; - - @Inject - ThreadDatabase threadDatabase; - @Inject - MmsDatabase mmsDatabase; - @Inject - SmsDatabase smsDatabase; - @Inject - Storage storage; - @Inject - MessageNotifier messageNotifier; - @Inject - SnodeClock clock; - @Inject - RecipientRepository recipientRepository; - @Inject - MarkReadProcessor markReadProcessor; - @Inject - MessageSender messageSender; - - @SuppressLint("StaticFieldLeak") - @Override - public void onReceive(final Context context, Intent intent) { - if (!REPLY_ACTION.equals(intent.getAction())) return; - - Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - - if (remoteInput == null) return; - - final Address address = intent.getParcelableExtra(ADDRESS_EXTRA); - final ReplyMethod replyMethod = (ReplyMethod) intent.getSerializableExtra(REPLY_METHOD); - final CharSequence responseText = remoteInput.getCharSequence(DefaultMessageNotifier.EXTRA_REMOTE_REPLY); - - if (address == null) throw new AssertionError("No address specified"); - if (replyMethod == null) throw new AssertionError("No reply method specified"); - - if (responseText != null) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - long threadId = threadDatabase.getOrCreateThreadIdFor(address); - VisibleMessage message = new VisibleMessage(); - message.setSentTimestamp(clock.currentTimeMills()); - message.setText(responseText.toString()); - ExpiryMode expiryMode = recipientRepository.getRecipientSync(address).getExpiryMode(); - - long expiresInMillis = expiryMode.getExpiryMillis(); - long expireStartedAt = expiryMode instanceof ExpiryMode.AfterSend ? message.getSentTimestamp() : 0L; - switch (replyMethod) { - case GroupMessage: { - OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, address, Collections.emptyList(), null, null, expiresInMillis, 0); - try { - message.setId(new MessageId(mmsDatabase.insertMessageOutbox(reply, threadId, false, true), true)); - messageSender.send(message, address); - } catch (MmsException e) { - Log.w(TAG, e); - } - break; - } - case SecureMessage: { - OutgoingTextMessage reply = OutgoingTextMessage.from(message, address, expiresInMillis, expireStartedAt); - message.setId(new MessageId(smsDatabase.insertMessageOutbox(threadId, reply, false, System.currentTimeMillis(), true), false)); - messageSender.send(message, address); - break; - } - default: - throw new AssertionError("Unknown Reply method"); - } - - List messageIds = threadDatabase.setRead(threadId, true); - - messageNotifier.updateNotification(context); - markReadProcessor.process(messageIds); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.kt new file mode 100644 index 0000000000..f9e18340ea --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms.notifications + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.AsyncTask +import androidx.core.app.RemoteInput +import dagger.hilt.android.AndroidEntryPoint +import network.loki.messenger.libsession_util.util.ExpiryMode.AfterSend +import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage +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.notifications.MessageNotifier +import org.session.libsession.snode.SnodeClock +import org.session.libsession.utilities.Address +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.MmsDatabase +import org.thoughtcrime.securesms.database.RecipientRepository +import org.thoughtcrime.securesms.database.SmsDatabase +import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.mms.MmsException +import javax.inject.Inject + +/** + * Get the response text from the Wearable Device and sends an message as a reply + */ +@AndroidEntryPoint +class RemoteReplyReceiver : BroadcastReceiver() { + @Inject + lateinit var threadDatabase: ThreadDatabase + + @Inject + lateinit var mmsDatabase: MmsDatabase + + @Inject + lateinit var smsDatabase: SmsDatabase + + @Inject + lateinit var storage: Storage + + @Inject + lateinit var messageNotifier: MessageNotifier + + @Inject + lateinit var clock: SnodeClock + + @Inject + lateinit var recipientRepository: RecipientRepository + + @Inject + lateinit var markReadProcessor: MarkReadProcessor + + @Inject + lateinit var messageSender: MessageSender + + @SuppressLint("StaticFieldLeak") + override fun onReceive(context: Context, intent: Intent) { + if (REPLY_ACTION != intent.getAction()) return + + val remoteInput = RemoteInput.getResultsFromIntent(intent) + + if (remoteInput == null) return + + val address = intent.getParcelableExtra(ADDRESS_EXTRA) + val replyMethod = intent.getSerializableExtra(REPLY_METHOD) as ReplyMethod? + val responseText = remoteInput.getCharSequence(DefaultMessageNotifier.EXTRA_REMOTE_REPLY) + + if (address == null) throw AssertionError("No address specified") + if (replyMethod == null) throw AssertionError("No reply method specified") + + if (responseText != null) { + object : AsyncTask() { + override fun doInBackground(vararg params: Void?): Void? { + val threadId = threadDatabase.getOrCreateThreadIdFor(address) + val message = VisibleMessage() + message.sentTimestamp = clock.currentTimeMills() + message.text = responseText.toString() + val expiryMode = recipientRepository.getRecipientSync(address).expiryMode + + val expiresInMillis = expiryMode.expiryMillis + val expireStartedAt: Long = + (if (expiryMode is AfterSend) message.sentTimestamp else 0L)!! + when (replyMethod) { + ReplyMethod.GroupMessage -> { + val reply = OutgoingMediaMessage( + message = message, + recipient = address, + attachments = listOf(), + outgoingQuote = null, + linkPreview = null, + expiresInMillis = expiresInMillis, + expireStartedAt = 0 + ) + try { + message.id = MessageId( + mmsDatabase.insertMessageOutbox( + message = reply, + threadId = threadId, + forceSms = false, + runThreadUpdate = true + ), true + ) + messageSender.send(message, address) + } catch (e: MmsException) { + Log.w(TAG, e) + } + } + + ReplyMethod.SecureMessage -> { + val reply = OutgoingTextMessage( + message = message, + recipient = address, + expiresInMillis = expiresInMillis, + expireStartedAtMillis = expireStartedAt + ) + message.id = MessageId( + smsDatabase.insertMessageOutbox( + threadId, + reply, + false, + System.currentTimeMillis(), + true + ), false + ) + messageSender.send(message, address) + } + } + + val messageIds = threadDatabase.setRead(threadId, true) + + messageNotifier.updateNotification(context) + markReadProcessor.process(messageIds) + + return null + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } + } + + companion object { + val TAG: String = RemoteReplyReceiver::class.java.getSimpleName() + const val REPLY_ACTION: String = "network.loki.securesms.notifications.WEAR_REPLY" + const val ADDRESS_EXTRA: String = "address" + const val REPLY_METHOD: String = "reply_method" + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 6c9c4ff8a9..a78897ac18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -274,10 +274,10 @@ class DefaultConversationRepository @Inject constructor( val outgoingTextMessage = OutgoingTextMessage.fromOpenGroupInvitation( openGroupInvitation, contact, - message.sentTimestamp, + message.sentTimestamp!!, expirationConfig.expiryMillis, expireStartedAt - ) + )!! message.id = MessageId( smsDb.insertMessageOutbox(contactThreadId, outgoingTextMessage, false, message.sentTimestamp!!, true), diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt index 221fda5723..9e31662708 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt @@ -8,12 +8,12 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull +import network.loki.messenger.libsession_util.protocol.ProFeatures import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.signal.IncomingMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage +import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized @@ -21,10 +21,8 @@ import org.session.libsession.utilities.Address.Companion.toAddress import org.session.libsession.utilities.DistributionTypes import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol -import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.auth.LoginStateRepository import org.thoughtcrime.securesms.database.MessagingDatabase import org.thoughtcrime.securesms.database.MmsDatabase @@ -83,7 +81,6 @@ class ExpiringMessageManager @Inject constructor( val sentTimestamp = message.sentTimestamp val groupAddress = message.groupPublicKey?.toAddress() as? Address.GroupLike val expiresInMillis = message.expiryMode.expiryMillis - var groupInfo = Optional.absent() val address = fromSerialized(senderPublicKey!!) var recipient = recipientRepository.getRecipientSync(address) @@ -96,20 +93,23 @@ class ExpiringMessageManager @Inject constructor( val threadId = recipient.address.let(storage.get()::getThreadId) ?: return null val mediaMessage = IncomingMediaMessage( - address, sentTimestamp!!, -1, - expiresInMillis, - 0, // Marking expiryStartedAt as 0 as expiration logic will be universally applied on received messages + from = address, + sentTimeMillis = sentTimestamp!!, + subscriptionId = -1, + expiresIn = expiresInMillis, + expireStartedAt = 0, // Marking expiryStartedAt as 0 as expiration logic will be universally applied on received messages // We no longer set this to true anymore as it won't be used in the future, - false, - false, - Optional.absent(), - Optional.fromNullable(groupAddress), - Optional.absent(), - DisappearingMessageUpdate(message.expiryMode), - Optional.absent(), - Optional.absent(), - Optional.absent(), - Optional.absent() + isMessageRequestResponse = false, + hasMention = false, + body = null, + group = groupAddress, + attachments = emptyList(), + proFeatures = ProFeatures.NONE, + messageContent = DisappearingMessageUpdate(message.expiryMode), + quote = null, + sharedContacts = emptyList(), + linkPreviews = emptyList(), + dataExtractionNotification = null ) //insert the timer update message mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) @@ -128,43 +128,43 @@ class ExpiringMessageManager @Inject constructor( message: ExpirationTimerUpdate, ): MessageId? { val sentTimestamp = message.sentTimestamp - val groupId = message.groupPublicKey + val groupId = message.groupPublicKey?.toAddress() as? Address.GroupLike val duration = message.expiryMode.expiryMillis try { - val serializedAddress = when { - groupId == null -> message.syncTarget ?: message.recipient!! - groupId.startsWith(IdPrefix.GROUP.value) -> groupId - else -> doubleEncodeGroupID(groupId) - } - val address = fromSerialized(serializedAddress) + val serializedAddress = groupId ?: (message.syncTarget ?: message.recipient!!).toAddress() - message.threadID = storage.get().getOrCreateThreadIdFor(address) + message.threadID = storage.get().getOrCreateThreadIdFor(serializedAddress) val content = DisappearingMessageUpdate(message.expiryMode) - val timerUpdateMessage = if (groupId != null) OutgoingGroupMediaMessage( - address, - "", - groupId, - null, - sentTimestamp!!, - duration, - 0, // Marking as 0 as expiration shouldn't start until we send the message - false, - null, - emptyList(), - emptyList(), - content - ) else OutgoingSecureMediaMessage( - address, - "", - emptyList(), - sentTimestamp!!, - DistributionTypes.CONVERSATION, - duration, - 0, // Marking as 0 as expiration shouldn't start until we send the message - null, - emptyList(), - emptyList(), - content + val timerUpdateMessage = if (groupId != null) OutgoingMediaMessage( + recipient = serializedAddress, + body = "", + group = groupId, + avatar = null, + sentTimeMillis = sentTimestamp!!, + expiresInMillis = duration, + expireStartedAtMillis = 0, // Marking as 0 as expiration shouldn't start until we send the message + isGroupUpdateMessage = false, + quote = null, + contacts = emptyList(), + previews = emptyList(), + messageContent = content + ) else OutgoingMediaMessage( + recipient = serializedAddress, + body = "", + attachments = emptyList(), + sentTimeMillis = sentTimestamp!!, + distributionType = DistributionTypes.CONVERSATION, + subscriptionId = -1, + expiresInMillis = duration, + expireStartedAtMillis = 0, // Marking as 0 as expiration shouldn't start until we send the message + outgoingQuote = null, + messageContent = content, + networkFailures = emptyList(), + identityKeyMismatches = emptyList(), + contacts = emptyList(), + linkPreviews = emptyList(), + group = null, + isGroupUpdateMessage = false ) return mmsDatabase.insertSecureDecryptedMessageOutbox( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7e1e2707e0..ec3c3d5217 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ kotlinVersion = "2.2.20" kryoVersion = "5.6.2" kspVersion = "2.3.0" legacySupportV13Version = "1.0.0" -libsessionUtilAndroidVersion = "1.0.9-12-ga1e5a23" +libsessionUtilAndroidVersion = "1.0.9-13-g49bee28" media3ExoplayerVersion = "1.8.0" mockitoCoreVersion = "5.20.0" navVersion = "2.9.5"